551 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
	
			
		
		
	
	
			551 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
	
| //#define LOGGING
 | |
| 
 | |
| #if UNITY_4_6 || UNITY_4_7 || UNITY_5_0 || UNITY_5_1
 | |
| #define LEGACY_UI
 | |
| #endif
 | |
| 
 | |
| namespace SRDebugger.UI.Controls
 | |
| {
 | |
|     using System.Collections.Generic;
 | |
|     using Services;
 | |
|     using SRF;
 | |
|     using SRF.Service;
 | |
|     using UnityEngine;
 | |
|     using UnityEngine.UI;
 | |
| 
 | |
|     [ExecuteInEditMode]
 | |
|     [RequireComponent(typeof (RectTransform))]
 | |
|     [RequireComponent(typeof (CanvasRenderer))]
 | |
|     public class ProfilerGraphControl : Graphic
 | |
|     {
 | |
|         public enum VerticalAlignments
 | |
|         {
 | |
|             Top,
 | |
|             Bottom
 | |
|         }
 | |
| 
 | |
|         public VerticalAlignments VerticalAlignment = VerticalAlignments.Bottom;
 | |
| 
 | |
|         private static readonly float[] ScaleSteps =
 | |
|         {
 | |
|             1f/200f,
 | |
|             1f/160f,
 | |
|             1f/120f,
 | |
|             1f/100f,
 | |
|             1f/60f,
 | |
|             1f/30f,
 | |
|             1f/20f,
 | |
|             1f/12f,
 | |
|             1f/6f
 | |
|         };
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Resize the y-axis to fit the nearest useful fps value
 | |
|         /// </summary>
 | |
|         public bool FloatingScale;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// If not using FloatingScale, use the target FPS set by Application.targetFrameRate for TargetFps
 | |
|         /// </summary>
 | |
|         public bool TargetFpsUseApplication;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Toggle drawing of the various axes
 | |
|         /// </summary>
 | |
|         public bool DrawAxes = true;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// If FloatingScale is disabled, use this value to determine y-axis
 | |
|         /// </summary>
 | |
|         public int TargetFps = 60;
 | |
| 
 | |
|         public bool Clip = true;
 | |
| 
 | |
|         public const float DataPointMargin = 2f;
 | |
|         public const float DataPointVerticalMargin = 2f;
 | |
| 
 | |
|         public const float DataPointWidth = 4f;
 | |
| 
 | |
|         public int VerticalPadding = 10;
 | |
| 
 | |
|         public const int LineCount = 3;
 | |
| 
 | |
|         public Color[] LineColours = new Color[0];
 | |
| 
 | |
|         private IProfilerService _profilerService;
 | |
| 
 | |
|         private ProfilerGraphAxisLabel[] _axisLabels;
 | |
| 
 | |
|         private Rect _clipBounds;
 | |
| 
 | |
| #if LEGACY_UI
 | |
|         private List<UIVertex> _vbo;
 | |
| #else
 | |
|         private readonly List<Vector3> _meshVertices = new List<Vector3>();
 | |
|         private readonly List<Color32> _meshVertexColors = new List<Color32>();
 | |
|         private readonly List<int> _meshTriangles = new List<int>();
 | |
| #endif
 | |
| 
 | |
|         protected override void Awake()
 | |
|         {
 | |
|             base.Awake();
 | |
|             _profilerService = SRServiceManager.GetService<IProfilerService>();
 | |
|         }
 | |
| 
 | |
|         protected override void Start()
 | |
|         {
 | |
|             base.Start();
 | |
|         }
 | |
| 
 | |
|         protected void Update()
 | |
|         {
 | |
|             SetVerticesDirty();
 | |
|         }
 | |
| 
 | |
| #if LEGACY_UI
 | |
|         protected override void OnFillVBO(List<UIVertex> vbo)
 | |
| #else
 | |
|         [System.ObsoleteAttribute]
 | |
|         protected override void OnPopulateMesh(Mesh m)
 | |
| #endif
 | |
|         {
 | |
| #if LEGACY_UI
 | |
|             _vbo = vbo;
 | |
| #else
 | |
|             _meshVertices.Clear();
 | |
|             _meshVertexColors.Clear();
 | |
|             _meshTriangles.Clear();
 | |
| #endif
 | |
| 
 | |
| #if LOGGING
 | |
| 			if(!Application.isPlaying)
 | |
| 				Debug.Log("Draw");
 | |
| #endif
 | |
| 
 | |
|             var graphWidth = rectTransform.rect.width;
 | |
|             var graphHeight = rectTransform.rect.height;
 | |
| 
 | |
|             _clipBounds = new Rect(0, 0, graphWidth, graphHeight);
 | |
| 
 | |
|             var targetFps = TargetFps;
 | |
| 
 | |
|             if (Application.isPlaying && TargetFpsUseApplication && Application.targetFrameRate > 0)
 | |
|             {
 | |
|                 targetFps = Application.targetFrameRate;
 | |
|             }
 | |
| 
 | |
|             var maxValue = 1f/targetFps;
 | |
| 
 | |
|             // Holds the index of the nearest 'useful' FPS step
 | |
|             var fpsStep = -1;
 | |
| 
 | |
|             var maxFrameTime = FloatingScale ? CalculateMaxFrameTime() : 1f/targetFps;
 | |
| 
 | |
|             if (FloatingScale)
 | |
|             {
 | |
|                 for (var i = 0; i < ScaleSteps.Length; i++)
 | |
|                 {
 | |
|                     var step = ScaleSteps[i];
 | |
| 
 | |
|                     if (maxFrameTime < step*1.1f)
 | |
|                     {
 | |
|                         maxValue = step;
 | |
|                         fpsStep = i;
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 // Fall back on the largest one
 | |
|                 if (fpsStep < 0)
 | |
|                 {
 | |
|                     fpsStep = ScaleSteps.Length - 1;
 | |
|                     maxValue = ScaleSteps[fpsStep];
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 // Search for the next scale step after the user-provided step
 | |
|                 for (var i = 0; i < ScaleSteps.Length; i++)
 | |
|                 {
 | |
|                     var step = ScaleSteps[i];
 | |
| 
 | |
|                     if (maxFrameTime > step)
 | |
|                     {
 | |
|                         fpsStep = i;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             var verticalScale = (graphHeight - (VerticalPadding*2))/maxValue;
 | |
| 
 | |
|             // Number of data points that can fit into the graph space
 | |
|             var availableDataPoints = CalculateVisibleDataPointCount();
 | |
| 
 | |
|             // Reallocate vertex array if insufficient length (or not yet created)
 | |
|             var sampleCount = GetFrameBufferCurrentSize();
 | |
| 
 | |
|             for (var i = 0; i < sampleCount; i++)
 | |
|             {
 | |
|                 // Break loop if all visible data points have been drawn
 | |
|                 if (i >= availableDataPoints)
 | |
|                 {
 | |
|                     break;
 | |
|                 }
 | |
| 
 | |
|                 // When using right-alignment, read from the end of the profiler buffer
 | |
|                 var frame = GetFrame(sampleCount - i - 1);
 | |
| 
 | |
|                 // Left-hand x coord
 | |
|                 var lx = graphWidth - DataPointWidth*i - DataPointWidth - graphWidth/2f;
 | |
| 
 | |
|                 DrawDataPoint(lx, verticalScale, frame);
 | |
|             }
 | |
| 
 | |
|             if (DrawAxes)
 | |
|             {
 | |
|                 if (!FloatingScale)
 | |
|                 {
 | |
|                     DrawAxis(maxValue, maxValue*verticalScale, GetAxisLabel(0));
 | |
|                 }
 | |
| 
 | |
|                 var axisCount = 2;
 | |
|                 var j = 0;
 | |
| 
 | |
|                 if (!FloatingScale)
 | |
|                 {
 | |
|                     j++;
 | |
|                 }
 | |
| 
 | |
|                 for (var i = fpsStep; i >= 0; --i)
 | |
|                 {
 | |
|                     if (j >= axisCount)
 | |
|                     {
 | |
|                         break;
 | |
|                     }
 | |
| 
 | |
|                     DrawAxis(ScaleSteps[i], ScaleSteps[i]*verticalScale, GetAxisLabel(j));
 | |
|                     ++j;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
| #if !LEGACY_UI
 | |
| 
 | |
|             m.Clear();
 | |
|             m.SetVertices(_meshVertices);
 | |
|             m.SetColors(_meshVertexColors);
 | |
|             m.SetTriangles(_meshTriangles, 0);
 | |
| 
 | |
| #endif
 | |
|         }
 | |
| 
 | |
|         protected void DrawDataPoint(float xPosition, float verticalScale, ProfilerFrame frame)
 | |
|         {
 | |
|             // Right-hand x-coord
 | |
|             var rx = Mathf.Min(_clipBounds.width/2f, xPosition + DataPointWidth - DataPointMargin);
 | |
| 
 | |
|             var currentLineHeight = 0f;
 | |
| 
 | |
|             for (var j = 0; j < LineCount; j++)
 | |
|             {
 | |
|                 var lineIndex = j;
 | |
| 
 | |
|                 var value = 0f;
 | |
| 
 | |
|                 if (j == 0)
 | |
|                 {
 | |
|                     value = (float) frame.UpdateTime;
 | |
|                 }
 | |
|                 else if (j == 1)
 | |
|                 {
 | |
|                     value = (float) frame.RenderTime;
 | |
|                 }
 | |
|                 else if (j == 2)
 | |
|                 {
 | |
|                     value = (float) frame.OtherTime;
 | |
|                 }
 | |
| 
 | |
|                 value *= verticalScale;
 | |
| 
 | |
|                 if (value.ApproxZero() || value - DataPointVerticalMargin*2f < 0f)
 | |
|                 {
 | |
|                     continue;
 | |
|                 }
 | |
| 
 | |
|                 // Lower y-coord
 | |
|                 var ly = currentLineHeight + DataPointVerticalMargin - rectTransform.rect.height/2f;
 | |
| 
 | |
|                 if (VerticalAlignment == VerticalAlignments.Top)
 | |
|                 {
 | |
|                     ly = rectTransform.rect.height/2f - currentLineHeight - DataPointVerticalMargin;
 | |
|                 }
 | |
| 
 | |
|                 // Upper y-coord
 | |
|                 var uy = ly + value - DataPointVerticalMargin;
 | |
| 
 | |
|                 if (VerticalAlignment == VerticalAlignments.Top)
 | |
|                 {
 | |
|                     uy = ly - value + DataPointVerticalMargin;
 | |
|                 }
 | |
| 
 | |
|                 var c = LineColours[lineIndex];
 | |
| 
 | |
|                 AddRect(new Vector3(Mathf.Max(-_clipBounds.width/2f, xPosition), ly),
 | |
|                     new Vector3(Mathf.Max(-_clipBounds.width/2f, xPosition), uy), new Vector3(rx, uy),
 | |
|                     new Vector3(rx, ly), c);
 | |
| 
 | |
|                 currentLineHeight += value;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         protected void DrawAxis(float frameTime, float yPosition, ProfilerGraphAxisLabel label)
 | |
|         {
 | |
| #if LOGGING
 | |
| 			if(!Application.isPlaying)
 | |
| 				Debug.Log("Draw Axis: {0}".Fmt(yPosition));
 | |
| #endif
 | |
| 
 | |
|             var lx = -rectTransform.rect.width*0.5f;
 | |
|             var rx = -lx;
 | |
| 
 | |
|             var uy = yPosition - rectTransform.rect.height*0.5f + 0.5f;
 | |
|             var ly = yPosition - rectTransform.rect.height*0.5f - 0.5f;
 | |
| 
 | |
|             var c = new Color(1f, 1f, 1f, 0.4f);
 | |
| 
 | |
|             AddRect(new Vector3(lx, ly), new Vector3(lx, uy), new Vector3(rx, uy), new Vector3(rx, ly), c);
 | |
| 
 | |
|             if (label != null)
 | |
|             {
 | |
|                 label.SetValue(frameTime, yPosition);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         protected void AddRect(Vector3 tl, Vector3 tr, Vector3 bl, Vector3 br, Color c)
 | |
|         {
 | |
| #if LEGACY_UI
 | |
| 
 | |
|             var v = UIVertex.simpleVert;
 | |
|             v.color = c;
 | |
| 
 | |
|             v.position = tl;
 | |
|             _vbo.Add(v);
 | |
| 
 | |
|             v.position = tr;
 | |
|             _vbo.Add(v);
 | |
| 
 | |
|             v.position = bl;
 | |
|             _vbo.Add(v);
 | |
| 
 | |
|             v.position = br;
 | |
|             _vbo.Add(v);
 | |
| 
 | |
| #else
 | |
| 
 | |
|     // New UI system uses triangles
 | |
| 
 | |
|             _meshVertices.Add(tl);
 | |
|             _meshVertices.Add(tr);
 | |
|             _meshVertices.Add(bl);
 | |
|             _meshVertices.Add(br);
 | |
| 
 | |
|             _meshTriangles.Add(_meshVertices.Count - 4); // tl
 | |
|             _meshTriangles.Add(_meshVertices.Count - 3); // tr
 | |
|             _meshTriangles.Add(_meshVertices.Count - 1); // br
 | |
| 
 | |
|             _meshTriangles.Add(_meshVertices.Count - 2); // bl
 | |
|             _meshTriangles.Add(_meshVertices.Count - 1); // br
 | |
|             _meshTriangles.Add(_meshVertices.Count - 3); // tr
 | |
| 
 | |
|             _meshVertexColors.Add(c);
 | |
|             _meshVertexColors.Add(c);
 | |
|             _meshVertexColors.Add(c);
 | |
|             _meshVertexColors.Add(c);
 | |
| 
 | |
| #endif
 | |
|         }
 | |
| 
 | |
|         protected ProfilerFrame GetFrame(int i)
 | |
|         {
 | |
| #if UNITY_EDITOR
 | |
| 
 | |
|             if (!Application.isPlaying)
 | |
|             {
 | |
|                 return TestData[i];
 | |
|             }
 | |
| 
 | |
| #endif
 | |
| 
 | |
|             return _profilerService.FrameBuffer[i];
 | |
|         }
 | |
| 
 | |
|         protected int CalculateVisibleDataPointCount()
 | |
|         {
 | |
|             return Mathf.RoundToInt(rectTransform.rect.width/DataPointWidth);
 | |
|         }
 | |
| 
 | |
|         protected int GetFrameBufferCurrentSize()
 | |
|         {
 | |
| #if UNITY_EDITOR
 | |
| 
 | |
|             if (!Application.isPlaying)
 | |
|             {
 | |
|                 return TestData.Length;
 | |
|             }
 | |
| 
 | |
| #endif
 | |
| 
 | |
|             return _profilerService.FrameBuffer.Count;
 | |
|         }
 | |
| 
 | |
|         protected int GetFrameBufferMaxSize()
 | |
|         {
 | |
| #if UNITY_EDITOR
 | |
| 
 | |
|             if (!Application.isPlaying)
 | |
|             {
 | |
|                 return TestData.Length;
 | |
|             }
 | |
| 
 | |
| #endif
 | |
| 
 | |
|             return _profilerService.FrameBuffer.Capacity;
 | |
|         }
 | |
| 
 | |
|         protected float CalculateMaxFrameTime()
 | |
|         {
 | |
|             var frameCount = GetFrameBufferCurrentSize();
 | |
|             var c = Mathf.Min(CalculateVisibleDataPointCount(), frameCount);
 | |
| 
 | |
|             var max = 0d;
 | |
| 
 | |
|             for (var i = 0; i < c; i++)
 | |
|             {
 | |
|                 var frameNumber = frameCount - i - 1;
 | |
| 
 | |
|                 var t = GetFrame(frameNumber);
 | |
| 
 | |
|                 if (t.FrameTime > max)
 | |
|                 {
 | |
|                     max = t.FrameTime;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return (float) max;
 | |
|         }
 | |
| 
 | |
|         private ProfilerGraphAxisLabel GetAxisLabel(int index)
 | |
|         {
 | |
|             if (_axisLabels == null || !Application.isPlaying)
 | |
|             {
 | |
|                 _axisLabels = GetComponentsInChildren<ProfilerGraphAxisLabel>();
 | |
|             }
 | |
| 
 | |
|             if (_axisLabels.Length > index)
 | |
|             {
 | |
|                 return _axisLabels[index];
 | |
|             }
 | |
| 
 | |
|             Debug.LogWarning("[SRDebugger.Profiler] Not enough axis labels in pool");
 | |
| 
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         #region Editor Only test data
 | |
| 
 | |
| #if UNITY_EDITOR
 | |
| 
 | |
|         private ProfilerFrame[] TestData
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 if (_testData == null)
 | |
|                 {
 | |
|                     _testData = GenerateSampleData();
 | |
|                 }
 | |
| 
 | |
|                 return _testData;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private ProfilerFrame[] _testData;
 | |
| 
 | |
|         protected static ProfilerFrame[] GenerateSampleData()
 | |
|         {
 | |
|             var sampleCount = 200;
 | |
| 
 | |
|             var data = new ProfilerFrame[sampleCount];
 | |
| 
 | |
|             for (var i = 0; i < sampleCount; i++)
 | |
|             {
 | |
|                 var frame = new ProfilerFrame();
 | |
| 
 | |
|                 for (var j = 0; j < 3; j++)
 | |
|                 {
 | |
|                     var v = 0d;
 | |
| 
 | |
|                     if (j == 0)
 | |
|                     {
 | |
|                         v = Mathf.PerlinNoise(i/200f, 0);
 | |
|                     }
 | |
|                     else if (j == 1)
 | |
|                     {
 | |
|                         v = Mathf.PerlinNoise(0, i/200f);
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         v = Random.Range(0, 1f);
 | |
|                     }
 | |
| 
 | |
|                     v *= (1f/60f)*0.333f;
 | |
| 
 | |
|                     // Simulate spikes
 | |
|                     if (Random.value > 0.8f)
 | |
|                     {
 | |
|                         v *= Random.Range(1.2f, 1.8f);
 | |
|                     }
 | |
| 
 | |
|                     if (j == 2)
 | |
|                     {
 | |
|                         v *= 0.1f;
 | |
|                     }
 | |
| 
 | |
|                     if (j == 0)
 | |
|                     {
 | |
|                         frame.UpdateTime = v;
 | |
|                     }
 | |
|                     else if (j == 1)
 | |
|                     {
 | |
|                         frame.RenderTime = v;
 | |
|                     }
 | |
|                     else if (j == 2)
 | |
|                     {
 | |
|                         frame.FrameTime = frame.RenderTime + frame.UpdateTime + v;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 data[i] = frame;
 | |
|             }
 | |
| 
 | |
|             data[0] = new ProfilerFrame
 | |
|             {
 | |
|                 FrameTime = 0.005,
 | |
|                 RenderTime = 0.005,
 | |
|                 UpdateTime = 0.005
 | |
|             };
 | |
| 
 | |
|             data[sampleCount - 1] = new ProfilerFrame
 | |
|             {
 | |
|                 FrameTime = 1d/60d,
 | |
|                 RenderTime = 0.007,
 | |
|                 UpdateTime = 0.007
 | |
|             };
 | |
| 
 | |
|             return data;
 | |
|         }
 | |
| 
 | |
| #endif
 | |
| 
 | |
|         #endregion
 | |
|     }
 | |
| }
 |