ProfilerGraphControl.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. //#define LOGGING
  2. #if UNITY_4_6 || UNITY_4_7 || UNITY_5_0 || UNITY_5_1
  3. #define LEGACY_UI
  4. #endif
  5. namespace SRDebugger.UI.Controls
  6. {
  7. using System.Collections.Generic;
  8. using Services;
  9. using SRF;
  10. using SRF.Service;
  11. using UnityEngine;
  12. using UnityEngine.UI;
  13. [ExecuteInEditMode]
  14. [RequireComponent(typeof (RectTransform))]
  15. [RequireComponent(typeof (CanvasRenderer))]
  16. public class ProfilerGraphControl : Graphic
  17. {
  18. public enum VerticalAlignments
  19. {
  20. Top,
  21. Bottom
  22. }
  23. public VerticalAlignments VerticalAlignment = VerticalAlignments.Bottom;
  24. private static readonly float[] ScaleSteps =
  25. {
  26. 1f/200f,
  27. 1f/160f,
  28. 1f/120f,
  29. 1f/100f,
  30. 1f/60f,
  31. 1f/30f,
  32. 1f/20f,
  33. 1f/12f,
  34. 1f/6f
  35. };
  36. /// <summary>
  37. /// Resize the y-axis to fit the nearest useful fps value
  38. /// </summary>
  39. public bool FloatingScale;
  40. /// <summary>
  41. /// If not using FloatingScale, use the target FPS set by Application.targetFrameRate for TargetFps
  42. /// </summary>
  43. public bool TargetFpsUseApplication;
  44. /// <summary>
  45. /// Toggle drawing of the various axes
  46. /// </summary>
  47. public bool DrawAxes = true;
  48. /// <summary>
  49. /// If FloatingScale is disabled, use this value to determine y-axis
  50. /// </summary>
  51. public int TargetFps = 60;
  52. public bool Clip = true;
  53. public const float DataPointMargin = 2f;
  54. public const float DataPointVerticalMargin = 2f;
  55. public const float DataPointWidth = 4f;
  56. public int VerticalPadding = 10;
  57. public const int LineCount = 3;
  58. public Color[] LineColours = new Color[0];
  59. private IProfilerService _profilerService;
  60. private ProfilerGraphAxisLabel[] _axisLabels;
  61. private Rect _clipBounds;
  62. #if LEGACY_UI
  63. private List<UIVertex> _vbo;
  64. #else
  65. private readonly List<Vector3> _meshVertices = new List<Vector3>();
  66. private readonly List<Color32> _meshVertexColors = new List<Color32>();
  67. private readonly List<int> _meshTriangles = new List<int>();
  68. #endif
  69. protected override void Awake()
  70. {
  71. base.Awake();
  72. _profilerService = SRServiceManager.GetService<IProfilerService>();
  73. }
  74. protected override void Start()
  75. {
  76. base.Start();
  77. }
  78. protected void Update()
  79. {
  80. SetVerticesDirty();
  81. }
  82. #if LEGACY_UI
  83. protected override void OnFillVBO(List<UIVertex> vbo)
  84. #else
  85. [System.ObsoleteAttribute]
  86. protected override void OnPopulateMesh(Mesh m)
  87. #endif
  88. {
  89. #if LEGACY_UI
  90. _vbo = vbo;
  91. #else
  92. _meshVertices.Clear();
  93. _meshVertexColors.Clear();
  94. _meshTriangles.Clear();
  95. #endif
  96. #if LOGGING
  97. if(!Application.isPlaying)
  98. Debug.Log("Draw");
  99. #endif
  100. var graphWidth = rectTransform.rect.width;
  101. var graphHeight = rectTransform.rect.height;
  102. _clipBounds = new Rect(0, 0, graphWidth, graphHeight);
  103. var targetFps = TargetFps;
  104. if (Application.isPlaying && TargetFpsUseApplication && Application.targetFrameRate > 0)
  105. {
  106. targetFps = Application.targetFrameRate;
  107. }
  108. var maxValue = 1f/targetFps;
  109. // Holds the index of the nearest 'useful' FPS step
  110. var fpsStep = -1;
  111. var maxFrameTime = FloatingScale ? CalculateMaxFrameTime() : 1f/targetFps;
  112. if (FloatingScale)
  113. {
  114. for (var i = 0; i < ScaleSteps.Length; i++)
  115. {
  116. var step = ScaleSteps[i];
  117. if (maxFrameTime < step*1.1f)
  118. {
  119. maxValue = step;
  120. fpsStep = i;
  121. break;
  122. }
  123. }
  124. // Fall back on the largest one
  125. if (fpsStep < 0)
  126. {
  127. fpsStep = ScaleSteps.Length - 1;
  128. maxValue = ScaleSteps[fpsStep];
  129. }
  130. }
  131. else
  132. {
  133. // Search for the next scale step after the user-provided step
  134. for (var i = 0; i < ScaleSteps.Length; i++)
  135. {
  136. var step = ScaleSteps[i];
  137. if (maxFrameTime > step)
  138. {
  139. fpsStep = i;
  140. }
  141. }
  142. }
  143. var verticalScale = (graphHeight - (VerticalPadding*2))/maxValue;
  144. // Number of data points that can fit into the graph space
  145. var availableDataPoints = CalculateVisibleDataPointCount();
  146. // Reallocate vertex array if insufficient length (or not yet created)
  147. var sampleCount = GetFrameBufferCurrentSize();
  148. for (var i = 0; i < sampleCount; i++)
  149. {
  150. // Break loop if all visible data points have been drawn
  151. if (i >= availableDataPoints)
  152. {
  153. break;
  154. }
  155. // When using right-alignment, read from the end of the profiler buffer
  156. var frame = GetFrame(sampleCount - i - 1);
  157. // Left-hand x coord
  158. var lx = graphWidth - DataPointWidth*i - DataPointWidth - graphWidth/2f;
  159. DrawDataPoint(lx, verticalScale, frame);
  160. }
  161. if (DrawAxes)
  162. {
  163. if (!FloatingScale)
  164. {
  165. DrawAxis(maxValue, maxValue*verticalScale, GetAxisLabel(0));
  166. }
  167. var axisCount = 2;
  168. var j = 0;
  169. if (!FloatingScale)
  170. {
  171. j++;
  172. }
  173. for (var i = fpsStep; i >= 0; --i)
  174. {
  175. if (j >= axisCount)
  176. {
  177. break;
  178. }
  179. DrawAxis(ScaleSteps[i], ScaleSteps[i]*verticalScale, GetAxisLabel(j));
  180. ++j;
  181. }
  182. }
  183. #if !LEGACY_UI
  184. m.Clear();
  185. m.SetVertices(_meshVertices);
  186. m.SetColors(_meshVertexColors);
  187. m.SetTriangles(_meshTriangles, 0);
  188. #endif
  189. }
  190. protected void DrawDataPoint(float xPosition, float verticalScale, ProfilerFrame frame)
  191. {
  192. // Right-hand x-coord
  193. var rx = Mathf.Min(_clipBounds.width/2f, xPosition + DataPointWidth - DataPointMargin);
  194. var currentLineHeight = 0f;
  195. for (var j = 0; j < LineCount; j++)
  196. {
  197. var lineIndex = j;
  198. var value = 0f;
  199. if (j == 0)
  200. {
  201. value = (float) frame.UpdateTime;
  202. }
  203. else if (j == 1)
  204. {
  205. value = (float) frame.RenderTime;
  206. }
  207. else if (j == 2)
  208. {
  209. value = (float) frame.OtherTime;
  210. }
  211. value *= verticalScale;
  212. if (value.ApproxZero() || value - DataPointVerticalMargin*2f < 0f)
  213. {
  214. continue;
  215. }
  216. // Lower y-coord
  217. var ly = currentLineHeight + DataPointVerticalMargin - rectTransform.rect.height/2f;
  218. if (VerticalAlignment == VerticalAlignments.Top)
  219. {
  220. ly = rectTransform.rect.height/2f - currentLineHeight - DataPointVerticalMargin;
  221. }
  222. // Upper y-coord
  223. var uy = ly + value - DataPointVerticalMargin;
  224. if (VerticalAlignment == VerticalAlignments.Top)
  225. {
  226. uy = ly - value + DataPointVerticalMargin;
  227. }
  228. var c = LineColours[lineIndex];
  229. AddRect(new Vector3(Mathf.Max(-_clipBounds.width/2f, xPosition), ly),
  230. new Vector3(Mathf.Max(-_clipBounds.width/2f, xPosition), uy), new Vector3(rx, uy),
  231. new Vector3(rx, ly), c);
  232. currentLineHeight += value;
  233. }
  234. }
  235. protected void DrawAxis(float frameTime, float yPosition, ProfilerGraphAxisLabel label)
  236. {
  237. #if LOGGING
  238. if(!Application.isPlaying)
  239. Debug.Log("Draw Axis: {0}".Fmt(yPosition));
  240. #endif
  241. var lx = -rectTransform.rect.width*0.5f;
  242. var rx = -lx;
  243. var uy = yPosition - rectTransform.rect.height*0.5f + 0.5f;
  244. var ly = yPosition - rectTransform.rect.height*0.5f - 0.5f;
  245. var c = new Color(1f, 1f, 1f, 0.4f);
  246. AddRect(new Vector3(lx, ly), new Vector3(lx, uy), new Vector3(rx, uy), new Vector3(rx, ly), c);
  247. if (label != null)
  248. {
  249. label.SetValue(frameTime, yPosition);
  250. }
  251. }
  252. protected void AddRect(Vector3 tl, Vector3 tr, Vector3 bl, Vector3 br, Color c)
  253. {
  254. #if LEGACY_UI
  255. var v = UIVertex.simpleVert;
  256. v.color = c;
  257. v.position = tl;
  258. _vbo.Add(v);
  259. v.position = tr;
  260. _vbo.Add(v);
  261. v.position = bl;
  262. _vbo.Add(v);
  263. v.position = br;
  264. _vbo.Add(v);
  265. #else
  266. // New UI system uses triangles
  267. _meshVertices.Add(tl);
  268. _meshVertices.Add(tr);
  269. _meshVertices.Add(bl);
  270. _meshVertices.Add(br);
  271. _meshTriangles.Add(_meshVertices.Count - 4); // tl
  272. _meshTriangles.Add(_meshVertices.Count - 3); // tr
  273. _meshTriangles.Add(_meshVertices.Count - 1); // br
  274. _meshTriangles.Add(_meshVertices.Count - 2); // bl
  275. _meshTriangles.Add(_meshVertices.Count - 1); // br
  276. _meshTriangles.Add(_meshVertices.Count - 3); // tr
  277. _meshVertexColors.Add(c);
  278. _meshVertexColors.Add(c);
  279. _meshVertexColors.Add(c);
  280. _meshVertexColors.Add(c);
  281. #endif
  282. }
  283. protected ProfilerFrame GetFrame(int i)
  284. {
  285. #if UNITY_EDITOR
  286. if (!Application.isPlaying)
  287. {
  288. return TestData[i];
  289. }
  290. #endif
  291. return _profilerService.FrameBuffer[i];
  292. }
  293. protected int CalculateVisibleDataPointCount()
  294. {
  295. return Mathf.RoundToInt(rectTransform.rect.width/DataPointWidth);
  296. }
  297. protected int GetFrameBufferCurrentSize()
  298. {
  299. #if UNITY_EDITOR
  300. if (!Application.isPlaying)
  301. {
  302. return TestData.Length;
  303. }
  304. #endif
  305. return _profilerService.FrameBuffer.Count;
  306. }
  307. protected int GetFrameBufferMaxSize()
  308. {
  309. #if UNITY_EDITOR
  310. if (!Application.isPlaying)
  311. {
  312. return TestData.Length;
  313. }
  314. #endif
  315. return _profilerService.FrameBuffer.Capacity;
  316. }
  317. protected float CalculateMaxFrameTime()
  318. {
  319. var frameCount = GetFrameBufferCurrentSize();
  320. var c = Mathf.Min(CalculateVisibleDataPointCount(), frameCount);
  321. var max = 0d;
  322. for (var i = 0; i < c; i++)
  323. {
  324. var frameNumber = frameCount - i - 1;
  325. var t = GetFrame(frameNumber);
  326. if (t.FrameTime > max)
  327. {
  328. max = t.FrameTime;
  329. }
  330. }
  331. return (float) max;
  332. }
  333. private ProfilerGraphAxisLabel GetAxisLabel(int index)
  334. {
  335. if (_axisLabels == null || !Application.isPlaying)
  336. {
  337. _axisLabels = GetComponentsInChildren<ProfilerGraphAxisLabel>();
  338. }
  339. if (_axisLabels.Length > index)
  340. {
  341. return _axisLabels[index];
  342. }
  343. Debug.LogWarning("[SRDebugger.Profiler] Not enough axis labels in pool");
  344. return null;
  345. }
  346. #region Editor Only test data
  347. #if UNITY_EDITOR
  348. private ProfilerFrame[] TestData
  349. {
  350. get
  351. {
  352. if (_testData == null)
  353. {
  354. _testData = GenerateSampleData();
  355. }
  356. return _testData;
  357. }
  358. }
  359. private ProfilerFrame[] _testData;
  360. protected static ProfilerFrame[] GenerateSampleData()
  361. {
  362. var sampleCount = 200;
  363. var data = new ProfilerFrame[sampleCount];
  364. for (var i = 0; i < sampleCount; i++)
  365. {
  366. var frame = new ProfilerFrame();
  367. for (var j = 0; j < 3; j++)
  368. {
  369. var v = 0d;
  370. if (j == 0)
  371. {
  372. v = Mathf.PerlinNoise(i/200f, 0);
  373. }
  374. else if (j == 1)
  375. {
  376. v = Mathf.PerlinNoise(0, i/200f);
  377. }
  378. else
  379. {
  380. v = Random.Range(0, 1f);
  381. }
  382. v *= (1f/60f)*0.333f;
  383. // Simulate spikes
  384. if (Random.value > 0.8f)
  385. {
  386. v *= Random.Range(1.2f, 1.8f);
  387. }
  388. if (j == 2)
  389. {
  390. v *= 0.1f;
  391. }
  392. if (j == 0)
  393. {
  394. frame.UpdateTime = v;
  395. }
  396. else if (j == 1)
  397. {
  398. frame.RenderTime = v;
  399. }
  400. else if (j == 2)
  401. {
  402. frame.FrameTime = frame.RenderTime + frame.UpdateTime + v;
  403. }
  404. }
  405. data[i] = frame;
  406. }
  407. data[0] = new ProfilerFrame
  408. {
  409. FrameTime = 0.005,
  410. RenderTime = 0.005,
  411. UpdateTime = 0.005
  412. };
  413. data[sampleCount - 1] = new ProfilerFrame
  414. {
  415. FrameTime = 1d/60d,
  416. RenderTime = 0.007,
  417. UpdateTime = 0.007
  418. };
  419. return data;
  420. }
  421. #endif
  422. #endregion
  423. }
  424. }