Rasagar/Library/PackageCache/com.unity.render-pipelines.core/Runtime/Debugging/DebugDisplayStats.cs
2024-08-26 23:07:20 +03:00

227 lines
8.8 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
namespace UnityEngine.Rendering
{
/// <summary>
/// Base class for Rendering Debugger Display Stats.
/// </summary>
/// <typeparam name="TProfileId">Type of ProfileId the pipeline uses</typeparam>
public abstract class DebugDisplayStats<TProfileId> where TProfileId : Enum
{
// Accumulate values to avg over one second.
private class AccumulatedTiming
{
public float accumulatedValue = 0;
public float lastAverage = 0;
internal void UpdateLastAverage(int frameCount)
{
lastAverage = accumulatedValue / frameCount;
accumulatedValue = 0.0f;
}
}
private enum DebugProfilingType
{
CPU,
InlineCPU,
GPU
}
/// <summary>
/// Enable profiling recorders.
/// </summary>
public abstract void EnableProfilingRecorders();
/// <summary>
/// Disable all active profiling recorders.
/// </summary>
public abstract void DisableProfilingRecorders();
/// <summary>
/// Add display stats widgets to the list provided.
/// </summary>
/// <param name="list">List to add the widgets to.</param>
public abstract void RegisterDebugUI(List<DebugUI.Widget> list);
/// <summary>
/// Update the timing data displayed in Display Stats panel.
/// </summary>
public abstract void Update();
/// <summary>
/// Helper function to get all TProfilerId values of a given type to show in Detailed Stats section.
/// </summary>
/// <returns>List of TProfileId values excluding ones marked with [HideInDebugUI]</returns>
protected List<TProfileId> GetProfilerIdsToDisplay()
{
List<TProfileId> ids = new();
var type = typeof(TProfileId);
var enumValues = Enum.GetValues(type);
foreach (var enumValue in enumValues)
{
var memberInfos = type.GetMember(enumValue.ToString());
var enumValueMemberInfo = memberInfos.First(m => m.DeclaringType == type);
var hiddenAttribute = Attribute.GetCustomAttribute(enumValueMemberInfo, typeof(HideInDebugUIAttribute));
if (hiddenAttribute == null)
ids.Add((TProfileId)enumValue);
}
return ids;
}
/// <summary>
/// Update the detailed stats
/// </summary>
/// <param name="samplers">List of samplers to update</param>
protected void UpdateDetailedStats(List<TProfileId> samplers)
{
m_HiddenProfileIds.Clear();
m_TimeSinceLastAvgValue += Time.unscaledDeltaTime;
m_AccumulatedFrames++;
bool needUpdatingAverages = m_TimeSinceLastAvgValue >= k_AccumulationTimeInSeconds;
UpdateListOfAveragedProfilerTimings(needUpdatingAverages, samplers);
if (needUpdatingAverages)
{
m_TimeSinceLastAvgValue = 0.0f;
m_AccumulatedFrames = 0;
}
}
private static readonly string[] k_DetailedStatsColumnLabels = {"CPU", "CPUInline", "GPU"};
private Dictionary<TProfileId, AccumulatedTiming>[] m_AccumulatedTiming = { new(), new(), new() };
private float m_TimeSinceLastAvgValue = 0.0f;
private int m_AccumulatedFrames = 0;
private HashSet<TProfileId> m_HiddenProfileIds = new();
private const float k_AccumulationTimeInSeconds = 1.0f;
/// <summary> Whether to display timings averaged over a second instead of updating every frame. </summary>
protected bool averageProfilerTimingsOverASecond = false;
/// <summary> Whether to hide empty scopes from UI. </summary>
protected bool hideEmptyScopes = true;
/// <summary>
/// Helper function to build a list of sampler widgets for display stats
/// </summary>
/// <param name="title">Title for the stats list foldout</param>
/// <param name="samplers">List of samplers to display</param>
/// <returns>Foldout containing the list of sampler widgets</returns>
protected DebugUI.Widget BuildDetailedStatsList(string title, List<TProfileId> samplers)
{
var foldout = new DebugUI.Foldout(title, BuildProfilingSamplerWidgetList(samplers), k_DetailedStatsColumnLabels);
foldout.opened = true;
return foldout;
}
private void UpdateListOfAveragedProfilerTimings(bool needUpdatingAverages, List<TProfileId> samplers)
{
foreach (var samplerId in samplers)
{
var sampler = ProfilingSampler.Get(samplerId);
// Accumulate.
bool allValuesZero = true;
if (m_AccumulatedTiming[(int) DebugProfilingType.CPU].TryGetValue(samplerId, out var accCPUTiming))
{
accCPUTiming.accumulatedValue += sampler.cpuElapsedTime;
allValuesZero &= accCPUTiming.accumulatedValue == 0;
}
if (m_AccumulatedTiming[(int)DebugProfilingType.InlineCPU].TryGetValue(samplerId, out var accInlineCPUTiming))
{
accInlineCPUTiming.accumulatedValue += sampler.inlineCpuElapsedTime;
allValuesZero &= accInlineCPUTiming.accumulatedValue == 0;
}
if (m_AccumulatedTiming[(int)DebugProfilingType.GPU].TryGetValue(samplerId, out var accGPUTiming))
{
accGPUTiming.accumulatedValue += sampler.gpuElapsedTime;
allValuesZero &= accGPUTiming.accumulatedValue == 0;
}
if (needUpdatingAverages)
{
accCPUTiming?.UpdateLastAverage(m_AccumulatedFrames);
accInlineCPUTiming?.UpdateLastAverage(m_AccumulatedFrames);
accGPUTiming?.UpdateLastAverage(m_AccumulatedFrames);
}
// Update visibility status based on whether each accumulated value of this scope is zero
if (allValuesZero)
m_HiddenProfileIds.Add(samplerId);
}
}
private float GetSamplerTiming(TProfileId samplerId, ProfilingSampler sampler, DebugProfilingType type)
{
if (averageProfilerTimingsOverASecond)
{
// Find the right accumulated dictionary
if (m_AccumulatedTiming[(int)type].TryGetValue(samplerId, out AccumulatedTiming accTiming))
return accTiming.lastAverage;
}
return (type == DebugProfilingType.CPU)
? sampler.cpuElapsedTime
: ((type == DebugProfilingType.GPU) ? sampler.gpuElapsedTime : sampler.inlineCpuElapsedTime);
}
private ObservableList<DebugUI.Widget> BuildProfilingSamplerWidgetList(IEnumerable<TProfileId> samplers)
{
var result = new ObservableList<DebugUI.Widget>();
DebugUI.Value CreateWidgetForSampler(TProfileId samplerId, ProfilingSampler sampler,
DebugProfilingType type)
{
// Find the right accumulated dictionary and add it there if not existing yet.
var accumulatedDictionary = m_AccumulatedTiming[(int)type];
if (!accumulatedDictionary.ContainsKey(samplerId))
{
accumulatedDictionary.Add(samplerId, new AccumulatedTiming());
}
return new()
{
formatString = "{0:F2}ms",
refreshRate = 1.0f / 5.0f,
getter = () => GetSamplerTiming(samplerId, sampler, type)
};
}
foreach (var samplerId in samplers)
{
var sampler = ProfilingSampler.Get(samplerId);
// In non-dev build ProfilingSampler.Get always returns null.
if (sampler == null)
continue;
sampler.enableRecording = true;
result.Add(new DebugUI.ValueTuple
{
displayName = sampler.name,
isHiddenCallback = () =>
{
if (hideEmptyScopes && m_HiddenProfileIds.Contains(samplerId))
return true;
return false;
},
values = Enum.GetValues(typeof(DebugProfilingType)).Cast<DebugProfilingType>()
.Select(e => CreateWidgetForSampler(samplerId, sampler, e)).ToArray()
});
}
return result;
}
}
}