/****************************************************************************
* 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
///
/// 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.
public class NRVisualProfiler : MonoBehaviour
{
/// The maximum length of the string.
private static readonly int maxStringLength = 32;
/// The maximum target frame rate.
private static readonly int maxTargetFrameRate = 120;
/// The maximum frame timings.
private static readonly int maxFrameTimings = 128;
/// The frame range.
private static readonly int frameRange = 30;
/// The default window rotation.
private static readonly Vector2 defaultWindowRotation = new Vector2(10.0f, 20.0f);
/// The default window scale.
private static readonly Vector3 defaultWindowScale = new Vector3(0.2f, 0.04f, 1.0f);
/// The used memory string.
private static readonly string usedMemoryString = "Used: ";
/// The peak memory string.
private static readonly string peakMemoryString = "Peak: ";
/// The limit memory string.
private static readonly string limitMemoryString = "Limit: ";
/// Gets or sets the window parent.
/// The window parent.
public Transform WindowParent { get; set; } = null;
/// True if is visible, false if not.
[Header("Profiler Settings")]
[SerializeField, Tooltip("Is the profiler currently visible.")]
private bool isVisible = true;
/// Gets or sets a value indicating whether this object is visible.
/// True if this object is visible, false if not.
public bool IsVisible
{
get { return isVisible; }
set { isVisible = value; }
}
/// The frame sample rate.
[SerializeField, Tooltip("The amount of time, in seconds, to collect frames for frame rate calculation.")]
private float frameSampleRate = 0.1f;
/// Gets or sets the frame sample rate.
/// The frame sample rate.
public float FrameSampleRate
{
get { return frameSampleRate; }
set { frameSampleRate = value; }
}
/// The window anchor.
[Header("Window Settings")]
[SerializeField, Tooltip("What part of the view port to anchor the window to.")]
private TextAnchor windowAnchor = TextAnchor.LowerCenter;
/// Gets or sets the window anchor.
/// The window anchor.
public TextAnchor WindowAnchor
{
get { return windowAnchor; }
set { windowAnchor = value; }
}
/// The window offset.
[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);
/// Gets or sets the window offset.
/// The window offset.
public Vector2 WindowOffset
{
get { return windowOffset; }
set { windowOffset = value; }
}
/// The window scale.
[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;
/// Gets or sets the window scale.
/// The window scale.
public float WindowScale
{
get { return windowScale; }
set { windowScale = Mathf.Clamp(value, 0.5f, 5.0f); }
}
/// The window follow speed.
[SerializeField, Range(0.0f, 100.0f), Tooltip("How quickly to interpolate the window towards its target position and rotation.")]
private float windowFollowSpeed = 5.0f;
/// Gets or sets the window follow speed.
/// The window follow speed.
public float WindowFollowSpeed
{
get { return windowFollowSpeed; }
set { windowFollowSpeed = Mathf.Abs(value); }
}
/// The toggle keyworlds.
[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" };
/// The displayed decimal digits.
[SerializeField, Range(0, 3), Tooltip("How many decimal places to display on numeric strings.")]
private int displayedDecimalDigits = 1;
/// The base color.
[SerializeField, Tooltip("The color of the window backplate.")]
private Color baseColor = new Color(80 / 256.0f, 80 / 256.0f, 80 / 256.0f, 1.0f);
/// The target frame rate color.
[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);
/// The missed frame rate color.
[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);
/// The memory used color.
[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);
/// The memory peak color.
[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);
/// The memory limit color.
[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);
/// The window.
private GameObject window;
/// The CPU frame rate text.
private TextMesh cpuFrameRateText;
#if USING_XR_SDK && !UNITY_EDITOR
/// The Dropped frame count in last one second.
private TextMesh droppedFrameCount;
private static readonly string droppedFrameCountString = "DroppedFrameCount: {0}";
#endif
/// The GPU frame rate text.
private TextMesh gpuFrameRateText;
/// The used memory text.
private TextMesh usedMemoryText;
/// The peak memory text.
private TextMesh peakMemoryText;
/// The limit memory text.
private TextMesh limitMemoryText;
/// The used anchor.
private Transform usedAnchor;
/// The peak anchor.
private Transform peakAnchor;
/// The window horizontal rotation.
private Quaternion windowHorizontalRotation;
/// The window horizontal rotation inverse.
private Quaternion windowHorizontalRotationInverse;
/// The window vertical rotation.
private Quaternion windowVerticalRotation;
/// The window vertical rotation inverse.
private Quaternion windowVerticalRotationInverse;
/// The frame information matrices.
private Matrix4x4[] frameInfoMatrices;
/// List of colors of the frame informations.
private Vector4[] frameInfoColors;
/// The frame information property block.
private MaterialPropertyBlock frameInfoPropertyBlock;
/// Identifier for the color.
private int colorID;
/// Identifier for the parent matrix.
private int parentMatrixID;
/// Number of frames.
private int frameCount;
/// The stopwatch.
private System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
/// The frame timings.
private FrameTiming[] frameTimings = new FrameTiming[maxFrameTimings];
/// The CPU frame rate strings.
private string[] cpuFrameRateStrings;
/// The GPU frame rate strings.
private string[] gpuFrameRateStrings;
/// Buffer for string data.
private char[] stringBuffer = new char[maxStringLength];
/// The memory usage.
private ulong memoryUsage;
/// The peak memory usage.
private ulong peakMemoryUsage;
/// The limit memory usage.
private ulong limitMemoryUsage;
/// Rendering resources.
[SerializeField, HideInInspector]
private Material defaultMaterial;
/// The default instanced material.
[SerializeField, HideInInspector]
private Material defaultInstancedMaterial;
/// The background material.
private Material backgroundMaterial;
/// The foreground material.
private Material foregroundMaterial;
/// The text material.
private Material textMaterial;
/// The quad mesh.
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;
}
}
/// The instance.
private static NRVisualProfiler m_Instance = null;
/// Gets the instance.
/// The instance.
public static NRVisualProfiler Instance
{
get
{
if (m_Instance == null)
{
GameObject go = new GameObject("VisualProfiler");
DontDestroyOnLoad(go);
m_Instance = go.AddComponent();
}
return m_Instance;
}
}
/// Awakes this object.
void Awake()
{
if (m_Instance != null && m_Instance != this)
{
DestroyImmediate(this.gameObject);
return;
}
m_Instance = this;
}
/// Switches.
/// True to flag.
public void Switch(bool flag)
{
this.gameObject.SetActive(flag);
}
/// Resets this object.
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().GetComponent();
textMaterial = new Material(meshRenderer.sharedMaterial);
textMaterial.renderQueue = defaultMaterial.renderQueue;
Destroy(meshRenderer.gameObject);
MeshFilter quadMeshFilter = GameObject.CreatePrimitive(PrimitiveType.Quad).GetComponent();
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();
}
/// Starts this object.
private void Start()
{
Reset();
BuildWindow();
BuildFrameRateStrings();
}
/// Executes the 'destroy' action.
private void OnDestroy()
{
Destroy(window);
}
/// Late update.
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);
}
/// Calculates the window position.
/// The camera transform.
/// The calculated window position.
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;
}
/// Calculates the window rotation.
/// The camera transform.
/// The calculated window rotation.
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;
}
/// Builds the window.
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);
}
/// Builds frame rate strings.
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;
}
}
/// Creates an anchor.
/// The name.
/// The parent.
/// The new anchor.
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;
}
/// Creates a quad.
/// The name.
/// The parent.
/// The new quad.
private static GameObject CreateQuad(string name, Transform parent)
{
GameObject quad = GameObject.CreatePrimitive(PrimitiveType.Quad);
Destroy(quad.GetComponent());
quad.name = name;
quad.transform.parent = parent;
return quad;
}
/// Creates a text.
/// The name.
/// The position.
/// The parent.
/// The anchor.
/// The material.
/// The color.
/// The text.
/// The new text.
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.fontSize = 48;
textMesh.anchor = anchor;
textMesh.color = color;
textMesh.text = text;
textMesh.richText = false;
Renderer renderer = obj.GetComponent();
renderer.sharedMaterial = material;
OptimizeRenderer(renderer);
return textMesh;
}
/// Initializes the renderer.
/// The object.
/// The material.
/// Identifier for the color.
/// The color.
/// A Renderer.
private static Renderer InitializeRenderer(GameObject obj, Material material, int colorID, Color color)
{
Renderer renderer = obj.GetComponent();
renderer.sharedMaterial = material;
MaterialPropertyBlock propertyBlock = new MaterialPropertyBlock();
renderer.GetPropertyBlock(propertyBlock);
propertyBlock.SetColor(colorID, color);
renderer.SetPropertyBlock(propertyBlock);
OptimizeRenderer(renderer);
return renderer;
}
/// Optimize renderer.
/// The renderer.
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;
}
/// Memory usage to string.
/// Buffer for string data.
/// The displayed decimal digits.
/// The text mesh.
/// The prefix string.
/// The memory usage.
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);
}
/// Memory ito a.
/// The value.
/// Buffer for string data.
/// Zero-based index of the buffer.
/// An int.
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;
}
/// Gets the application frame rate.
/// The application frame rate.
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;
}
}
/// Average frame timing.
/// The frame timings.
/// Number of frame timings.
/// [out] The CPU frame time.
/// [out] The GPU frame time.
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);
}
/// Gets the application memory usage.
/// The application memory usage.
private static ulong AppMemoryUsage
{
get
{
#if WINDOWS_UWP
return MemoryManager.AppMemoryUsage;
#else
return (ulong)Profiler.GetTotalAllocatedMemoryLong();
#endif
}
}
/// Gets the application memory usage limit.
/// The application memory usage limit.
private static ulong AppMemoryUsageLimit
{
get
{
#if WINDOWS_UWP
return MemoryManager.AppMemoryUsageLimit;
#else
return ConvertMegabytesToBytes(SystemInfo.systemMemorySize);
#endif
}
}
/// Will displayed memory usage differ.
/// The old usage.
/// The new usage.
/// The displayed decimal digits.
/// True if it succeeds, false if it fails.
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);
}
/// Convert megabytes to bytes.
/// The megabytes.
/// The megabytes converted to bytes.
private static ulong ConvertMegabytesToBytes(int megabytes)
{
return ((ulong)megabytes * 1024UL) * 1024UL;
}
/// Convert bytes to megabytes.
/// The bytes.
/// The bytes converted to megabytes.
private static float ConvertBytesToMegabytes(ulong bytes)
{
return (bytes / 1024.0f) / 1024.0f;
}
}
}