A game about forced loneliness, made by TACStudios
1//#define RTPROFILER_DEBUG
2
3using System;
4using System.Collections.Generic;
5using System.Linq;
6using UnityEngine;
7
8namespace UnityEngine.Rendering
9{
10 /// <summary>
11 /// Debug frame timings class
12 /// </summary>
13 public class DebugFrameTiming
14 {
15 const string k_FpsFormatString = "{0:F1}";
16 const string k_MsFormatString = "{0:F2}ms";
17 const float k_RefreshRate = 1f / 5f;
18
19 internal FrameTimeSampleHistory m_FrameHistory;
20 internal BottleneckHistory m_BottleneckHistory;
21
22 /// <summary>
23 /// Size of the Bottleneck History Window in number of samples.
24 /// </summary>
25 public int bottleneckHistorySize { get; set; } = 60;
26
27 /// <summary>
28 /// Size of the Sample History Window in number of samples.
29 /// </summary>
30 public int sampleHistorySize { get; set; } = 30;
31
32 FrameTiming[] m_Timing = new FrameTiming[1];
33 FrameTimeSample m_Sample = new FrameTimeSample();
34
35 /// <summary>
36 /// Constructs the debug frame timing
37 /// </summary>
38 public DebugFrameTiming()
39 {
40 m_FrameHistory = new FrameTimeSampleHistory(sampleHistorySize);
41 m_BottleneckHistory = new BottleneckHistory(bottleneckHistorySize);
42 }
43
44 /// <summary>
45 /// Update timing data from profiling counters.
46 /// </summary>
47 public void UpdateFrameTiming()
48 {
49 m_Timing[0] = default;
50 m_Sample = default;
51 FrameTimingManager.CaptureFrameTimings();
52 FrameTimingManager.GetLatestTimings(1, m_Timing);
53
54 if (m_Timing.Length > 0)
55 {
56 m_Sample.FullFrameTime = (float)m_Timing.First().cpuFrameTime;
57 m_Sample.FramesPerSecond = m_Sample.FullFrameTime > 0f ? 1000f / m_Sample.FullFrameTime : 0f;
58 m_Sample.MainThreadCPUFrameTime = (float)m_Timing.First().cpuMainThreadFrameTime;
59 m_Sample.MainThreadCPUPresentWaitTime = (float)m_Timing.First().cpuMainThreadPresentWaitTime;
60 m_Sample.RenderThreadCPUFrameTime = (float)m_Timing.First().cpuRenderThreadFrameTime;
61 m_Sample.GPUFrameTime = (float)m_Timing.First().gpuFrameTime;
62 }
63
64 m_FrameHistory.DiscardOldSamples(sampleHistorySize);
65 m_FrameHistory.Add(m_Sample);
66 m_FrameHistory.ComputeAggregateValues();
67
68 m_BottleneckHistory.DiscardOldSamples(bottleneckHistorySize);
69 m_BottleneckHistory.AddBottleneckFromAveragedSample(m_FrameHistory.SampleAverage);
70 m_BottleneckHistory.ComputeHistogram();
71 }
72
73 /// <summary>
74 /// Add frame timing data widgets to debug UI.
75 /// </summary>
76 /// <param name="list">List of widgets to add the stats.</param>
77 public void RegisterDebugUI(List<DebugUI.Widget> list)
78 {
79 list.Add(new DebugUI.Foldout()
80 {
81 displayName = "Frame Stats",
82 isHeader = true,
83 opened = true,
84 columnLabels = new string[] { "Avg", "Min", "Max" },
85 children =
86 {
87 new DebugUI.ValueTuple
88 {
89 displayName = "Frame Rate (FPS)",
90 values = new[]
91 {
92 new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_FpsFormatString, getter = () => m_FrameHistory.SampleAverage.FramesPerSecond },
93 new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_FpsFormatString, getter = () => m_FrameHistory.SampleMin.FramesPerSecond },
94 new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_FpsFormatString, getter = () => m_FrameHistory.SampleMax.FramesPerSecond },
95 }
96 },
97 new DebugUI.ValueTuple
98 {
99 displayName = "Frame Time",
100 values = new[]
101 {
102 new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleAverage.FullFrameTime },
103 new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMin.FullFrameTime },
104 new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMax.FullFrameTime },
105 }
106 },
107 new DebugUI.ValueTuple
108 {
109 displayName = "CPU Main Thread Frame",
110 values = new[]
111 {
112 new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleAverage.MainThreadCPUFrameTime },
113 new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMin.MainThreadCPUFrameTime },
114 new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMax.MainThreadCPUFrameTime },
115 }
116 },
117 new DebugUI.ValueTuple
118 {
119 displayName = "CPU Render Thread Frame",
120 values = new[]
121 {
122 new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleAverage.RenderThreadCPUFrameTime },
123 new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMin.RenderThreadCPUFrameTime },
124 new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMax.RenderThreadCPUFrameTime },
125 }
126 },
127 new DebugUI.ValueTuple
128 {
129 displayName = "CPU Present Wait",
130 values = new[]
131 {
132 new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleAverage.MainThreadCPUPresentWaitTime },
133 new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMin.MainThreadCPUPresentWaitTime },
134 new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMax.MainThreadCPUPresentWaitTime },
135 }
136 },
137 new DebugUI.ValueTuple
138 {
139 displayName = "GPU Frame",
140 values = new[]
141 {
142 new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleAverage.GPUFrameTime },
143 new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMin.GPUFrameTime },
144 new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMax.GPUFrameTime },
145 }
146 }
147 }
148 });
149
150 list.Add(new DebugUI.Foldout
151 {
152 displayName = "Bottlenecks",
153 isHeader = true,
154 children =
155 {
156#if UNITY_EDITOR
157 new DebugUI.Container { displayName = "Not supported in Editor" }
158#else
159 new DebugUI.ProgressBarValue { displayName = "CPU", getter = () => m_BottleneckHistory.Histogram.CPU },
160 new DebugUI.ProgressBarValue { displayName = "GPU", getter = () => m_BottleneckHistory.Histogram.GPU },
161 new DebugUI.ProgressBarValue { displayName = "Present limited", getter = () => m_BottleneckHistory.Histogram.PresentLimited },
162 new DebugUI.ProgressBarValue { displayName = "Balanced", getter = () => m_BottleneckHistory.Histogram.Balanced },
163#endif
164 }
165 });
166#if RTPROFILER_DEBUG
167 list.Add(new DebugUI.Foldout
168 {
169 displayName = "Realtime Profiler Debug",
170 children =
171 {
172 new DebugUI.IntField
173 {
174 displayName = "Frame Time Sample History Size",
175 getter = () => sampleHistorySize,
176 setter = (value) => { sampleHistorySize = value; },
177 min = () => 1,
178 max = () => 100
179 },
180 new DebugUI.IntField
181 {
182 displayName = "Bottleneck History Size",
183 getter = () => bottleneckHistorySize,
184 setter = (value) => { bottleneckHistorySize = value; },
185 min = () => 1,
186 max = () => 100
187 },
188 new DebugUI.IntField
189 {
190 displayName = "Force VSyncCount",
191 min = () => - 1,
192 max = () => 4,
193 getter = () => QualitySettings.vSyncCount,
194 setter = (value) => { QualitySettings.vSyncCount = value; }
195 },
196 new DebugUI.IntField
197 {
198 displayName = "Force TargetFrameRate",
199 min = () => - 1,
200 max = () => 1000,
201 getter = () => Application.targetFrameRate,
202 setter = (value) => { Application.targetFrameRate = value; }
203 },
204 }
205 });
206#endif
207 }
208
209 internal void Reset()
210 {
211 m_BottleneckHistory.Clear();
212 m_FrameHistory.Clear();
213 }
214 }
215}