NRVisualProfiler.cs 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896
  1. /****************************************************************************
  2. * Copyright 2019 Nreal Techonology Limited. All rights reserved.
  3. *
  4. * This file is part of NRSDK.
  5. *
  6. * https://www.nreal.ai/
  7. *
  8. *****************************************************************************/
  9. namespace NRKernal
  10. {
  11. using System.Text;
  12. using UnityEngine;
  13. using UnityEngine.Profiling;
  14. #if WINDOWS_UWP
  15. using Windows.System;
  16. #endif
  17. /// <summary>
  18. /// ABOUT: The VisualProfiler provides a drop in, single file, solution for viewing your Nreal
  19. /// Unity application's frame rate and memory usage. Missed frames are displayed over time to
  20. /// visually find problem areas. Memory is reported as current, peak and max usage in a bar graph.
  21. ///
  22. /// USAGE: To use this profiler simply add this script as a component of any GameObject in your
  23. /// Unity scene. The profiler is initially active and visible (toggle-able via the IsVisible
  24. /// property), but can be toggled via the enabled/disable voice commands keywords.
  25. ///
  26. /// NOTE: For improved rendering performance you can optionally include the "Nreal/Instanced-
  27. /// Colored" shader in your project along with the VisualProfiler. </summary>
  28. public class NRVisualProfiler : MonoBehaviour
  29. {
  30. /// <summary> The maximum length of the string. </summary>
  31. private static readonly int maxStringLength = 32;
  32. /// <summary> The maximum target frame rate. </summary>
  33. private static readonly int maxTargetFrameRate = 120;
  34. /// <summary> The maximum frame timings. </summary>
  35. private static readonly int maxFrameTimings = 128;
  36. /// <summary> The frame range. </summary>
  37. private static readonly int frameRange = 30;
  38. /// <summary> The default window rotation. </summary>
  39. private static readonly Vector2 defaultWindowRotation = new Vector2(10.0f, 20.0f);
  40. /// <summary> The default window scale. </summary>
  41. private static readonly Vector3 defaultWindowScale = new Vector3(0.2f, 0.04f, 1.0f);
  42. /// <summary> The used memory string. </summary>
  43. private static readonly string usedMemoryString = "Used: ";
  44. /// <summary> The peak memory string. </summary>
  45. private static readonly string peakMemoryString = "Peak: ";
  46. /// <summary> The limit memory string. </summary>
  47. private static readonly string limitMemoryString = "Limit: ";
  48. /// <summary> Gets or sets the window parent. </summary>
  49. /// <value> The window parent. </value>
  50. public Transform WindowParent { get; set; } = null;
  51. /// <summary> True if is visible, false if not. </summary>
  52. [Header("Profiler Settings")]
  53. [SerializeField, Tooltip("Is the profiler currently visible.")]
  54. private bool isVisible = true;
  55. /// <summary> Gets or sets a value indicating whether this object is visible. </summary>
  56. /// <value> True if this object is visible, false if not. </value>
  57. public bool IsVisible
  58. {
  59. get { return isVisible; }
  60. set { isVisible = value; }
  61. }
  62. /// <summary> The frame sample rate. </summary>
  63. [SerializeField, Tooltip("The amount of time, in seconds, to collect frames for frame rate calculation.")]
  64. private float frameSampleRate = 0.1f;
  65. /// <summary> Gets or sets the frame sample rate. </summary>
  66. /// <value> The frame sample rate. </value>
  67. public float FrameSampleRate
  68. {
  69. get { return frameSampleRate; }
  70. set { frameSampleRate = value; }
  71. }
  72. /// <summary> The window anchor. </summary>
  73. [Header("Window Settings")]
  74. [SerializeField, Tooltip("What part of the view port to anchor the window to.")]
  75. private TextAnchor windowAnchor = TextAnchor.LowerCenter;
  76. /// <summary> Gets or sets the window anchor. </summary>
  77. /// <value> The window anchor. </value>
  78. public TextAnchor WindowAnchor
  79. {
  80. get { return windowAnchor; }
  81. set { windowAnchor = value; }
  82. }
  83. /// <summary> The window offset. </summary>
  84. [SerializeField, Tooltip("The offset from the view port center applied based on the window anchor selection.")]
  85. private Vector2 windowOffset = new Vector2(0.1f, 0.1f);
  86. /// <summary> Gets or sets the window offset. </summary>
  87. /// <value> The window offset. </value>
  88. public Vector2 WindowOffset
  89. {
  90. get { return windowOffset; }
  91. set { windowOffset = value; }
  92. }
  93. /// <summary> The window scale. </summary>
  94. [SerializeField, Range(0.5f, 5.0f), Tooltip("Use to scale the window size up or down, can simulate a zooming effect.")]
  95. private float windowScale = 1.0f;
  96. /// <summary> Gets or sets the window scale. </summary>
  97. /// <value> The window scale. </value>
  98. public float WindowScale
  99. {
  100. get { return windowScale; }
  101. set { windowScale = Mathf.Clamp(value, 0.5f, 5.0f); }
  102. }
  103. /// <summary> The window follow speed. </summary>
  104. [SerializeField, Range(0.0f, 100.0f), Tooltip("How quickly to interpolate the window towards its target position and rotation.")]
  105. private float windowFollowSpeed = 5.0f;
  106. /// <summary> Gets or sets the window follow speed. </summary>
  107. /// <value> The window follow speed. </value>
  108. public float WindowFollowSpeed
  109. {
  110. get { return windowFollowSpeed; }
  111. set { windowFollowSpeed = Mathf.Abs(value); }
  112. }
  113. /// <summary> The toggle keyworlds. </summary>
  114. [Header("UI Settings")]
  115. [SerializeField, Tooltip("Voice commands to toggle the profiler on and off.")]
  116. private string[] toggleKeyworlds = new string[] { "Profiler", "Toggle Profiler", "Show Profiler", "Hide Profiler" };
  117. /// <summary> The displayed decimal digits. </summary>
  118. [SerializeField, Range(0, 3), Tooltip("How many decimal places to display on numeric strings.")]
  119. private int displayedDecimalDigits = 1;
  120. /// <summary> The base color. </summary>
  121. [SerializeField, Tooltip("The color of the window backplate.")]
  122. private Color baseColor = new Color(80 / 256.0f, 80 / 256.0f, 80 / 256.0f, 1.0f);
  123. /// <summary> The target frame rate color. </summary>
  124. [SerializeField, Tooltip("The color to display on frames which meet or exceed the target frame rate.")]
  125. private Color targetFrameRateColor = new Color(127 / 256.0f, 186 / 256.0f, 0 / 256.0f, 1.0f);
  126. /// <summary> The missed frame rate color. </summary>
  127. [SerializeField, Tooltip("The color to display on frames which fall below the target frame rate.")]
  128. private Color missedFrameRateColor = new Color(242 / 256.0f, 80 / 256.0f, 34 / 256.0f, 1.0f);
  129. /// <summary> The memory used color. </summary>
  130. [SerializeField, Tooltip("The color to display for current memory usage values.")]
  131. private Color memoryUsedColor = new Color(0 / 256.0f, 164 / 256.0f, 239 / 256.0f, 1.0f);
  132. /// <summary> The memory peak color. </summary>
  133. [SerializeField, Tooltip("The color to display for peak (aka max) memory usage values.")]
  134. private Color memoryPeakColor = new Color(255 / 256.0f, 185 / 256.0f, 0 / 256.0f, 1.0f);
  135. /// <summary> The memory limit color. </summary>
  136. [SerializeField, Tooltip("The color to display for the platforms memory usage limit.")]
  137. private Color memoryLimitColor = new Color(150 / 256.0f, 150 / 256.0f, 150 / 256.0f, 1.0f);
  138. /// <summary> The window. </summary>
  139. private GameObject window;
  140. /// <summary> The CPU frame rate text. </summary>
  141. private TextMesh cpuFrameRateText;
  142. #if USING_XR_SDK && !UNITY_EDITOR
  143. /// <summary> The Dropped frame count in last one second. </summary>
  144. private TextMesh droppedFrameCount;
  145. private static readonly string droppedFrameCountString = "DroppedFrameCount: {0}";
  146. #endif
  147. /// <summary> The GPU frame rate text. </summary>
  148. private TextMesh gpuFrameRateText;
  149. /// <summary> The used memory text. </summary>
  150. private TextMesh usedMemoryText;
  151. /// <summary> The peak memory text. </summary>
  152. private TextMesh peakMemoryText;
  153. /// <summary> The limit memory text. </summary>
  154. private TextMesh limitMemoryText;
  155. /// <summary> The used anchor. </summary>
  156. private Transform usedAnchor;
  157. /// <summary> The peak anchor. </summary>
  158. private Transform peakAnchor;
  159. /// <summary> The window horizontal rotation. </summary>
  160. private Quaternion windowHorizontalRotation;
  161. /// <summary> The window horizontal rotation inverse. </summary>
  162. private Quaternion windowHorizontalRotationInverse;
  163. /// <summary> The window vertical rotation. </summary>
  164. private Quaternion windowVerticalRotation;
  165. /// <summary> The window vertical rotation inverse. </summary>
  166. private Quaternion windowVerticalRotationInverse;
  167. /// <summary> The frame information matrices. </summary>
  168. private Matrix4x4[] frameInfoMatrices;
  169. /// <summary> List of colors of the frame informations. </summary>
  170. private Vector4[] frameInfoColors;
  171. /// <summary> The frame information property block. </summary>
  172. private MaterialPropertyBlock frameInfoPropertyBlock;
  173. /// <summary> Identifier for the color. </summary>
  174. private int colorID;
  175. /// <summary> Identifier for the parent matrix. </summary>
  176. private int parentMatrixID;
  177. /// <summary> Number of frames. </summary>
  178. private int frameCount;
  179. /// <summary> The stopwatch. </summary>
  180. private System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
  181. /// <summary> The frame timings. </summary>
  182. private FrameTiming[] frameTimings = new FrameTiming[maxFrameTimings];
  183. /// <summary> The CPU frame rate strings. </summary>
  184. private string[] cpuFrameRateStrings;
  185. /// <summary> The GPU frame rate strings. </summary>
  186. private string[] gpuFrameRateStrings;
  187. /// <summary> Buffer for string data. </summary>
  188. private char[] stringBuffer = new char[maxStringLength];
  189. /// <summary> The memory usage. </summary>
  190. private ulong memoryUsage;
  191. /// <summary> The peak memory usage. </summary>
  192. private ulong peakMemoryUsage;
  193. /// <summary> The limit memory usage. </summary>
  194. private ulong limitMemoryUsage;
  195. /// <summary> Rendering resources. </summary>
  196. [SerializeField, HideInInspector]
  197. private Material defaultMaterial;
  198. /// <summary> The default instanced material. </summary>
  199. [SerializeField, HideInInspector]
  200. private Material defaultInstancedMaterial;
  201. /// <summary> The background material. </summary>
  202. private Material backgroundMaterial;
  203. /// <summary> The foreground material. </summary>
  204. private Material foregroundMaterial;
  205. /// <summary> The text material. </summary>
  206. private Material textMaterial;
  207. /// <summary> The quad mesh. </summary>
  208. private Mesh quadMesh;
  209. private Transform m_CenterCamera;
  210. private Transform CenterCamera
  211. {
  212. get
  213. {
  214. if (m_CenterCamera == null)
  215. {
  216. if (NRSessionManager.Instance.CenterCameraAnchor != null)
  217. {
  218. m_CenterCamera = NRSessionManager.Instance.CenterCameraAnchor;
  219. }
  220. else
  221. {
  222. m_CenterCamera = Camera.main.transform;
  223. }
  224. }
  225. return m_CenterCamera;
  226. }
  227. }
  228. /// <summary> The instance. </summary>
  229. private static NRVisualProfiler m_Instance = null;
  230. /// <summary> Gets the instance. </summary>
  231. /// <value> The instance. </value>
  232. public static NRVisualProfiler Instance
  233. {
  234. get
  235. {
  236. if (m_Instance == null)
  237. {
  238. GameObject go = new GameObject("VisualProfiler");
  239. DontDestroyOnLoad(go);
  240. m_Instance = go.AddComponent<NRVisualProfiler>();
  241. }
  242. return m_Instance;
  243. }
  244. }
  245. /// <summary> Awakes this object. </summary>
  246. void Awake()
  247. {
  248. if (m_Instance != null && m_Instance != this)
  249. {
  250. DestroyImmediate(this.gameObject);
  251. return;
  252. }
  253. m_Instance = this;
  254. }
  255. /// <summary> Switches. </summary>
  256. /// <param name="flag"> True to flag.</param>
  257. public void Switch(bool flag)
  258. {
  259. this.gameObject.SetActive(flag);
  260. }
  261. /// <summary> Resets this object. </summary>
  262. private void Reset()
  263. {
  264. if (defaultMaterial == null)
  265. {
  266. defaultMaterial = new Material(Shader.Find("Nreal/Instanced-Colored"));
  267. defaultMaterial.SetFloat("_ZWrite", 0.0f);
  268. defaultMaterial.SetFloat("_ZTest", (float)UnityEngine.Rendering.CompareFunction.Disabled);
  269. defaultMaterial.renderQueue = 5000;
  270. }
  271. if (defaultInstancedMaterial == null)
  272. {
  273. Shader defaultInstancedShader = Shader.Find("Nreal/Instanced-Colored");
  274. if (defaultInstancedShader != null)
  275. {
  276. defaultInstancedMaterial = new Material(defaultInstancedShader);
  277. defaultInstancedMaterial.enableInstancing = true;
  278. defaultInstancedMaterial.SetFloat("_ZWrite", 0.0f);
  279. defaultInstancedMaterial.SetFloat("_ZTest", (float)UnityEngine.Rendering.CompareFunction.Disabled);
  280. defaultInstancedMaterial.renderQueue = 5000;
  281. }
  282. else
  283. {
  284. Debug.LogWarning("A shader supporting instancing could not be found for the VisualProfiler, falling back to traditional rendering. This may impact performance.");
  285. }
  286. }
  287. if (Application.isPlaying)
  288. {
  289. backgroundMaterial = new Material(defaultMaterial);
  290. foregroundMaterial = new Material(defaultMaterial);
  291. defaultMaterial.renderQueue = foregroundMaterial.renderQueue - 1;
  292. backgroundMaterial.renderQueue = defaultMaterial.renderQueue - 1;
  293. MeshRenderer meshRenderer = new GameObject().AddComponent<TextMesh>().GetComponent<MeshRenderer>();
  294. textMaterial = new Material(meshRenderer.sharedMaterial);
  295. textMaterial.renderQueue = defaultMaterial.renderQueue;
  296. Destroy(meshRenderer.gameObject);
  297. MeshFilter quadMeshFilter = GameObject.CreatePrimitive(PrimitiveType.Quad).GetComponent<MeshFilter>();
  298. if (defaultInstancedMaterial != null)
  299. {
  300. // Create a quad mesh with artificially large bounds to disable culling for instanced rendering.
  301. quadMesh = quadMeshFilter.mesh;
  302. quadMesh.bounds = new Bounds(Vector3.zero, Vector3.one * float.MaxValue);
  303. }
  304. else
  305. {
  306. quadMesh = quadMeshFilter.sharedMesh;
  307. }
  308. Destroy(quadMeshFilter.gameObject);
  309. }
  310. stopwatch.Reset();
  311. stopwatch.Start();
  312. }
  313. /// <summary> Starts this object. </summary>
  314. private void Start()
  315. {
  316. Reset();
  317. BuildWindow();
  318. BuildFrameRateStrings();
  319. }
  320. /// <summary> Executes the 'destroy' action. </summary>
  321. private void OnDestroy()
  322. {
  323. Destroy(window);
  324. }
  325. /// <summary> Late update. </summary>
  326. private void LateUpdate()
  327. {
  328. if (window == null)
  329. {
  330. return;
  331. }
  332. // Update window transformation.
  333. if (window.activeSelf && CenterCamera != null)
  334. {
  335. float t = Time.deltaTime * windowFollowSpeed;
  336. window.transform.position = Vector3.Lerp(window.transform.position, CalculateWindowPosition(CenterCamera.transform), t);
  337. window.transform.rotation = Quaternion.Slerp(window.transform.rotation, CalculateWindowRotation(CenterCamera.transform), t);
  338. window.transform.localScale = defaultWindowScale * windowScale;
  339. }
  340. // Capture frame timings every frame and read from it depending on the frameSampleRate.
  341. FrameTimingManager.CaptureFrameTimings();
  342. ++frameCount;
  343. float elapsedSeconds = stopwatch.ElapsedMilliseconds * 0.001f;
  344. if (elapsedSeconds >= frameSampleRate)
  345. {
  346. int cpuFrameRate = (int)(1.0f / (elapsedSeconds / frameCount));
  347. int gpuFrameRate = 0;
  348. // Many platforms do not yet support the FrameTimingManager. When timing data is returned from the FrameTimingManager we will use
  349. // its timing data, else we will depend on the stopwatch.
  350. uint frameTimingsCount = FrameTimingManager.GetLatestTimings((uint)Mathf.Min(frameCount, maxFrameTimings), frameTimings);
  351. if (frameTimingsCount != 0)
  352. {
  353. float cpuFrameTime, gpuFrameTime;
  354. AverageFrameTiming(frameTimings, frameTimingsCount, out cpuFrameTime, out gpuFrameTime);
  355. cpuFrameRate = (int)(1.0f / (cpuFrameTime / frameCount));
  356. gpuFrameRate = (int)(1.0f / (gpuFrameTime / frameCount));
  357. }
  358. // Update frame rate text.
  359. cpuFrameRateText.text = cpuFrameRateStrings[Mathf.Clamp(cpuFrameRate, 0, maxTargetFrameRate)];
  360. #if USING_XR_SDK && !UNITY_EDITOR
  361. int dropped_framecount = 0;
  362. NRSessionManager.Instance.XRDisplaySubsystem?.TryGetDroppedFrameCount(out dropped_framecount);
  363. droppedFrameCount.text = string.Format(droppedFrameCountString, dropped_framecount);
  364. #endif
  365. if (gpuFrameRate != 0)
  366. {
  367. gpuFrameRateText.gameObject.SetActive(true);
  368. gpuFrameRateText.text = gpuFrameRateStrings[Mathf.Clamp(gpuFrameRate, 0, maxTargetFrameRate)];
  369. }
  370. // Update frame colors.
  371. for (int i = frameRange - 1; i > 0; --i)
  372. {
  373. frameInfoColors[i] = frameInfoColors[i - 1];
  374. }
  375. // Ideally we would query a device specific API (like the HolographicFramePresentationReport) to detect missed frames.
  376. // But, many of these APIs are inaccessible in Unity. Currently missed frames are assumed when the average cpuFrameRate
  377. // is under the target frame rate.
  378. frameInfoColors[0] = (cpuFrameRate < ((int)(AppFrameRate) - 1)) ? missedFrameRateColor : targetFrameRateColor;
  379. frameInfoPropertyBlock.SetVectorArray(colorID, frameInfoColors);
  380. // Reset timers.
  381. frameCount = 0;
  382. stopwatch.Reset();
  383. stopwatch.Start();
  384. }
  385. // Draw frame info.
  386. if (window.activeSelf)
  387. {
  388. Matrix4x4 parentLocalToWorldMatrix = window.transform.localToWorldMatrix;
  389. //if (defaultInstancedMaterial != null)
  390. //{
  391. // frameInfoPropertyBlock.SetMatrix(parentMatrixID, parentLocalToWorldMatrix);
  392. // Graphics.DrawMeshInstanced(quadMesh, 0, defaultInstancedMaterial, frameInfoMatrices, frameInfoMatrices.Length, frameInfoPropertyBlock, UnityEngine.Rendering.ShadowCastingMode.Off, false);
  393. //}
  394. //else
  395. //{
  396. // If a instanced material is not available, fall back to non-instanced rendering.
  397. for (int i = 0; i < frameInfoMatrices.Length; ++i)
  398. {
  399. frameInfoPropertyBlock.SetColor(colorID, frameInfoColors[i]);
  400. Graphics.DrawMesh(quadMesh, parentLocalToWorldMatrix * frameInfoMatrices[i], defaultMaterial, 0, null, 0, frameInfoPropertyBlock, false, false, false);
  401. }
  402. //}
  403. }
  404. // Update memory statistics.
  405. ulong limit = AppMemoryUsageLimit;
  406. if (limit != limitMemoryUsage)
  407. {
  408. if (window.activeSelf && WillDisplayedMemoryUsageDiffer(limitMemoryUsage, limit, displayedDecimalDigits))
  409. {
  410. MemoryUsageToString(stringBuffer, displayedDecimalDigits, limitMemoryText, limitMemoryString, limit);
  411. }
  412. limitMemoryUsage = limit;
  413. }
  414. ulong usage = AppMemoryUsage;
  415. if (usage != memoryUsage)
  416. {
  417. usedAnchor.localScale = new Vector3((float)usage / limitMemoryUsage, usedAnchor.localScale.y, usedAnchor.localScale.z);
  418. if (window.activeSelf && WillDisplayedMemoryUsageDiffer(memoryUsage, usage, displayedDecimalDigits))
  419. {
  420. MemoryUsageToString(stringBuffer, displayedDecimalDigits, usedMemoryText, usedMemoryString, usage);
  421. }
  422. memoryUsage = usage;
  423. }
  424. if (memoryUsage > peakMemoryUsage)
  425. {
  426. peakAnchor.localScale = new Vector3((float)memoryUsage / limitMemoryUsage, peakAnchor.localScale.y, peakAnchor.localScale.z);
  427. if (window.activeSelf && WillDisplayedMemoryUsageDiffer(peakMemoryUsage, memoryUsage, displayedDecimalDigits))
  428. {
  429. MemoryUsageToString(stringBuffer, displayedDecimalDigits, peakMemoryText, peakMemoryString, memoryUsage);
  430. }
  431. peakMemoryUsage = memoryUsage;
  432. }
  433. window.SetActive(isVisible);
  434. }
  435. /// <summary> Calculates the window position. </summary>
  436. /// <param name="cameraTransform"> The camera transform.</param>
  437. /// <returns> The calculated window position. </returns>
  438. private Vector3 CalculateWindowPosition(Transform cameraTransform)
  439. {
  440. float windowDistance = Mathf.Max(16.0f / Camera.main.fieldOfView, Camera.main.nearClipPlane + 0.25f);
  441. Vector3 position = cameraTransform.position + (cameraTransform.forward * windowDistance);
  442. Vector3 horizontalOffset = cameraTransform.right * windowOffset.x;
  443. Vector3 verticalOffset = cameraTransform.up * windowOffset.y;
  444. switch (windowAnchor)
  445. {
  446. case TextAnchor.UpperLeft: position += verticalOffset - horizontalOffset; break;
  447. case TextAnchor.UpperCenter: position += verticalOffset; break;
  448. case TextAnchor.UpperRight: position += verticalOffset + horizontalOffset; break;
  449. case TextAnchor.MiddleLeft: position -= horizontalOffset; break;
  450. case TextAnchor.MiddleRight: position += horizontalOffset; break;
  451. case TextAnchor.LowerLeft: position -= verticalOffset + horizontalOffset; break;
  452. case TextAnchor.LowerCenter: position -= verticalOffset; break;
  453. case TextAnchor.LowerRight: position -= verticalOffset - horizontalOffset; break;
  454. }
  455. return position;
  456. }
  457. /// <summary> Calculates the window rotation. </summary>
  458. /// <param name="cameraTransform"> The camera transform.</param>
  459. /// <returns> The calculated window rotation. </returns>
  460. private Quaternion CalculateWindowRotation(Transform cameraTransform)
  461. {
  462. Quaternion rotation = cameraTransform.rotation;
  463. switch (windowAnchor)
  464. {
  465. case TextAnchor.UpperLeft: rotation *= windowHorizontalRotationInverse * windowVerticalRotationInverse; break;
  466. case TextAnchor.UpperCenter: rotation *= windowHorizontalRotationInverse; break;
  467. case TextAnchor.UpperRight: rotation *= windowHorizontalRotationInverse * windowVerticalRotation; break;
  468. case TextAnchor.MiddleLeft: rotation *= windowVerticalRotationInverse; break;
  469. case TextAnchor.MiddleRight: rotation *= windowVerticalRotation; break;
  470. case TextAnchor.LowerLeft: rotation *= windowHorizontalRotation * windowVerticalRotationInverse; break;
  471. case TextAnchor.LowerCenter: rotation *= windowHorizontalRotation; break;
  472. case TextAnchor.LowerRight: rotation *= windowHorizontalRotation * windowVerticalRotation; break;
  473. }
  474. return rotation;
  475. }
  476. /// <summary> Builds the window. </summary>
  477. private void BuildWindow()
  478. {
  479. // Initialize property block state.
  480. colorID = Shader.PropertyToID("_Color");
  481. parentMatrixID = Shader.PropertyToID("_ParentLocalToWorldMatrix");
  482. WindowParent = transform;
  483. // Build the window root.
  484. {
  485. window = CreateQuad("VisualProfiler", null);
  486. window.transform.parent = WindowParent;
  487. InitializeRenderer(window, backgroundMaterial, colorID, baseColor);
  488. window.transform.localScale = defaultWindowScale;
  489. windowHorizontalRotation = Quaternion.AngleAxis(defaultWindowRotation.y, Vector3.right);
  490. windowHorizontalRotationInverse = Quaternion.Inverse(windowHorizontalRotation);
  491. windowVerticalRotation = Quaternion.AngleAxis(defaultWindowRotation.x, Vector3.up);
  492. windowVerticalRotationInverse = Quaternion.Inverse(windowVerticalRotation);
  493. }
  494. // Add frame rate text and frame indicators.
  495. {
  496. cpuFrameRateText = CreateText("CPUFrameRateText", new Vector3(-0.495f, 0.5f, 0.0f), window.transform, TextAnchor.UpperLeft, textMaterial, Color.white, string.Empty);
  497. #if USING_XR_SDK && !UNITY_EDITOR
  498. droppedFrameCount = CreateText("DroppedFrameCount", new Vector3(0, 0.5f, 0.0f), window.transform, TextAnchor.UpperLeft, textMaterial, Color.white, string.Empty);
  499. #endif
  500. gpuFrameRateText = CreateText("GPUFrameRateText", new Vector3(0.495f, 0.5f, 0.0f), window.transform, TextAnchor.UpperRight, textMaterial, Color.white, string.Empty);
  501. gpuFrameRateText.gameObject.SetActive(false);
  502. frameInfoMatrices = new Matrix4x4[frameRange];
  503. frameInfoColors = new Vector4[frameRange];
  504. Vector3 scale = new Vector3(1.0f / frameRange, 0.2f, 1.0f);
  505. Vector3 position = new Vector3(0.5f - (scale.x * 0.5f), 0.15f, 0.0f);
  506. for (int i = 0; i < frameRange; ++i)
  507. {
  508. frameInfoMatrices[i] = Matrix4x4.TRS(position, Quaternion.identity, new Vector3(scale.x * 0.8f, scale.y, scale.z));
  509. position.x -= scale.x;
  510. frameInfoColors[i] = targetFrameRateColor;
  511. }
  512. frameInfoPropertyBlock = new MaterialPropertyBlock();
  513. frameInfoPropertyBlock.SetVectorArray(colorID, frameInfoColors);
  514. }
  515. // Add memory usage text and bars.
  516. {
  517. usedMemoryText = CreateText("UsedMemoryText", new Vector3(-0.495f, 0.0f, 0.0f), window.transform, TextAnchor.UpperLeft, textMaterial, memoryUsedColor, usedMemoryString);
  518. peakMemoryText = CreateText("PeakMemoryText", new Vector3(0.0f, 0.0f, 0.0f), window.transform, TextAnchor.UpperCenter, textMaterial, memoryPeakColor, peakMemoryString);
  519. limitMemoryText = CreateText("LimitMemoryText", new Vector3(0.495f, 0.0f, 0.0f), window.transform, TextAnchor.UpperRight, textMaterial, Color.white, limitMemoryString);
  520. GameObject limitBar = CreateQuad("LimitBar", window.transform);
  521. InitializeRenderer(limitBar, defaultMaterial, colorID, memoryLimitColor);
  522. limitBar.transform.localScale = new Vector3(0.99f, 0.2f, 1.0f);
  523. limitBar.transform.localPosition = new Vector3(0.0f, -0.37f, 0.0f);
  524. {
  525. usedAnchor = CreateAnchor("UsedAnchor", limitBar.transform);
  526. GameObject bar = CreateQuad("UsedBar", usedAnchor);
  527. Material material = new Material(foregroundMaterial);
  528. material.renderQueue = material.renderQueue + 1;
  529. InitializeRenderer(bar, material, colorID, memoryUsedColor);
  530. bar.transform.localScale = Vector3.one;
  531. bar.transform.localPosition = new Vector3(0.5f, 0.0f, 0.0f);
  532. }
  533. {
  534. peakAnchor = CreateAnchor("PeakAnchor", limitBar.transform);
  535. GameObject bar = CreateQuad("PeakBar", peakAnchor);
  536. InitializeRenderer(bar, foregroundMaterial, colorID, memoryPeakColor);
  537. bar.transform.localScale = Vector3.one;
  538. bar.transform.localPosition = new Vector3(0.5f, 0.0f, 0.0f);
  539. }
  540. }
  541. window.SetActive(isVisible);
  542. }
  543. /// <summary> Builds frame rate strings. </summary>
  544. private void BuildFrameRateStrings()
  545. {
  546. cpuFrameRateStrings = new string[maxTargetFrameRate + 1];
  547. gpuFrameRateStrings = new string[maxTargetFrameRate + 1];
  548. string displayedDecimalFormat = string.Format("{{0:F{0}}}", displayedDecimalDigits);
  549. StringBuilder stringBuilder = new StringBuilder(32);
  550. StringBuilder milisecondStringBuilder = new StringBuilder(16);
  551. for (int i = 0; i < cpuFrameRateStrings.Length; ++i)
  552. {
  553. float miliseconds = (i == 0) ? 0.0f : (1.0f / i) * 1000.0f;
  554. milisecondStringBuilder.AppendFormat(displayedDecimalFormat, miliseconds);
  555. stringBuilder.AppendFormat("CPU: {0} fps ({1} ms)", i.ToString(), milisecondStringBuilder.ToString());
  556. cpuFrameRateStrings[i] = stringBuilder.ToString();
  557. stringBuilder.Length = 0;
  558. stringBuilder.AppendFormat("GPU: {0} fps ({1} ms)", i.ToString(), milisecondStringBuilder.ToString());
  559. gpuFrameRateStrings[i] = stringBuilder.ToString();
  560. milisecondStringBuilder.Length = 0;
  561. stringBuilder.Length = 0;
  562. }
  563. }
  564. /// <summary> Creates an anchor. </summary>
  565. /// <param name="name"> The name.</param>
  566. /// <param name="parent"> The parent.</param>
  567. /// <returns> The new anchor. </returns>
  568. private static Transform CreateAnchor(string name, Transform parent)
  569. {
  570. Transform anchor = new GameObject(name).transform;
  571. anchor.parent = parent;
  572. anchor.localScale = Vector3.one;
  573. anchor.localPosition = new Vector3(-0.5f, 0.0f, 0.0f);
  574. return anchor;
  575. }
  576. /// <summary> Creates a quad. </summary>
  577. /// <param name="name"> The name.</param>
  578. /// <param name="parent"> The parent.</param>
  579. /// <returns> The new quad. </returns>
  580. private static GameObject CreateQuad(string name, Transform parent)
  581. {
  582. GameObject quad = GameObject.CreatePrimitive(PrimitiveType.Quad);
  583. Destroy(quad.GetComponent<Collider>());
  584. quad.name = name;
  585. quad.transform.parent = parent;
  586. return quad;
  587. }
  588. /// <summary> Creates a text. </summary>
  589. /// <param name="name"> The name.</param>
  590. /// <param name="position"> The position.</param>
  591. /// <param name="parent"> The parent.</param>
  592. /// <param name="anchor"> The anchor.</param>
  593. /// <param name="material"> The material.</param>
  594. /// <param name="color"> The color.</param>
  595. /// <param name="text"> The text.</param>
  596. /// <returns> The new text. </returns>
  597. private static TextMesh CreateText(string name, Vector3 position, Transform parent, TextAnchor anchor, Material material, Color color, string text)
  598. {
  599. GameObject obj = new GameObject(name);
  600. obj.transform.localScale = Vector3.one * 0.0016f;
  601. obj.transform.parent = parent;
  602. obj.transform.localPosition = position;
  603. TextMesh textMesh = obj.AddComponent<TextMesh>();
  604. textMesh.fontSize = 48;
  605. textMesh.anchor = anchor;
  606. textMesh.color = color;
  607. textMesh.text = text;
  608. textMesh.richText = false;
  609. Renderer renderer = obj.GetComponent<Renderer>();
  610. renderer.sharedMaterial = material;
  611. OptimizeRenderer(renderer);
  612. return textMesh;
  613. }
  614. /// <summary> Initializes the renderer. </summary>
  615. /// <param name="obj"> The object.</param>
  616. /// <param name="material"> The material.</param>
  617. /// <param name="colorID"> Identifier for the color.</param>
  618. /// <param name="color"> The color.</param>
  619. /// <returns> A Renderer. </returns>
  620. private static Renderer InitializeRenderer(GameObject obj, Material material, int colorID, Color color)
  621. {
  622. Renderer renderer = obj.GetComponent<Renderer>();
  623. renderer.sharedMaterial = material;
  624. MaterialPropertyBlock propertyBlock = new MaterialPropertyBlock();
  625. renderer.GetPropertyBlock(propertyBlock);
  626. propertyBlock.SetColor(colorID, color);
  627. renderer.SetPropertyBlock(propertyBlock);
  628. OptimizeRenderer(renderer);
  629. return renderer;
  630. }
  631. /// <summary> Optimize renderer. </summary>
  632. /// <param name="renderer"> The renderer.</param>
  633. private static void OptimizeRenderer(Renderer renderer)
  634. {
  635. renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
  636. renderer.receiveShadows = false;
  637. renderer.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
  638. renderer.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off;
  639. renderer.reflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Off;
  640. renderer.allowOcclusionWhenDynamic = false;
  641. }
  642. /// <summary> Memory usage to string. </summary>
  643. /// <param name="stringBuffer"> Buffer for string data.</param>
  644. /// <param name="displayedDecimalDigits"> The displayed decimal digits.</param>
  645. /// <param name="textMesh"> The text mesh.</param>
  646. /// <param name="prefixString"> The prefix string.</param>
  647. /// <param name="memoryUsage"> The memory usage.</param>
  648. private static void MemoryUsageToString(char[] stringBuffer, int displayedDecimalDigits, TextMesh textMesh, string prefixString, ulong memoryUsage)
  649. {
  650. // Using a custom number to string method to avoid the overhead, and allocations, of built in string.Format/StringBuilder methods.
  651. // We can also make some assumptions since the domain of the input number (memoryUsage) is known.
  652. float memoryUsageMB = ConvertBytesToMegabytes(memoryUsage);
  653. int memoryUsageIntegerDigits = (int)memoryUsageMB;
  654. int memoryUsageFractionalDigits = (int)((memoryUsageMB - memoryUsageIntegerDigits) * Mathf.Pow(10.0f, displayedDecimalDigits));
  655. int bufferIndex = 0;
  656. for (int i = 0; i < prefixString.Length; ++i)
  657. {
  658. stringBuffer[bufferIndex++] = prefixString[i];
  659. }
  660. bufferIndex = MemoryItoA(memoryUsageIntegerDigits, stringBuffer, bufferIndex);
  661. stringBuffer[bufferIndex++] = '.';
  662. if (memoryUsageFractionalDigits != 0)
  663. {
  664. bufferIndex = MemoryItoA(memoryUsageFractionalDigits, stringBuffer, bufferIndex);
  665. }
  666. else
  667. {
  668. for (int i = 0; i < displayedDecimalDigits; ++i)
  669. {
  670. stringBuffer[bufferIndex++] = '0';
  671. }
  672. }
  673. stringBuffer[bufferIndex++] = 'M';
  674. stringBuffer[bufferIndex++] = 'B';
  675. textMesh.text = new string(stringBuffer, 0, bufferIndex);
  676. }
  677. /// <summary> Memory ito a. </summary>
  678. /// <param name="value"> The value.</param>
  679. /// <param name="stringBuffer"> Buffer for string data.</param>
  680. /// <param name="bufferIndex"> Zero-based index of the buffer.</param>
  681. /// <returns> An int. </returns>
  682. private static int MemoryItoA(int value, char[] stringBuffer, int bufferIndex)
  683. {
  684. int startIndex = bufferIndex;
  685. for (; value != 0; value /= 10)
  686. {
  687. stringBuffer[bufferIndex++] = (char)((char)(value % 10) + '0');
  688. }
  689. char temp;
  690. for (int endIndex = bufferIndex - 1; startIndex < endIndex; ++startIndex, --endIndex)
  691. {
  692. temp = stringBuffer[startIndex];
  693. stringBuffer[startIndex] = stringBuffer[endIndex];
  694. stringBuffer[endIndex] = temp;
  695. }
  696. return bufferIndex;
  697. }
  698. /// <summary> Gets the application frame rate. </summary>
  699. /// <value> The application frame rate. </value>
  700. private static float AppFrameRate
  701. {
  702. get
  703. {
  704. // If the current XR SDK does not report refresh rate information, assume 60Hz.
  705. //float refreshRate = UnityEngine.XR.XRDevice.refreshRate;
  706. //return ((int)refreshRate == 0) ? 60.0f : refreshRate;
  707. return 45f;
  708. }
  709. }
  710. /// <summary> Average frame timing. </summary>
  711. /// <param name="frameTimings"> The frame timings.</param>
  712. /// <param name="frameTimingsCount"> Number of frame timings.</param>
  713. /// <param name="cpuFrameTime"> [out] The CPU frame time.</param>
  714. /// <param name="gpuFrameTime"> [out] The GPU frame time.</param>
  715. private static void AverageFrameTiming(FrameTiming[] frameTimings, uint frameTimingsCount, out float cpuFrameTime, out float gpuFrameTime)
  716. {
  717. double cpuTime = 0.0f;
  718. double gpuTime = 0.0f;
  719. for (int i = 0; i < frameTimingsCount; ++i)
  720. {
  721. cpuTime += frameTimings[i].cpuFrameTime;
  722. gpuTime += frameTimings[i].gpuFrameTime;
  723. }
  724. cpuTime /= frameTimingsCount;
  725. gpuTime /= frameTimingsCount;
  726. cpuFrameTime = (float)(cpuTime * 0.001);
  727. gpuFrameTime = (float)(gpuTime * 0.001);
  728. }
  729. /// <summary> Gets the application memory usage. </summary>
  730. /// <value> The application memory usage. </value>
  731. private static ulong AppMemoryUsage
  732. {
  733. get
  734. {
  735. #if WINDOWS_UWP
  736. return MemoryManager.AppMemoryUsage;
  737. #else
  738. return (ulong)Profiler.GetTotalAllocatedMemoryLong();
  739. #endif
  740. }
  741. }
  742. /// <summary> Gets the application memory usage limit. </summary>
  743. /// <value> The application memory usage limit. </value>
  744. private static ulong AppMemoryUsageLimit
  745. {
  746. get
  747. {
  748. #if WINDOWS_UWP
  749. return MemoryManager.AppMemoryUsageLimit;
  750. #else
  751. return ConvertMegabytesToBytes(SystemInfo.systemMemorySize);
  752. #endif
  753. }
  754. }
  755. /// <summary> Will displayed memory usage differ. </summary>
  756. /// <param name="oldUsage"> The old usage.</param>
  757. /// <param name="newUsage"> The new usage.</param>
  758. /// <param name="displayedDecimalDigits"> The displayed decimal digits.</param>
  759. /// <returns> True if it succeeds, false if it fails. </returns>
  760. private static bool WillDisplayedMemoryUsageDiffer(ulong oldUsage, ulong newUsage, int displayedDecimalDigits)
  761. {
  762. float oldUsageMBs = ConvertBytesToMegabytes(oldUsage);
  763. float newUsageMBs = ConvertBytesToMegabytes(newUsage);
  764. float decimalPower = Mathf.Pow(10.0f, displayedDecimalDigits);
  765. return (int)(oldUsageMBs * decimalPower) != (int)(newUsageMBs * decimalPower);
  766. }
  767. /// <summary> Convert megabytes to bytes. </summary>
  768. /// <param name="megabytes"> The megabytes.</param>
  769. /// <returns> The megabytes converted to bytes. </returns>
  770. private static ulong ConvertMegabytesToBytes(int megabytes)
  771. {
  772. return ((ulong)megabytes * 1024UL) * 1024UL;
  773. }
  774. /// <summary> Convert bytes to megabytes. </summary>
  775. /// <param name="bytes"> The bytes.</param>
  776. /// <returns> The bytes converted to megabytes. </returns>
  777. private static float ConvertBytesToMegabytes(ulong bytes)
  778. {
  779. return (bytes / 1024.0f) / 1024.0f;
  780. }
  781. }
  782. }