//#define RTPROFILER_DEBUG using System; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace UnityEngine.Rendering { /// /// Debug frame timings class /// public class DebugFrameTiming { const string k_FpsFormatString = "{0:F1}"; const string k_MsFormatString = "{0:F2}ms"; const float k_RefreshRate = 1f / 5f; internal FrameTimeSampleHistory m_FrameHistory; internal BottleneckHistory m_BottleneckHistory; /// /// Size of the Bottleneck History Window in number of samples. /// public int bottleneckHistorySize { get; set; } = 60; /// /// Size of the Sample History Window in number of samples. /// public int sampleHistorySize { get; set; } = 30; FrameTiming[] m_Timing = new FrameTiming[1]; FrameTimeSample m_Sample = new FrameTimeSample(); /// /// Constructs the debug frame timing /// public DebugFrameTiming() { m_FrameHistory = new FrameTimeSampleHistory(sampleHistorySize); m_BottleneckHistory = new BottleneckHistory(bottleneckHistorySize); } /// /// Update timing data from profiling counters. /// public void UpdateFrameTiming() { m_Timing[0] = default; m_Sample = default; FrameTimingManager.CaptureFrameTimings(); FrameTimingManager.GetLatestTimings(1, m_Timing); if (m_Timing.Length > 0) { m_Sample.FullFrameTime = (float)m_Timing.First().cpuFrameTime; m_Sample.FramesPerSecond = m_Sample.FullFrameTime > 0f ? 1000f / m_Sample.FullFrameTime : 0f; m_Sample.MainThreadCPUFrameTime = (float)m_Timing.First().cpuMainThreadFrameTime; m_Sample.MainThreadCPUPresentWaitTime = (float)m_Timing.First().cpuMainThreadPresentWaitTime; m_Sample.RenderThreadCPUFrameTime = (float)m_Timing.First().cpuRenderThreadFrameTime; m_Sample.GPUFrameTime = (float)m_Timing.First().gpuFrameTime; } m_FrameHistory.DiscardOldSamples(sampleHistorySize); m_FrameHistory.Add(m_Sample); m_FrameHistory.ComputeAggregateValues(); m_BottleneckHistory.DiscardOldSamples(bottleneckHistorySize); m_BottleneckHistory.AddBottleneckFromAveragedSample(m_FrameHistory.SampleAverage); m_BottleneckHistory.ComputeHistogram(); } /// /// Add frame timing data widgets to debug UI. /// /// List of widgets to add the stats. public void RegisterDebugUI(List list) { list.Add(new DebugUI.Foldout() { displayName = "Frame Stats", isHeader = true, opened = true, columnLabels = new string[] { "Avg", "Min", "Max" }, children = { new DebugUI.ValueTuple { displayName = "Frame Rate (FPS)", values = new[] { new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_FpsFormatString, getter = () => m_FrameHistory.SampleAverage.FramesPerSecond }, new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_FpsFormatString, getter = () => m_FrameHistory.SampleMin.FramesPerSecond }, new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_FpsFormatString, getter = () => m_FrameHistory.SampleMax.FramesPerSecond }, } }, new DebugUI.ValueTuple { displayName = "Frame Time", values = new[] { new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleAverage.FullFrameTime }, new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMin.FullFrameTime }, new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMax.FullFrameTime }, } }, new DebugUI.ValueTuple { displayName = "CPU Main Thread Frame", values = new[] { new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleAverage.MainThreadCPUFrameTime }, new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMin.MainThreadCPUFrameTime }, new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMax.MainThreadCPUFrameTime }, } }, new DebugUI.ValueTuple { displayName = "CPU Render Thread Frame", values = new[] { new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleAverage.RenderThreadCPUFrameTime }, new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMin.RenderThreadCPUFrameTime }, new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMax.RenderThreadCPUFrameTime }, } }, new DebugUI.ValueTuple { displayName = "CPU Present Wait", values = new[] { new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleAverage.MainThreadCPUPresentWaitTime }, new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMin.MainThreadCPUPresentWaitTime }, new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMax.MainThreadCPUPresentWaitTime }, } }, new DebugUI.ValueTuple { displayName = "GPU Frame", values = new[] { new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleAverage.GPUFrameTime }, new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMin.GPUFrameTime }, new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMax.GPUFrameTime }, } } } }); list.Add(new DebugUI.Foldout { displayName = "Bottlenecks", isHeader = true, children = { #if UNITY_EDITOR new DebugUI.Container { displayName = "Not supported in Editor" } #else new DebugUI.ProgressBarValue { displayName = "CPU", getter = () => m_BottleneckHistory.Histogram.CPU }, new DebugUI.ProgressBarValue { displayName = "GPU", getter = () => m_BottleneckHistory.Histogram.GPU }, new DebugUI.ProgressBarValue { displayName = "Present limited", getter = () => m_BottleneckHistory.Histogram.PresentLimited }, new DebugUI.ProgressBarValue { displayName = "Balanced", getter = () => m_BottleneckHistory.Histogram.Balanced }, #endif } }); #if RTPROFILER_DEBUG list.Add(new DebugUI.Foldout { displayName = "Realtime Profiler Debug", children = { new DebugUI.IntField { displayName = "Frame Time Sample History Size", getter = () => sampleHistorySize, setter = (value) => { sampleHistorySize = value; }, min = () => 1, max = () => 100 }, new DebugUI.IntField { displayName = "Bottleneck History Size", getter = () => bottleneckHistorySize, setter = (value) => { bottleneckHistorySize = value; }, min = () => 1, max = () => 100 }, new DebugUI.IntField { displayName = "Force VSyncCount", min = () => - 1, max = () => 4, getter = () => QualitySettings.vSyncCount, setter = (value) => { QualitySettings.vSyncCount = value; } }, new DebugUI.IntField { displayName = "Force TargetFrameRate", min = () => - 1, max = () => 1000, getter = () => Application.targetFrameRate, setter = (value) => { Application.targetFrameRate = value; } }, } }); #endif } internal void Reset() { m_BottleneckHistory.Clear(); m_FrameHistory.Clear(); } } }