Tanoda
RealtimeGraph.cs
Go to the documentation of this file.
1/******************************************************************************
2 * Copyright (C) Ultraleap, Inc. 2011-2020. *
3 * *
4 * Use subject to the terms of the Apache License 2.0 available at *
5 * http://www.apache.org/licenses/LICENSE-2.0, or another agreement *
6 * between Ultraleap and you, your company or other organization. *
7 ******************************************************************************/
8
9using UnityEngine;
10using UnityEngine.UI;
11using UnityEngine.Profiling;
12using System;
13using System.Collections;
14using System.Collections.Generic;
15using Leap.Unity.Query;
16
18
19 public class RealtimeGraph : MonoBehaviour {
20
21 private static RealtimeGraph _cachedInstance = null;
22 public static RealtimeGraph Instance {
23 get {
24 if (_cachedInstance == null) {
25 _cachedInstance = FindObjectOfType<RealtimeGraph>();
26 }
27 return _cachedInstance;
28 }
29 }
30
31 public struct GraphKey {
32 public string name;
34 public long tick;
35
36 public GraphKey(string name, GraphUnits units, long tick = 0) {
37 this.name = name;
38 this.units = units;
39 this.tick = tick;
40 }
41 }
42
43 public enum GraphUnits {
44 Miliseconds,
45 Framerate
46 }
47
48 public enum GraphMode {
49 Inclusive,
50 Exclusive
51 }
52
53 [SerializeField]
54 protected string _defaultGraph = "Framerate";
55
56 [SerializeField]
57 protected string[] _unitySamplerNames = new string[0];
58
59 [SerializeField]
60 protected GraphMode _graphMode = GraphMode.Exclusive;
61
62 [SerializeField]
63 protected int _historyLength = 128;
64
65 [SerializeField]
66 protected int _updatePeriod = 10;
67
68 [SerializeField]
69 protected int _samplesPerFrame = 1;
70
71 [SerializeField]
72 protected float _framerateLineSpacing = 60;
73
74 [SerializeField]
75 protected float _deltaLineSpacing = 10;
76
77 [SerializeField]
78 protected float _maxSmoothingDelay = 0.1f;
79
80 [SerializeField]
81 protected float _valueSmoothingDelay = 1;
82
83 [Header("References")]
84 [SerializeField]
86
87 [SerializeField]
88 protected Renderer _graphRenderer;
89
90 [SerializeField]
91 protected Text titleLabel;
92
93 [SerializeField]
94 protected TextMesh valueMesh;
95
96 [SerializeField]
97 protected GameObject customGraphPrefab;
98
99 public float UpdatePeriodFloat {
100 set {
101 _updatePeriod = Mathf.RoundToInt(Mathf.Lerp(1, 10, value));
102 }
103 }
104
105 public float BatchSizeFloat {
106 set {
107 _samplesPerFrame = Mathf.RoundToInt(Mathf.Lerp(1, 10, value));
108 }
109 }
110
111 public float GraphModeFloat {
112 set {
113 _graphMode = value > 0.5f ? GraphMode.Inclusive : GraphMode.Exclusive;
114 }
115 }
116
117 protected System.Diagnostics.Stopwatch _stopwatch = new System.Diagnostics.Stopwatch();
118
119 protected int _sampleIndex = 0;
120 protected int _updateCount = 0;
121
122 protected bool _paused = false;
123
124 protected Texture2D _texture;
125 protected Color32[] _colors;
126
129
131 protected Dictionary<string, Graph> _graphs;
132 protected Stack<Graph> _currentGraphStack = new Stack<Graph>();
133
135
136 protected List<Recorder> _unityRecorders = new List<Recorder>();
137
138 //Custom sample timers
140
141 public void BeginSample(GraphKey key) {
142 key.tick = _stopwatch.ElapsedTicks;
143 _keyBuffer.PushFront(key);
144 }
145
146 public void BeginSample(string sampleName, GraphUnits units) {
147 GraphKey key = new GraphKey(sampleName, units, _stopwatch.ElapsedTicks);
148 _keyBuffer.PushFront(key);
149 }
150
151 public void EndSample() {
152 _keyBuffer.PushFront(new GraphKey(null, GraphUnits.Miliseconds, _stopwatch.ElapsedTicks));
153 }
154
155 public void AddSample(string sampleName, GraphUnits units, long ticks) {
156 Graph graph = getGraph(sampleName, units);
157 graph.AddSample(ticks);
158 }
159
160 public void AddSample(string sampleName, GraphUnits units, float ms) {
161 Graph graph = getGraph(sampleName, units);
162 graph.AddSample((long)(ms * System.Diagnostics.Stopwatch.Frequency * 0.001f));
163 }
164
165 public void SwtichGraph(string graphName) {
166 _currentGraph = _graphs[graphName];
167 titleLabel.text = graphName;
168 }
169
170 public void TogglePaused() {
171 _paused = !_paused;
172 }
173
174 protected virtual void OnValidate() {
175 _historyLength = Mathf.Max(1, _historyLength);
176 _updatePeriod = Mathf.Max(1, _updatePeriod);
177 }
178
179 protected virtual void Awake() {
180 _graphs = new Dictionary<string, Graph>();
181
184
187 }
188
189 protected virtual void Start() {
190 _provider = _provider ?? FindObjectOfType<LeapServiceProvider>();
191
192 _texture = new Texture2D(_historyLength, 1, TextureFormat.Alpha8, false, true);
193 _texture.filterMode = FilterMode.Point;
194 _texture.wrapMode = TextureWrapMode.Clamp;
195 _colors = new Color32[_historyLength];
196
197 _graphRenderer.material.SetTexture("_GraphTexture", _texture);
198
199 _unitySamplerNames.Query().Select(n => {
200 getGraph(n, GraphUnits.Miliseconds);
201 return Recorder.Get(n);
202 }).FillList(_unityRecorders);
203
204 _stopwatch.Start();
205 }
206
207 protected virtual void OnEnable() {
208 Camera.onPreCull += onPreCull;
209 Camera.onPostRender += onPostRender;
210
211 StartCoroutine(endOfFrameWaiter());
212 }
213
214 protected virtual void OnDisable() {
215 Camera.onPreCull -= onPreCull;
216 Camera.onPostRender -= onPostRender;
217 }
218
219 protected virtual void Update() {
220 if (_fixedTicks != -1) {
221 AddSample("Physics Delta", GraphUnits.Miliseconds, _stopwatch.ElapsedTicks - _fixedTicks);
222 _fixedTicks = -1;
223 }
224
225 _preCullTicks = -1;
226
227 if (_provider != null) {
228 AddSample("Tracking Framerate", GraphUnits.Framerate, 1000.0f / _provider.CurrentFrame.CurrentFramesPerSecond);
229 }
230
231 for (int i = 0; i < _unityRecorders.Count; i++) {
232 var recorder = _unityRecorders[i];
233 AddSample(_unitySamplerNames[i], GraphUnits.Miliseconds, recorder.elapsedNanoseconds / 1000000.0f);
234 }
235
236 if (_currentGraph == null) {
237 return;
238 }
239
240 _sampleIndex++;
242 return;
243 }
244
245 replayKeys();
246
247 for (var it = _graphs.GetEnumerator(); it.MoveNext();) {
248 var graph = it.Current.Value;
249 if (_paused) {
250 graph.ClearSample();
251 } else {
252 graph.RecordSample(_sampleIndex);
253 }
254 }
255 _sampleIndex = 0;
256
257 float currValue, currMax;
258 switch (_graphMode) {
259 case GraphMode.Exclusive:
260 currValue = _currentGraph.exclusive.Back;
262 break;
263 case GraphMode.Inclusive:
264 currValue = _currentGraph.inclusive.Back;
266 break;
267 default:
268 throw new Exception("Unexpected graph mode");
269 }
270
271 _smoothedValue.Update(currValue, Time.deltaTime);
272
273 _smoothedMax.Update(currMax, Time.deltaTime);
274
275 _updateCount++;
277 UpdateTexture();
278 _updateCount = 0;
279 }
280 }
281
282 protected virtual void FixedUpdate() {
283 if (_fixedTicks == -1) {
284 _fixedTicks = _stopwatch.ElapsedTicks;
285 }
286 }
287
288 private IEnumerator endOfFrameWaiter() {
289 WaitForEndOfFrame waiter = new WaitForEndOfFrame();
290 long endOfFrameTicks = _stopwatch.ElapsedTicks;
291 while (true) {
292 yield return waiter;
293
294 long newTicks = _stopwatch.ElapsedTicks;
295 AddSample("Frame Delta", GraphUnits.Miliseconds, newTicks - endOfFrameTicks);
296 AddSample("Framerate", GraphUnits.Framerate, newTicks - endOfFrameTicks);
297 endOfFrameTicks = newTicks;
298
299 AddSample("Render Delta", GraphUnits.Miliseconds, _renderTicks);
300
301 float gpuTime = XRSupportUtil.GetGPUTime();
302
303 AddSample("GPU Time", GraphUnits.Miliseconds, gpuTime);
304
305 if (_provider != null) {
306 AddSample("Tracking Latency", GraphUnits.Miliseconds, (_provider.GetLeapController().Now() - _provider.CurrentFrame.Timestamp) * 0.001f);
307 }
308 }
309 }
310
311 private void onPreCull(Camera camera) {
312 if (_preCullTicks == -1) {
313 _preCullTicks = _stopwatch.ElapsedTicks;
314 }
315 }
316
317 private void onPostRender(Camera camera) {
318 _renderTicks = _stopwatch.ElapsedTicks - _preCullTicks;
319 }
320
321 protected static float ticksToMs(long ticks) {
322 return (float)(ticks / (System.Diagnostics.Stopwatch.Frequency / 1000.0));
323 }
324
325 private string msToString(float ms) {
326 return (Mathf.Round(ms * 10) * 0.1f).ToString();
327 }
328
329 private long msToTicks(float ms) {
330 return (long)(ms * System.Diagnostics.Stopwatch.Frequency * 1000);
331 }
332
333 private float getGraphSpacing() {
334 switch (_currentGraph.units) {
335 case GraphUnits.Framerate:
337 case GraphUnits.Miliseconds:
338 return _deltaLineSpacing;
339 default:
340 throw new Exception("Unexpected graph units");
341 }
342 }
343
344 private void UpdateTexture() {
345 float max = _smoothedMax.value * 1.5f;
346 max = (max > 0f) ? Mathf.Max(0.0001f, max) : Mathf.Min(-0.0001f, max);
347
348 Deque<float> history;
349 switch (_graphMode) {
350 case GraphMode.Exclusive:
351 history = _currentGraph.exclusive;
352 break;
353 case GraphMode.Inclusive:
354 history = _currentGraph.inclusive;
355 break;
356 default:
357 throw new Exception("Unexpected graph mode");
358 }
359
360 for (int i = 0; i < history.Count; i++) {
361 float percent = Mathf.Clamp01(history[i] / max);
362 byte percentByte = (byte)(percent * 255.9999f);
363 _colors[i] = new Color32(percentByte, percentByte, percentByte, percentByte);
364 }
365
366 _graphRenderer.material.SetFloat("_GraphScale", max / getGraphSpacing());
367
368 _texture.SetPixels32(_colors);
369 _texture.Apply();
370
371 valueMesh.text = msToString(_smoothedValue.value);
372
373 Vector3 localP = valueMesh.transform.localPosition;
374 localP.y = _smoothedValue.value / max - 0.5f;
375 if(!float.IsNaN(localP.y) && !float.IsInfinity(localP.y)) valueMesh.transform.localPosition = localP;
376 }
377
378 protected Graph getGraph(string name, GraphUnits units) {
379 Graph graph;
380 if (!_graphs.TryGetValue(name, out graph)) {
381 graph = new Graph(name, units, _historyLength);
382 _graphs[name] = graph;
383
384 GameObject buttonObj = Instantiate(customGraphPrefab);
385 buttonObj.transform.SetParent(customGraphPrefab.transform.parent, false);
386
387 Text buttonText = buttonObj.GetComponentInChildren<Text>();
388 buttonText.text = name.Replace(' ', '\n');
389
390 Button button = buttonObj.GetComponentInChildren<Button>();
391 addCallback(button, name);
392
393 buttonObj.SetActive(true);
394
395 if (_currentGraph == null && name == _defaultGraph) {
396 SwtichGraph(name);
397 }
398 }
399 return graph;
400 }
401
402 protected void addCallback(Button button, string name) {
403 button.onClick.AddListener(() => SwtichGraph(name));
404 }
405
406 protected void replayKeys() {
407 GraphKey key;
408 while (_keyBuffer.Count > 0) {
409 _keyBuffer.PopBack(out key);
410
411 if (key.name != null) {
412 Graph graph = getGraph(key.name, key.units);
413
414 if (_currentGraphStack.Count != 0) {
415 _currentGraphStack.Peek().PauseSample(key.tick);
416 }
417
418 graph.BeginSample(key.tick);
419
420 _currentGraphStack.Push(graph);
421 } else {
422 Graph graph = _currentGraphStack.Pop();
423 graph.EndSample(key.tick);
424
425 if (_currentGraphStack.Count != 0) {
426 _currentGraphStack.Peek().ResumeSample(key.tick);
427 }
428 }
429 }
430 }
431
432 protected class Graph {
433 public string name;
438
439 private int maxHistory;
440
441 private long accumulatedInclusiveTicks, accumulatedExclusiveTicks;
442 private long inclusiveStart, exclusiveStart;
443
444 public Graph(string name, GraphUnits units, int maxHistory) {
445 this.name = name;
446 this.units = units;
447 this.maxHistory = maxHistory;
448 exclusive = new Deque<float>(maxHistory);
449 inclusive = new Deque<float>(maxHistory);
450 exclusiveMax = new SlidingMax(maxHistory);
451 inclusiveMax = new SlidingMax(maxHistory);
452 }
453
454 public void BeginSample(long currTicks) {
455 inclusiveStart = exclusiveStart = currTicks;
456 }
457
458 public void PauseSample(long currTicks) {
459 accumulatedInclusiveTicks += currTicks - inclusiveStart;
460 }
461
462 public void ResumeSample(long currTicks) {
463 inclusiveStart = currTicks;
464 }
465
466 public void EndSample(long currTicks) {
467 accumulatedInclusiveTicks += currTicks - inclusiveStart;
468 accumulatedExclusiveTicks += currTicks - exclusiveStart;
469 }
470
471 public void AddSample(long ticks) {
472 accumulatedInclusiveTicks += ticks;
473 accumulatedExclusiveTicks += ticks;
474 }
475
476 public void ClearSample() {
477 accumulatedExclusiveTicks = accumulatedInclusiveTicks = 0;
478 }
479
480 public void RecordSample(int sampleCount) {
481 float inclusiveMs = ticksToMs(accumulatedInclusiveTicks / sampleCount);
482 float exclusiveMs = ticksToMs(accumulatedExclusiveTicks / sampleCount);
483 ClearSample();
484
485 switch (units) {
486 case GraphUnits.Miliseconds:
487 inclusive.PushBack(inclusiveMs);
488 exclusive.PushBack(exclusiveMs);
489 inclusiveMax.AddValue(inclusiveMs);
490 exclusiveMax.AddValue(exclusiveMs);
491 break;
492 case GraphUnits.Framerate:
493 inclusive.PushBack(1000.0f / inclusiveMs);
494 exclusive.PushBack(1000.0f / exclusiveMs);
495 inclusiveMax.AddValue(1000.0f / inclusiveMs);
496 exclusiveMax.AddValue(1000.0f / exclusiveMs);
497 break;
498 default:
499 throw new Exception("Unexpected units type");
500 }
501
502 while (inclusive.Count > maxHistory) {
504 }
505 while (exclusive.Count > maxHistory) {
507 }
508 }
509 }
510 }
511}
UnityEngine.UI.Button Button
Definition: Pointer.cs:7
long Now()
Returns a timestamp value as close as possible to the current time. Values are in microseconds,...
long Timestamp
The frame capture time in microseconds elapsed since an arbitrary point in time in the past.
Definition: Frame.cs:136
float CurrentFramesPerSecond
The instantaneous framerate.
Definition: Frame.cs:148
void PopFront()
Definition: Deque.cs:70
void PushBack(T t)
Definition: Deque.cs:50
Graph(string name, GraphUnits units, int maxHistory)
void BeginSample(string sampleName, GraphUnits units)
Graph getGraph(string name, GraphUnits units)
static RealtimeGraph Instance
void SwtichGraph(string graphName)
Dictionary< string, Graph > _graphs
void AddSample(string sampleName, GraphUnits units, float ms)
static float ticksToMs(long ticks)
void addCallback(Button button, string name)
System.Diagnostics.Stopwatch _stopwatch
void AddSample(string sampleName, GraphUnits units, long ticks)
The LeapServiceProvider provides tracked Leap Hand data and images from the device via the Leap servi...
Controller GetLeapController()
Returns the Leap Controller instance.
void AddValue(float value)
Definition: SlidingMax.cs:32
Time-step independent exponential smoothing.
float Update(float input, float deltaTime=1f)
GraphKey(string name, GraphUnits units, long tick=0)