using System; using System.Collections.Generic; namespace UnityEngine.Rendering { /// /// Represents a system bottleneck, meaning the factor that is most dominant in determining /// the total frame time. /// internal enum PerformanceBottleneck { Indeterminate, // Cannot be determined PresentLimited, // Limited by presentation (vsync or framerate cap) CPU, // Limited by CPU (main and/or render thread) GPU, // Limited by GPU Balanced, // Limited by both CPU and GPU, i.e. well balanced } /// /// BottleneckHistogram represents the distribution of bottlenecks over the Bottleneck History Window, /// the size of which is determined by . /// internal struct BottleneckHistogram { internal float PresentLimited; internal float CPU; internal float GPU; internal float Balanced; }; /// /// Container class for bottleneck history with helper to calculate histogram. /// internal class BottleneckHistory { public BottleneckHistory(int initialCapacity) { m_Bottlenecks.Capacity = initialCapacity; } List m_Bottlenecks = new(); internal BottleneckHistogram Histogram; internal void DiscardOldSamples(int historySize) { Debug.Assert(historySize > 0, "Invalid sampleHistorySize"); while (m_Bottlenecks.Count >= historySize) m_Bottlenecks.RemoveAt(0); m_Bottlenecks.Capacity = historySize; } internal void AddBottleneckFromAveragedSample(FrameTimeSample frameHistorySampleAverage) { var bottleneck = DetermineBottleneck(frameHistorySampleAverage); m_Bottlenecks.Add(bottleneck); } internal void ComputeHistogram() { var stats = new BottleneckHistogram(); for (int i = 0; i < m_Bottlenecks.Count; i++) { switch (m_Bottlenecks[i]) { case PerformanceBottleneck.Balanced: stats.Balanced++; break; case PerformanceBottleneck.CPU: stats.CPU++; break; case PerformanceBottleneck.GPU: stats.GPU++; break; case PerformanceBottleneck.PresentLimited: stats.PresentLimited++; break; } } stats.Balanced /= m_Bottlenecks.Count; stats.CPU /= m_Bottlenecks.Count; stats.GPU /= m_Bottlenecks.Count; stats.PresentLimited /= m_Bottlenecks.Count; Histogram = stats; } static PerformanceBottleneck DetermineBottleneck(FrameTimeSample s) { const float kNearFullFrameTimeThresholdPercent = 0.2f; const float kNonZeroPresentWaitTimeMs = 0.5f; if (s.GPUFrameTime == 0 || s.MainThreadCPUFrameTime == 0) // In direct mode, render thread doesn't exist return PerformanceBottleneck.Indeterminate; // Missing data float fullFrameTimeWithMargin = (1f - kNearFullFrameTimeThresholdPercent) * s.FullFrameTime; // GPU time is close to frame time, CPU times are not if (s.GPUFrameTime > fullFrameTimeWithMargin && s.MainThreadCPUFrameTime < fullFrameTimeWithMargin && s.RenderThreadCPUFrameTime < fullFrameTimeWithMargin) return PerformanceBottleneck.GPU; // One of the CPU times is close to frame time, GPU is not if (s.GPUFrameTime < fullFrameTimeWithMargin && (s.MainThreadCPUFrameTime > fullFrameTimeWithMargin || s.RenderThreadCPUFrameTime > fullFrameTimeWithMargin)) return PerformanceBottleneck.CPU; // Main thread waited due to Vsync or target frame rate if (s.MainThreadCPUPresentWaitTime > kNonZeroPresentWaitTimeMs) { // None of the times are close to frame time if (s.GPUFrameTime < fullFrameTimeWithMargin && s.MainThreadCPUFrameTime < fullFrameTimeWithMargin && s.RenderThreadCPUFrameTime < fullFrameTimeWithMargin) return PerformanceBottleneck.PresentLimited; } return PerformanceBottleneck.Balanced; } internal void Clear() { m_Bottlenecks.Clear(); Histogram = new BottleneckHistogram(); } } }