123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896 |
- /****************************************************************************
- * Copyright 2019 Nreal Techonology Limited. All rights reserved.
- *
- * This file is part of NRSDK.
- *
- * https://www.nreal.ai/
- *
- *****************************************************************************/
- namespace NRKernal
- {
- using System.Text;
- using UnityEngine;
- using UnityEngine.Profiling;
- #if WINDOWS_UWP
- using Windows.System;
- #endif
- /// <summary>
- /// ABOUT: The VisualProfiler provides a drop in, single file, solution for viewing your Nreal
- /// Unity application's frame rate and memory usage. Missed frames are displayed over time to
- /// visually find problem areas. Memory is reported as current, peak and max usage in a bar graph.
- ///
- /// USAGE: To use this profiler simply add this script as a component of any GameObject in your
- /// Unity scene. The profiler is initially active and visible (toggle-able via the IsVisible
- /// property), but can be toggled via the enabled/disable voice commands keywords.
- ///
- /// NOTE: For improved rendering performance you can optionally include the "Nreal/Instanced-
- /// Colored" shader in your project along with the VisualProfiler. </summary>
- public class NRVisualProfiler : MonoBehaviour
- {
- /// <summary> The maximum length of the string. </summary>
- private static readonly int maxStringLength = 32;
- /// <summary> The maximum target frame rate. </summary>
- private static readonly int maxTargetFrameRate = 120;
- /// <summary> The maximum frame timings. </summary>
- private static readonly int maxFrameTimings = 128;
- /// <summary> The frame range. </summary>
- private static readonly int frameRange = 30;
- /// <summary> The default window rotation. </summary>
- private static readonly Vector2 defaultWindowRotation = new Vector2(10.0f, 20.0f);
- /// <summary> The default window scale. </summary>
- private static readonly Vector3 defaultWindowScale = new Vector3(0.2f, 0.04f, 1.0f);
- /// <summary> The used memory string. </summary>
- private static readonly string usedMemoryString = "Used: ";
- /// <summary> The peak memory string. </summary>
- private static readonly string peakMemoryString = "Peak: ";
- /// <summary> The limit memory string. </summary>
- private static readonly string limitMemoryString = "Limit: ";
- /// <summary> Gets or sets the window parent. </summary>
- /// <value> The window parent. </value>
- public Transform WindowParent { get; set; } = null;
- /// <summary> True if is visible, false if not. </summary>
- [Header("Profiler Settings")]
- [SerializeField, Tooltip("Is the profiler currently visible.")]
- private bool isVisible = true;
- /// <summary> Gets or sets a value indicating whether this object is visible. </summary>
- /// <value> True if this object is visible, false if not. </value>
- public bool IsVisible
- {
- get { return isVisible; }
- set { isVisible = value; }
- }
- /// <summary> The frame sample rate. </summary>
- [SerializeField, Tooltip("The amount of time, in seconds, to collect frames for frame rate calculation.")]
- private float frameSampleRate = 0.1f;
- /// <summary> Gets or sets the frame sample rate. </summary>
- /// <value> The frame sample rate. </value>
- public float FrameSampleRate
- {
- get { return frameSampleRate; }
- set { frameSampleRate = value; }
- }
- /// <summary> The window anchor. </summary>
- [Header("Window Settings")]
- [SerializeField, Tooltip("What part of the view port to anchor the window to.")]
- private TextAnchor windowAnchor = TextAnchor.LowerCenter;
- /// <summary> Gets or sets the window anchor. </summary>
- /// <value> The window anchor. </value>
- public TextAnchor WindowAnchor
- {
- get { return windowAnchor; }
- set { windowAnchor = value; }
- }
- /// <summary> The window offset. </summary>
- [SerializeField, Tooltip("The offset from the view port center applied based on the window anchor selection.")]
- private Vector2 windowOffset = new Vector2(0.1f, 0.1f);
- /// <summary> Gets or sets the window offset. </summary>
- /// <value> The window offset. </value>
- public Vector2 WindowOffset
- {
- get { return windowOffset; }
- set { windowOffset = value; }
- }
- /// <summary> The window scale. </summary>
- [SerializeField, Range(0.5f, 5.0f), Tooltip("Use to scale the window size up or down, can simulate a zooming effect.")]
- private float windowScale = 1.0f;
- /// <summary> Gets or sets the window scale. </summary>
- /// <value> The window scale. </value>
- public float WindowScale
- {
- get { return windowScale; }
- set { windowScale = Mathf.Clamp(value, 0.5f, 5.0f); }
- }
- /// <summary> The window follow speed. </summary>
- [SerializeField, Range(0.0f, 100.0f), Tooltip("How quickly to interpolate the window towards its target position and rotation.")]
- private float windowFollowSpeed = 5.0f;
- /// <summary> Gets or sets the window follow speed. </summary>
- /// <value> The window follow speed. </value>
- public float WindowFollowSpeed
- {
- get { return windowFollowSpeed; }
- set { windowFollowSpeed = Mathf.Abs(value); }
- }
- /// <summary> The toggle keyworlds. </summary>
- [Header("UI Settings")]
- [SerializeField, Tooltip("Voice commands to toggle the profiler on and off.")]
- private string[] toggleKeyworlds = new string[] { "Profiler", "Toggle Profiler", "Show Profiler", "Hide Profiler" };
- /// <summary> The displayed decimal digits. </summary>
- [SerializeField, Range(0, 3), Tooltip("How many decimal places to display on numeric strings.")]
- private int displayedDecimalDigits = 1;
- /// <summary> The base color. </summary>
- [SerializeField, Tooltip("The color of the window backplate.")]
- private Color baseColor = new Color(80 / 256.0f, 80 / 256.0f, 80 / 256.0f, 1.0f);
- /// <summary> The target frame rate color. </summary>
- [SerializeField, Tooltip("The color to display on frames which meet or exceed the target frame rate.")]
- private Color targetFrameRateColor = new Color(127 / 256.0f, 186 / 256.0f, 0 / 256.0f, 1.0f);
- /// <summary> The missed frame rate color. </summary>
- [SerializeField, Tooltip("The color to display on frames which fall below the target frame rate.")]
- private Color missedFrameRateColor = new Color(242 / 256.0f, 80 / 256.0f, 34 / 256.0f, 1.0f);
- /// <summary> The memory used color. </summary>
- [SerializeField, Tooltip("The color to display for current memory usage values.")]
- private Color memoryUsedColor = new Color(0 / 256.0f, 164 / 256.0f, 239 / 256.0f, 1.0f);
- /// <summary> The memory peak color. </summary>
- [SerializeField, Tooltip("The color to display for peak (aka max) memory usage values.")]
- private Color memoryPeakColor = new Color(255 / 256.0f, 185 / 256.0f, 0 / 256.0f, 1.0f);
- /// <summary> The memory limit color. </summary>
- [SerializeField, Tooltip("The color to display for the platforms memory usage limit.")]
- private Color memoryLimitColor = new Color(150 / 256.0f, 150 / 256.0f, 150 / 256.0f, 1.0f);
- /// <summary> The window. </summary>
- private GameObject window;
- /// <summary> The CPU frame rate text. </summary>
- private TextMesh cpuFrameRateText;
- #if USING_XR_SDK && !UNITY_EDITOR
- /// <summary> The Dropped frame count in last one second. </summary>
- private TextMesh droppedFrameCount;
- private static readonly string droppedFrameCountString = "DroppedFrameCount: {0}";
- #endif
- /// <summary> The GPU frame rate text. </summary>
- private TextMesh gpuFrameRateText;
- /// <summary> The used memory text. </summary>
- private TextMesh usedMemoryText;
- /// <summary> The peak memory text. </summary>
- private TextMesh peakMemoryText;
- /// <summary> The limit memory text. </summary>
- private TextMesh limitMemoryText;
- /// <summary> The used anchor. </summary>
- private Transform usedAnchor;
- /// <summary> The peak anchor. </summary>
- private Transform peakAnchor;
- /// <summary> The window horizontal rotation. </summary>
- private Quaternion windowHorizontalRotation;
- /// <summary> The window horizontal rotation inverse. </summary>
- private Quaternion windowHorizontalRotationInverse;
- /// <summary> The window vertical rotation. </summary>
- private Quaternion windowVerticalRotation;
- /// <summary> The window vertical rotation inverse. </summary>
- private Quaternion windowVerticalRotationInverse;
- /// <summary> The frame information matrices. </summary>
- private Matrix4x4[] frameInfoMatrices;
- /// <summary> List of colors of the frame informations. </summary>
- private Vector4[] frameInfoColors;
- /// <summary> The frame information property block. </summary>
- private MaterialPropertyBlock frameInfoPropertyBlock;
- /// <summary> Identifier for the color. </summary>
- private int colorID;
- /// <summary> Identifier for the parent matrix. </summary>
- private int parentMatrixID;
- /// <summary> Number of frames. </summary>
- private int frameCount;
- /// <summary> The stopwatch. </summary>
- private System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
- /// <summary> The frame timings. </summary>
- private FrameTiming[] frameTimings = new FrameTiming[maxFrameTimings];
- /// <summary> The CPU frame rate strings. </summary>
- private string[] cpuFrameRateStrings;
- /// <summary> The GPU frame rate strings. </summary>
- private string[] gpuFrameRateStrings;
- /// <summary> Buffer for string data. </summary>
- private char[] stringBuffer = new char[maxStringLength];
- /// <summary> The memory usage. </summary>
- private ulong memoryUsage;
- /// <summary> The peak memory usage. </summary>
- private ulong peakMemoryUsage;
- /// <summary> The limit memory usage. </summary>
- private ulong limitMemoryUsage;
- /// <summary> Rendering resources. </summary>
- [SerializeField, HideInInspector]
- private Material defaultMaterial;
- /// <summary> The default instanced material. </summary>
- [SerializeField, HideInInspector]
- private Material defaultInstancedMaterial;
- /// <summary> The background material. </summary>
- private Material backgroundMaterial;
- /// <summary> The foreground material. </summary>
- private Material foregroundMaterial;
- /// <summary> The text material. </summary>
- private Material textMaterial;
- /// <summary> The quad mesh. </summary>
- private Mesh quadMesh;
- private Transform m_CenterCamera;
- private Transform CenterCamera
- {
- get
- {
- if (m_CenterCamera == null)
- {
- if (NRSessionManager.Instance.CenterCameraAnchor != null)
- {
- m_CenterCamera = NRSessionManager.Instance.CenterCameraAnchor;
- }
- else
- {
- m_CenterCamera = Camera.main.transform;
- }
- }
- return m_CenterCamera;
- }
- }
- /// <summary> The instance. </summary>
- private static NRVisualProfiler m_Instance = null;
- /// <summary> Gets the instance. </summary>
- /// <value> The instance. </value>
- public static NRVisualProfiler Instance
- {
- get
- {
- if (m_Instance == null)
- {
- GameObject go = new GameObject("VisualProfiler");
- DontDestroyOnLoad(go);
- m_Instance = go.AddComponent<NRVisualProfiler>();
- }
- return m_Instance;
- }
- }
- /// <summary> Awakes this object. </summary>
- void Awake()
- {
- if (m_Instance != null && m_Instance != this)
- {
- DestroyImmediate(this.gameObject);
- return;
- }
- m_Instance = this;
- }
- /// <summary> Switches. </summary>
- /// <param name="flag"> True to flag.</param>
- public void Switch(bool flag)
- {
- this.gameObject.SetActive(flag);
- }
- /// <summary> Resets this object. </summary>
- private void Reset()
- {
- if (defaultMaterial == null)
- {
- defaultMaterial = new Material(Shader.Find("Nreal/Instanced-Colored"));
- defaultMaterial.SetFloat("_ZWrite", 0.0f);
- defaultMaterial.SetFloat("_ZTest", (float)UnityEngine.Rendering.CompareFunction.Disabled);
- defaultMaterial.renderQueue = 5000;
- }
- if (defaultInstancedMaterial == null)
- {
- Shader defaultInstancedShader = Shader.Find("Nreal/Instanced-Colored");
- if (defaultInstancedShader != null)
- {
- defaultInstancedMaterial = new Material(defaultInstancedShader);
- defaultInstancedMaterial.enableInstancing = true;
- defaultInstancedMaterial.SetFloat("_ZWrite", 0.0f);
- defaultInstancedMaterial.SetFloat("_ZTest", (float)UnityEngine.Rendering.CompareFunction.Disabled);
- defaultInstancedMaterial.renderQueue = 5000;
- }
- else
- {
- Debug.LogWarning("A shader supporting instancing could not be found for the VisualProfiler, falling back to traditional rendering. This may impact performance.");
- }
- }
- if (Application.isPlaying)
- {
- backgroundMaterial = new Material(defaultMaterial);
- foregroundMaterial = new Material(defaultMaterial);
- defaultMaterial.renderQueue = foregroundMaterial.renderQueue - 1;
- backgroundMaterial.renderQueue = defaultMaterial.renderQueue - 1;
- MeshRenderer meshRenderer = new GameObject().AddComponent<TextMesh>().GetComponent<MeshRenderer>();
- textMaterial = new Material(meshRenderer.sharedMaterial);
- textMaterial.renderQueue = defaultMaterial.renderQueue;
- Destroy(meshRenderer.gameObject);
- MeshFilter quadMeshFilter = GameObject.CreatePrimitive(PrimitiveType.Quad).GetComponent<MeshFilter>();
- if (defaultInstancedMaterial != null)
- {
- // Create a quad mesh with artificially large bounds to disable culling for instanced rendering.
- quadMesh = quadMeshFilter.mesh;
- quadMesh.bounds = new Bounds(Vector3.zero, Vector3.one * float.MaxValue);
- }
- else
- {
- quadMesh = quadMeshFilter.sharedMesh;
- }
- Destroy(quadMeshFilter.gameObject);
- }
- stopwatch.Reset();
- stopwatch.Start();
- }
- /// <summary> Starts this object. </summary>
- private void Start()
- {
- Reset();
- BuildWindow();
- BuildFrameRateStrings();
- }
- /// <summary> Executes the 'destroy' action. </summary>
- private void OnDestroy()
- {
- Destroy(window);
- }
- /// <summary> Late update. </summary>
- private void LateUpdate()
- {
- if (window == null)
- {
- return;
- }
- // Update window transformation.
- if (window.activeSelf && CenterCamera != null)
- {
- float t = Time.deltaTime * windowFollowSpeed;
- window.transform.position = Vector3.Lerp(window.transform.position, CalculateWindowPosition(CenterCamera.transform), t);
- window.transform.rotation = Quaternion.Slerp(window.transform.rotation, CalculateWindowRotation(CenterCamera.transform), t);
- window.transform.localScale = defaultWindowScale * windowScale;
- }
- // Capture frame timings every frame and read from it depending on the frameSampleRate.
- FrameTimingManager.CaptureFrameTimings();
- ++frameCount;
- float elapsedSeconds = stopwatch.ElapsedMilliseconds * 0.001f;
- if (elapsedSeconds >= frameSampleRate)
- {
- int cpuFrameRate = (int)(1.0f / (elapsedSeconds / frameCount));
- int gpuFrameRate = 0;
- // Many platforms do not yet support the FrameTimingManager. When timing data is returned from the FrameTimingManager we will use
- // its timing data, else we will depend on the stopwatch.
- uint frameTimingsCount = FrameTimingManager.GetLatestTimings((uint)Mathf.Min(frameCount, maxFrameTimings), frameTimings);
- if (frameTimingsCount != 0)
- {
- float cpuFrameTime, gpuFrameTime;
- AverageFrameTiming(frameTimings, frameTimingsCount, out cpuFrameTime, out gpuFrameTime);
- cpuFrameRate = (int)(1.0f / (cpuFrameTime / frameCount));
- gpuFrameRate = (int)(1.0f / (gpuFrameTime / frameCount));
- }
- // Update frame rate text.
- cpuFrameRateText.text = cpuFrameRateStrings[Mathf.Clamp(cpuFrameRate, 0, maxTargetFrameRate)];
- #if USING_XR_SDK && !UNITY_EDITOR
- int dropped_framecount = 0;
- NRSessionManager.Instance.XRDisplaySubsystem?.TryGetDroppedFrameCount(out dropped_framecount);
- droppedFrameCount.text = string.Format(droppedFrameCountString, dropped_framecount);
- #endif
- if (gpuFrameRate != 0)
- {
- gpuFrameRateText.gameObject.SetActive(true);
- gpuFrameRateText.text = gpuFrameRateStrings[Mathf.Clamp(gpuFrameRate, 0, maxTargetFrameRate)];
- }
- // Update frame colors.
- for (int i = frameRange - 1; i > 0; --i)
- {
- frameInfoColors[i] = frameInfoColors[i - 1];
- }
- // Ideally we would query a device specific API (like the HolographicFramePresentationReport) to detect missed frames.
- // But, many of these APIs are inaccessible in Unity. Currently missed frames are assumed when the average cpuFrameRate
- // is under the target frame rate.
- frameInfoColors[0] = (cpuFrameRate < ((int)(AppFrameRate) - 1)) ? missedFrameRateColor : targetFrameRateColor;
- frameInfoPropertyBlock.SetVectorArray(colorID, frameInfoColors);
- // Reset timers.
- frameCount = 0;
- stopwatch.Reset();
- stopwatch.Start();
- }
- // Draw frame info.
- if (window.activeSelf)
- {
- Matrix4x4 parentLocalToWorldMatrix = window.transform.localToWorldMatrix;
- //if (defaultInstancedMaterial != null)
- //{
- // frameInfoPropertyBlock.SetMatrix(parentMatrixID, parentLocalToWorldMatrix);
- // Graphics.DrawMeshInstanced(quadMesh, 0, defaultInstancedMaterial, frameInfoMatrices, frameInfoMatrices.Length, frameInfoPropertyBlock, UnityEngine.Rendering.ShadowCastingMode.Off, false);
- //}
- //else
- //{
- // If a instanced material is not available, fall back to non-instanced rendering.
- for (int i = 0; i < frameInfoMatrices.Length; ++i)
- {
- frameInfoPropertyBlock.SetColor(colorID, frameInfoColors[i]);
- Graphics.DrawMesh(quadMesh, parentLocalToWorldMatrix * frameInfoMatrices[i], defaultMaterial, 0, null, 0, frameInfoPropertyBlock, false, false, false);
- }
- //}
- }
- // Update memory statistics.
- ulong limit = AppMemoryUsageLimit;
- if (limit != limitMemoryUsage)
- {
- if (window.activeSelf && WillDisplayedMemoryUsageDiffer(limitMemoryUsage, limit, displayedDecimalDigits))
- {
- MemoryUsageToString(stringBuffer, displayedDecimalDigits, limitMemoryText, limitMemoryString, limit);
- }
- limitMemoryUsage = limit;
- }
- ulong usage = AppMemoryUsage;
- if (usage != memoryUsage)
- {
- usedAnchor.localScale = new Vector3((float)usage / limitMemoryUsage, usedAnchor.localScale.y, usedAnchor.localScale.z);
- if (window.activeSelf && WillDisplayedMemoryUsageDiffer(memoryUsage, usage, displayedDecimalDigits))
- {
- MemoryUsageToString(stringBuffer, displayedDecimalDigits, usedMemoryText, usedMemoryString, usage);
- }
- memoryUsage = usage;
- }
- if (memoryUsage > peakMemoryUsage)
- {
- peakAnchor.localScale = new Vector3((float)memoryUsage / limitMemoryUsage, peakAnchor.localScale.y, peakAnchor.localScale.z);
- if (window.activeSelf && WillDisplayedMemoryUsageDiffer(peakMemoryUsage, memoryUsage, displayedDecimalDigits))
- {
- MemoryUsageToString(stringBuffer, displayedDecimalDigits, peakMemoryText, peakMemoryString, memoryUsage);
- }
- peakMemoryUsage = memoryUsage;
- }
- window.SetActive(isVisible);
- }
- /// <summary> Calculates the window position. </summary>
- /// <param name="cameraTransform"> The camera transform.</param>
- /// <returns> The calculated window position. </returns>
- private Vector3 CalculateWindowPosition(Transform cameraTransform)
- {
- float windowDistance = Mathf.Max(16.0f / Camera.main.fieldOfView, Camera.main.nearClipPlane + 0.25f);
- Vector3 position = cameraTransform.position + (cameraTransform.forward * windowDistance);
- Vector3 horizontalOffset = cameraTransform.right * windowOffset.x;
- Vector3 verticalOffset = cameraTransform.up * windowOffset.y;
- switch (windowAnchor)
- {
- case TextAnchor.UpperLeft: position += verticalOffset - horizontalOffset; break;
- case TextAnchor.UpperCenter: position += verticalOffset; break;
- case TextAnchor.UpperRight: position += verticalOffset + horizontalOffset; break;
- case TextAnchor.MiddleLeft: position -= horizontalOffset; break;
- case TextAnchor.MiddleRight: position += horizontalOffset; break;
- case TextAnchor.LowerLeft: position -= verticalOffset + horizontalOffset; break;
- case TextAnchor.LowerCenter: position -= verticalOffset; break;
- case TextAnchor.LowerRight: position -= verticalOffset - horizontalOffset; break;
- }
- return position;
- }
- /// <summary> Calculates the window rotation. </summary>
- /// <param name="cameraTransform"> The camera transform.</param>
- /// <returns> The calculated window rotation. </returns>
- private Quaternion CalculateWindowRotation(Transform cameraTransform)
- {
- Quaternion rotation = cameraTransform.rotation;
- switch (windowAnchor)
- {
- case TextAnchor.UpperLeft: rotation *= windowHorizontalRotationInverse * windowVerticalRotationInverse; break;
- case TextAnchor.UpperCenter: rotation *= windowHorizontalRotationInverse; break;
- case TextAnchor.UpperRight: rotation *= windowHorizontalRotationInverse * windowVerticalRotation; break;
- case TextAnchor.MiddleLeft: rotation *= windowVerticalRotationInverse; break;
- case TextAnchor.MiddleRight: rotation *= windowVerticalRotation; break;
- case TextAnchor.LowerLeft: rotation *= windowHorizontalRotation * windowVerticalRotationInverse; break;
- case TextAnchor.LowerCenter: rotation *= windowHorizontalRotation; break;
- case TextAnchor.LowerRight: rotation *= windowHorizontalRotation * windowVerticalRotation; break;
- }
- return rotation;
- }
- /// <summary> Builds the window. </summary>
- private void BuildWindow()
- {
- // Initialize property block state.
- colorID = Shader.PropertyToID("_Color");
- parentMatrixID = Shader.PropertyToID("_ParentLocalToWorldMatrix");
- WindowParent = transform;
- // Build the window root.
- {
- window = CreateQuad("VisualProfiler", null);
- window.transform.parent = WindowParent;
- InitializeRenderer(window, backgroundMaterial, colorID, baseColor);
- window.transform.localScale = defaultWindowScale;
- windowHorizontalRotation = Quaternion.AngleAxis(defaultWindowRotation.y, Vector3.right);
- windowHorizontalRotationInverse = Quaternion.Inverse(windowHorizontalRotation);
- windowVerticalRotation = Quaternion.AngleAxis(defaultWindowRotation.x, Vector3.up);
- windowVerticalRotationInverse = Quaternion.Inverse(windowVerticalRotation);
- }
- // Add frame rate text and frame indicators.
- {
- cpuFrameRateText = CreateText("CPUFrameRateText", new Vector3(-0.495f, 0.5f, 0.0f), window.transform, TextAnchor.UpperLeft, textMaterial, Color.white, string.Empty);
- #if USING_XR_SDK && !UNITY_EDITOR
- droppedFrameCount = CreateText("DroppedFrameCount", new Vector3(0, 0.5f, 0.0f), window.transform, TextAnchor.UpperLeft, textMaterial, Color.white, string.Empty);
- #endif
- gpuFrameRateText = CreateText("GPUFrameRateText", new Vector3(0.495f, 0.5f, 0.0f), window.transform, TextAnchor.UpperRight, textMaterial, Color.white, string.Empty);
- gpuFrameRateText.gameObject.SetActive(false);
- frameInfoMatrices = new Matrix4x4[frameRange];
- frameInfoColors = new Vector4[frameRange];
- Vector3 scale = new Vector3(1.0f / frameRange, 0.2f, 1.0f);
- Vector3 position = new Vector3(0.5f - (scale.x * 0.5f), 0.15f, 0.0f);
- for (int i = 0; i < frameRange; ++i)
- {
- frameInfoMatrices[i] = Matrix4x4.TRS(position, Quaternion.identity, new Vector3(scale.x * 0.8f, scale.y, scale.z));
- position.x -= scale.x;
- frameInfoColors[i] = targetFrameRateColor;
- }
- frameInfoPropertyBlock = new MaterialPropertyBlock();
- frameInfoPropertyBlock.SetVectorArray(colorID, frameInfoColors);
- }
- // Add memory usage text and bars.
- {
- usedMemoryText = CreateText("UsedMemoryText", new Vector3(-0.495f, 0.0f, 0.0f), window.transform, TextAnchor.UpperLeft, textMaterial, memoryUsedColor, usedMemoryString);
- peakMemoryText = CreateText("PeakMemoryText", new Vector3(0.0f, 0.0f, 0.0f), window.transform, TextAnchor.UpperCenter, textMaterial, memoryPeakColor, peakMemoryString);
- limitMemoryText = CreateText("LimitMemoryText", new Vector3(0.495f, 0.0f, 0.0f), window.transform, TextAnchor.UpperRight, textMaterial, Color.white, limitMemoryString);
- GameObject limitBar = CreateQuad("LimitBar", window.transform);
- InitializeRenderer(limitBar, defaultMaterial, colorID, memoryLimitColor);
- limitBar.transform.localScale = new Vector3(0.99f, 0.2f, 1.0f);
- limitBar.transform.localPosition = new Vector3(0.0f, -0.37f, 0.0f);
- {
- usedAnchor = CreateAnchor("UsedAnchor", limitBar.transform);
- GameObject bar = CreateQuad("UsedBar", usedAnchor);
- Material material = new Material(foregroundMaterial);
- material.renderQueue = material.renderQueue + 1;
- InitializeRenderer(bar, material, colorID, memoryUsedColor);
- bar.transform.localScale = Vector3.one;
- bar.transform.localPosition = new Vector3(0.5f, 0.0f, 0.0f);
- }
- {
- peakAnchor = CreateAnchor("PeakAnchor", limitBar.transform);
- GameObject bar = CreateQuad("PeakBar", peakAnchor);
- InitializeRenderer(bar, foregroundMaterial, colorID, memoryPeakColor);
- bar.transform.localScale = Vector3.one;
- bar.transform.localPosition = new Vector3(0.5f, 0.0f, 0.0f);
- }
- }
- window.SetActive(isVisible);
- }
- /// <summary> Builds frame rate strings. </summary>
- private void BuildFrameRateStrings()
- {
- cpuFrameRateStrings = new string[maxTargetFrameRate + 1];
- gpuFrameRateStrings = new string[maxTargetFrameRate + 1];
- string displayedDecimalFormat = string.Format("{{0:F{0}}}", displayedDecimalDigits);
- StringBuilder stringBuilder = new StringBuilder(32);
- StringBuilder milisecondStringBuilder = new StringBuilder(16);
- for (int i = 0; i < cpuFrameRateStrings.Length; ++i)
- {
- float miliseconds = (i == 0) ? 0.0f : (1.0f / i) * 1000.0f;
- milisecondStringBuilder.AppendFormat(displayedDecimalFormat, miliseconds);
- stringBuilder.AppendFormat("CPU: {0} fps ({1} ms)", i.ToString(), milisecondStringBuilder.ToString());
- cpuFrameRateStrings[i] = stringBuilder.ToString();
- stringBuilder.Length = 0;
- stringBuilder.AppendFormat("GPU: {0} fps ({1} ms)", i.ToString(), milisecondStringBuilder.ToString());
- gpuFrameRateStrings[i] = stringBuilder.ToString();
- milisecondStringBuilder.Length = 0;
- stringBuilder.Length = 0;
- }
- }
- /// <summary> Creates an anchor. </summary>
- /// <param name="name"> The name.</param>
- /// <param name="parent"> The parent.</param>
- /// <returns> The new anchor. </returns>
- private static Transform CreateAnchor(string name, Transform parent)
- {
- Transform anchor = new GameObject(name).transform;
- anchor.parent = parent;
- anchor.localScale = Vector3.one;
- anchor.localPosition = new Vector3(-0.5f, 0.0f, 0.0f);
- return anchor;
- }
- /// <summary> Creates a quad. </summary>
- /// <param name="name"> The name.</param>
- /// <param name="parent"> The parent.</param>
- /// <returns> The new quad. </returns>
- private static GameObject CreateQuad(string name, Transform parent)
- {
- GameObject quad = GameObject.CreatePrimitive(PrimitiveType.Quad);
- Destroy(quad.GetComponent<Collider>());
- quad.name = name;
- quad.transform.parent = parent;
- return quad;
- }
- /// <summary> Creates a text. </summary>
- /// <param name="name"> The name.</param>
- /// <param name="position"> The position.</param>
- /// <param name="parent"> The parent.</param>
- /// <param name="anchor"> The anchor.</param>
- /// <param name="material"> The material.</param>
- /// <param name="color"> The color.</param>
- /// <param name="text"> The text.</param>
- /// <returns> The new text. </returns>
- private static TextMesh CreateText(string name, Vector3 position, Transform parent, TextAnchor anchor, Material material, Color color, string text)
- {
- GameObject obj = new GameObject(name);
- obj.transform.localScale = Vector3.one * 0.0016f;
- obj.transform.parent = parent;
- obj.transform.localPosition = position;
- TextMesh textMesh = obj.AddComponent<TextMesh>();
- textMesh.fontSize = 48;
- textMesh.anchor = anchor;
- textMesh.color = color;
- textMesh.text = text;
- textMesh.richText = false;
- Renderer renderer = obj.GetComponent<Renderer>();
- renderer.sharedMaterial = material;
- OptimizeRenderer(renderer);
- return textMesh;
- }
- /// <summary> Initializes the renderer. </summary>
- /// <param name="obj"> The object.</param>
- /// <param name="material"> The material.</param>
- /// <param name="colorID"> Identifier for the color.</param>
- /// <param name="color"> The color.</param>
- /// <returns> A Renderer. </returns>
- private static Renderer InitializeRenderer(GameObject obj, Material material, int colorID, Color color)
- {
- Renderer renderer = obj.GetComponent<Renderer>();
- renderer.sharedMaterial = material;
- MaterialPropertyBlock propertyBlock = new MaterialPropertyBlock();
- renderer.GetPropertyBlock(propertyBlock);
- propertyBlock.SetColor(colorID, color);
- renderer.SetPropertyBlock(propertyBlock);
- OptimizeRenderer(renderer);
- return renderer;
- }
- /// <summary> Optimize renderer. </summary>
- /// <param name="renderer"> The renderer.</param>
- private static void OptimizeRenderer(Renderer renderer)
- {
- renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
- renderer.receiveShadows = false;
- renderer.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
- renderer.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off;
- renderer.reflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Off;
- renderer.allowOcclusionWhenDynamic = false;
- }
- /// <summary> Memory usage to string. </summary>
- /// <param name="stringBuffer"> Buffer for string data.</param>
- /// <param name="displayedDecimalDigits"> The displayed decimal digits.</param>
- /// <param name="textMesh"> The text mesh.</param>
- /// <param name="prefixString"> The prefix string.</param>
- /// <param name="memoryUsage"> The memory usage.</param>
- private static void MemoryUsageToString(char[] stringBuffer, int displayedDecimalDigits, TextMesh textMesh, string prefixString, ulong memoryUsage)
- {
- // Using a custom number to string method to avoid the overhead, and allocations, of built in string.Format/StringBuilder methods.
- // We can also make some assumptions since the domain of the input number (memoryUsage) is known.
- float memoryUsageMB = ConvertBytesToMegabytes(memoryUsage);
- int memoryUsageIntegerDigits = (int)memoryUsageMB;
- int memoryUsageFractionalDigits = (int)((memoryUsageMB - memoryUsageIntegerDigits) * Mathf.Pow(10.0f, displayedDecimalDigits));
- int bufferIndex = 0;
- for (int i = 0; i < prefixString.Length; ++i)
- {
- stringBuffer[bufferIndex++] = prefixString[i];
- }
- bufferIndex = MemoryItoA(memoryUsageIntegerDigits, stringBuffer, bufferIndex);
- stringBuffer[bufferIndex++] = '.';
- if (memoryUsageFractionalDigits != 0)
- {
- bufferIndex = MemoryItoA(memoryUsageFractionalDigits, stringBuffer, bufferIndex);
- }
- else
- {
- for (int i = 0; i < displayedDecimalDigits; ++i)
- {
- stringBuffer[bufferIndex++] = '0';
- }
- }
- stringBuffer[bufferIndex++] = 'M';
- stringBuffer[bufferIndex++] = 'B';
- textMesh.text = new string(stringBuffer, 0, bufferIndex);
- }
- /// <summary> Memory ito a. </summary>
- /// <param name="value"> The value.</param>
- /// <param name="stringBuffer"> Buffer for string data.</param>
- /// <param name="bufferIndex"> Zero-based index of the buffer.</param>
- /// <returns> An int. </returns>
- private static int MemoryItoA(int value, char[] stringBuffer, int bufferIndex)
- {
- int startIndex = bufferIndex;
- for (; value != 0; value /= 10)
- {
- stringBuffer[bufferIndex++] = (char)((char)(value % 10) + '0');
- }
- char temp;
- for (int endIndex = bufferIndex - 1; startIndex < endIndex; ++startIndex, --endIndex)
- {
- temp = stringBuffer[startIndex];
- stringBuffer[startIndex] = stringBuffer[endIndex];
- stringBuffer[endIndex] = temp;
- }
- return bufferIndex;
- }
- /// <summary> Gets the application frame rate. </summary>
- /// <value> The application frame rate. </value>
- private static float AppFrameRate
- {
- get
- {
- // If the current XR SDK does not report refresh rate information, assume 60Hz.
- //float refreshRate = UnityEngine.XR.XRDevice.refreshRate;
- //return ((int)refreshRate == 0) ? 60.0f : refreshRate;
- return 45f;
- }
- }
- /// <summary> Average frame timing. </summary>
- /// <param name="frameTimings"> The frame timings.</param>
- /// <param name="frameTimingsCount"> Number of frame timings.</param>
- /// <param name="cpuFrameTime"> [out] The CPU frame time.</param>
- /// <param name="gpuFrameTime"> [out] The GPU frame time.</param>
- private static void AverageFrameTiming(FrameTiming[] frameTimings, uint frameTimingsCount, out float cpuFrameTime, out float gpuFrameTime)
- {
- double cpuTime = 0.0f;
- double gpuTime = 0.0f;
- for (int i = 0; i < frameTimingsCount; ++i)
- {
- cpuTime += frameTimings[i].cpuFrameTime;
- gpuTime += frameTimings[i].gpuFrameTime;
- }
- cpuTime /= frameTimingsCount;
- gpuTime /= frameTimingsCount;
- cpuFrameTime = (float)(cpuTime * 0.001);
- gpuFrameTime = (float)(gpuTime * 0.001);
- }
- /// <summary> Gets the application memory usage. </summary>
- /// <value> The application memory usage. </value>
- private static ulong AppMemoryUsage
- {
- get
- {
- #if WINDOWS_UWP
- return MemoryManager.AppMemoryUsage;
- #else
- return (ulong)Profiler.GetTotalAllocatedMemoryLong();
- #endif
- }
- }
- /// <summary> Gets the application memory usage limit. </summary>
- /// <value> The application memory usage limit. </value>
- private static ulong AppMemoryUsageLimit
- {
- get
- {
- #if WINDOWS_UWP
- return MemoryManager.AppMemoryUsageLimit;
- #else
- return ConvertMegabytesToBytes(SystemInfo.systemMemorySize);
- #endif
- }
- }
- /// <summary> Will displayed memory usage differ. </summary>
- /// <param name="oldUsage"> The old usage.</param>
- /// <param name="newUsage"> The new usage.</param>
- /// <param name="displayedDecimalDigits"> The displayed decimal digits.</param>
- /// <returns> True if it succeeds, false if it fails. </returns>
- private static bool WillDisplayedMemoryUsageDiffer(ulong oldUsage, ulong newUsage, int displayedDecimalDigits)
- {
- float oldUsageMBs = ConvertBytesToMegabytes(oldUsage);
- float newUsageMBs = ConvertBytesToMegabytes(newUsage);
- float decimalPower = Mathf.Pow(10.0f, displayedDecimalDigits);
- return (int)(oldUsageMBs * decimalPower) != (int)(newUsageMBs * decimalPower);
- }
- /// <summary> Convert megabytes to bytes. </summary>
- /// <param name="megabytes"> The megabytes.</param>
- /// <returns> The megabytes converted to bytes. </returns>
- private static ulong ConvertMegabytesToBytes(int megabytes)
- {
- return ((ulong)megabytes * 1024UL) * 1024UL;
- }
- /// <summary> Convert bytes to megabytes. </summary>
- /// <param name="bytes"> The bytes.</param>
- /// <returns> The bytes converted to megabytes. </returns>
- private static float ConvertBytesToMegabytes(ulong bytes)
- {
- return (bytes / 1024.0f) / 1024.0f;
- }
- }
- }
|