A game about forced loneliness, made by TACStudios
at master 226 lines 9.0 kB view raw
1using System; 2using System.Collections.Generic; 3using System.Linq; 4 5namespace UnityEngine.Rendering 6{ 7 /// <summary> 8 /// Base class for Rendering Debugger Display Stats. 9 /// </summary> 10 /// <typeparam name="TProfileId">Type of ProfileId the pipeline uses</typeparam> 11 public abstract class DebugDisplayStats<TProfileId> where TProfileId : Enum 12 { 13 // Accumulate values to avg over one second. 14 private class AccumulatedTiming 15 { 16 public float accumulatedValue = 0; 17 public float lastAverage = 0; 18 19 internal void UpdateLastAverage(int frameCount) 20 { 21 lastAverage = accumulatedValue / frameCount; 22 accumulatedValue = 0.0f; 23 } 24 } 25 26 private enum DebugProfilingType 27 { 28 CPU, 29 InlineCPU, 30 GPU 31 } 32 33 /// <summary> 34 /// Enable profiling recorders. 35 /// </summary> 36 public abstract void EnableProfilingRecorders(); 37 38 /// <summary> 39 /// Disable all active profiling recorders. 40 /// </summary> 41 public abstract void DisableProfilingRecorders(); 42 43 /// <summary> 44 /// Add display stats widgets to the list provided. 45 /// </summary> 46 /// <param name="list">List to add the widgets to.</param> 47 public abstract void RegisterDebugUI(List<DebugUI.Widget> list); 48 49 /// <summary> 50 /// Update the timing data displayed in Display Stats panel. 51 /// </summary> 52 public abstract void Update(); 53 54 /// <summary> 55 /// Helper function to get all TProfilerId values of a given type to show in Detailed Stats section. 56 /// </summary> 57 /// <returns>List of TProfileId values excluding ones marked with [HideInDebugUI]</returns> 58 protected List<TProfileId> GetProfilerIdsToDisplay() 59 { 60 List<TProfileId> ids = new(); 61 var type = typeof(TProfileId); 62 63 var enumValues = Enum.GetValues(type); 64 foreach (var enumValue in enumValues) 65 { 66 var memberInfos = type.GetMember(enumValue.ToString()); 67 var enumValueMemberInfo = memberInfos.First(m => m.DeclaringType == type); 68 var hiddenAttribute = Attribute.GetCustomAttribute(enumValueMemberInfo, typeof(HideInDebugUIAttribute)); 69 if (hiddenAttribute == null) 70 ids.Add((TProfileId)enumValue); 71 } 72 73 return ids; 74 } 75 76 /// <summary> 77 /// Update the detailed stats 78 /// </summary> 79 /// <param name="samplers">List of samplers to update</param> 80 protected void UpdateDetailedStats(List<TProfileId> samplers) 81 { 82 m_HiddenProfileIds.Clear(); 83 84 m_TimeSinceLastAvgValue += Time.unscaledDeltaTime; 85 m_AccumulatedFrames++; 86 bool needUpdatingAverages = m_TimeSinceLastAvgValue >= k_AccumulationTimeInSeconds; 87 88 UpdateListOfAveragedProfilerTimings(needUpdatingAverages, samplers); 89 90 if (needUpdatingAverages) 91 { 92 m_TimeSinceLastAvgValue = 0.0f; 93 m_AccumulatedFrames = 0; 94 } 95 } 96 97 private static readonly string[] k_DetailedStatsColumnLabels = {"CPU", "CPUInline", "GPU"}; 98 private Dictionary<TProfileId, AccumulatedTiming>[] m_AccumulatedTiming = { new(), new(), new() }; 99 private float m_TimeSinceLastAvgValue = 0.0f; 100 private int m_AccumulatedFrames = 0; 101 private HashSet<TProfileId> m_HiddenProfileIds = new(); 102 103 private const float k_AccumulationTimeInSeconds = 1.0f; 104 105 /// <summary> Whether to display timings averaged over a second instead of updating every frame. </summary> 106 protected bool averageProfilerTimingsOverASecond = false; 107 108 /// <summary> Whether to hide empty scopes from UI. </summary> 109 protected bool hideEmptyScopes = true; 110 111 /// <summary> 112 /// Helper function to build a list of sampler widgets for display stats 113 /// </summary> 114 /// <param name="title">Title for the stats list foldout</param> 115 /// <param name="samplers">List of samplers to display</param> 116 /// <returns>Foldout containing the list of sampler widgets</returns> 117 protected DebugUI.Widget BuildDetailedStatsList(string title, List<TProfileId> samplers) 118 { 119 var foldout = new DebugUI.Foldout(title, BuildProfilingSamplerWidgetList(samplers), k_DetailedStatsColumnLabels); 120 foldout.opened = true; 121 return foldout; 122 } 123 124 private void UpdateListOfAveragedProfilerTimings(bool needUpdatingAverages, List<TProfileId> samplers) 125 { 126 foreach (var samplerId in samplers) 127 { 128 var sampler = ProfilingSampler.Get(samplerId); 129 130 // Accumulate. 131 bool allValuesZero = true; 132 if (m_AccumulatedTiming[(int) DebugProfilingType.CPU].TryGetValue(samplerId, out var accCPUTiming)) 133 { 134 accCPUTiming.accumulatedValue += sampler.cpuElapsedTime; 135 allValuesZero &= accCPUTiming.accumulatedValue == 0; 136 } 137 138 if (m_AccumulatedTiming[(int)DebugProfilingType.InlineCPU].TryGetValue(samplerId, out var accInlineCPUTiming)) 139 { 140 accInlineCPUTiming.accumulatedValue += sampler.inlineCpuElapsedTime; 141 allValuesZero &= accInlineCPUTiming.accumulatedValue == 0; 142 } 143 144 if (m_AccumulatedTiming[(int)DebugProfilingType.GPU].TryGetValue(samplerId, out var accGPUTiming)) 145 { 146 accGPUTiming.accumulatedValue += sampler.gpuElapsedTime; 147 allValuesZero &= accGPUTiming.accumulatedValue == 0; 148 } 149 150 if (needUpdatingAverages) 151 { 152 accCPUTiming?.UpdateLastAverage(m_AccumulatedFrames); 153 accInlineCPUTiming?.UpdateLastAverage(m_AccumulatedFrames); 154 accGPUTiming?.UpdateLastAverage(m_AccumulatedFrames); 155 } 156 157 // Update visibility status based on whether each accumulated value of this scope is zero 158 if (allValuesZero) 159 m_HiddenProfileIds.Add(samplerId); 160 } 161 } 162 163 private float GetSamplerTiming(TProfileId samplerId, ProfilingSampler sampler, DebugProfilingType type) 164 { 165 if (averageProfilerTimingsOverASecond) 166 { 167 // Find the right accumulated dictionary 168 if (m_AccumulatedTiming[(int)type].TryGetValue(samplerId, out AccumulatedTiming accTiming)) 169 return accTiming.lastAverage; 170 } 171 172 return (type == DebugProfilingType.CPU) 173 ? sampler.cpuElapsedTime 174 : ((type == DebugProfilingType.GPU) ? sampler.gpuElapsedTime : sampler.inlineCpuElapsedTime); 175 } 176 177 private ObservableList<DebugUI.Widget> BuildProfilingSamplerWidgetList(IEnumerable<TProfileId> samplers) 178 { 179 var result = new ObservableList<DebugUI.Widget>(); 180 181 DebugUI.Value CreateWidgetForSampler(TProfileId samplerId, ProfilingSampler sampler, 182 DebugProfilingType type) 183 { 184 // Find the right accumulated dictionary and add it there if not existing yet. 185 var accumulatedDictionary = m_AccumulatedTiming[(int)type]; 186 if (!accumulatedDictionary.ContainsKey(samplerId)) 187 { 188 accumulatedDictionary.Add(samplerId, new AccumulatedTiming()); 189 } 190 191 return new() 192 { 193 formatString = "{0:F2}ms", 194 refreshRate = 1.0f / 5.0f, 195 getter = () => GetSamplerTiming(samplerId, sampler, type) 196 }; 197 } 198 199 foreach (var samplerId in samplers) 200 { 201 var sampler = ProfilingSampler.Get(samplerId); 202 203 // In non-dev build ProfilingSampler.Get always returns null. 204 if (sampler == null) 205 continue; 206 207 sampler.enableRecording = true; 208 209 result.Add(new DebugUI.ValueTuple 210 { 211 displayName = sampler.name, 212 isHiddenCallback = () => 213 { 214 if (hideEmptyScopes && m_HiddenProfileIds.Contains(samplerId)) 215 return true; 216 return false; 217 }, 218 values = Enum.GetValues(typeof(DebugProfilingType)).Cast<DebugProfilingType>() 219 .Select(e => CreateWidgetForSampler(samplerId, sampler, e)).ToArray() 220 }); 221 } 222 223 return result; 224 } 225 } 226}