A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3
4namespace UnityEngine.Rendering
5{
6 /// <summary>
7 /// Represents a system bottleneck, meaning the factor that is most dominant in determining
8 /// the total frame time.
9 /// </summary>
10 internal enum PerformanceBottleneck
11 {
12 Indeterminate, // Cannot be determined
13 PresentLimited, // Limited by presentation (vsync or framerate cap)
14 CPU, // Limited by CPU (main and/or render thread)
15 GPU, // Limited by GPU
16 Balanced, // Limited by both CPU and GPU, i.e. well balanced
17 }
18
19 /// <summary>
20 /// BottleneckHistogram represents the distribution of bottlenecks over the Bottleneck History Window,
21 /// the size of which is determined by <see cref="DebugFrameTiming.bottleneckHistorySize"/>.
22 /// </summary>
23 internal struct BottleneckHistogram
24 {
25 internal float PresentLimited;
26 internal float CPU;
27 internal float GPU;
28 internal float Balanced;
29 };
30
31 /// <summary>
32 /// Container class for bottleneck history with helper to calculate histogram.
33 /// </summary>
34 internal class BottleneckHistory
35 {
36 public BottleneckHistory(int initialCapacity)
37 {
38 m_Bottlenecks.Capacity = initialCapacity;
39 }
40
41 List<PerformanceBottleneck> m_Bottlenecks = new();
42
43 internal BottleneckHistogram Histogram;
44
45 internal void DiscardOldSamples(int historySize)
46 {
47 Debug.Assert(historySize > 0, "Invalid sampleHistorySize");
48
49 while (m_Bottlenecks.Count >= historySize)
50 m_Bottlenecks.RemoveAt(0);
51
52 m_Bottlenecks.Capacity = historySize;
53 }
54
55 internal void AddBottleneckFromAveragedSample(FrameTimeSample frameHistorySampleAverage)
56 {
57 var bottleneck = DetermineBottleneck(frameHistorySampleAverage);
58 m_Bottlenecks.Add(bottleneck);
59 }
60
61 internal void ComputeHistogram()
62 {
63 var stats = new BottleneckHistogram();
64 for (int i = 0; i < m_Bottlenecks.Count; i++)
65 {
66 switch (m_Bottlenecks[i])
67 {
68 case PerformanceBottleneck.Balanced:
69 stats.Balanced++;
70 break;
71 case PerformanceBottleneck.CPU:
72 stats.CPU++;
73 break;
74 case PerformanceBottleneck.GPU:
75 stats.GPU++;
76 break;
77 case PerformanceBottleneck.PresentLimited:
78 stats.PresentLimited++;
79 break;
80 }
81 }
82
83 stats.Balanced /= m_Bottlenecks.Count;
84 stats.CPU /= m_Bottlenecks.Count;
85 stats.GPU /= m_Bottlenecks.Count;
86 stats.PresentLimited /= m_Bottlenecks.Count;
87
88 Histogram = stats;
89 }
90
91 static PerformanceBottleneck DetermineBottleneck(FrameTimeSample s)
92 {
93 const float kNearFullFrameTimeThresholdPercent = 0.2f;
94 const float kNonZeroPresentWaitTimeMs = 0.5f;
95
96 if (s.GPUFrameTime == 0 || s.MainThreadCPUFrameTime == 0) // In direct mode, render thread doesn't exist
97 return PerformanceBottleneck.Indeterminate; // Missing data
98 float fullFrameTimeWithMargin = (1f - kNearFullFrameTimeThresholdPercent) * s.FullFrameTime;
99
100 // GPU time is close to frame time, CPU times are not
101 if (s.GPUFrameTime > fullFrameTimeWithMargin &&
102 s.MainThreadCPUFrameTime < fullFrameTimeWithMargin &&
103 s.RenderThreadCPUFrameTime < fullFrameTimeWithMargin)
104 return PerformanceBottleneck.GPU;
105
106 // One of the CPU times is close to frame time, GPU is not
107 if (s.GPUFrameTime < fullFrameTimeWithMargin &&
108 (s.MainThreadCPUFrameTime > fullFrameTimeWithMargin ||
109 s.RenderThreadCPUFrameTime > fullFrameTimeWithMargin))
110 return PerformanceBottleneck.CPU;
111
112 // Main thread waited due to Vsync or target frame rate
113 if (s.MainThreadCPUPresentWaitTime > kNonZeroPresentWaitTimeMs)
114 {
115 // None of the times are close to frame time
116 if (s.GPUFrameTime < fullFrameTimeWithMargin &&
117 s.MainThreadCPUFrameTime < fullFrameTimeWithMargin &&
118 s.RenderThreadCPUFrameTime < fullFrameTimeWithMargin)
119 return PerformanceBottleneck.PresentLimited;
120 }
121
122 return PerformanceBottleneck.Balanced;
123 }
124
125 internal void Clear()
126 {
127 m_Bottlenecks.Clear();
128 Histogram = new BottleneckHistogram();
129 }
130 }
131}