using System;
using System.Collections.Generic;
using System.Linq;
namespace UnityEngine.Rendering
{
///
/// Base class for Rendering Debugger Display Stats.
///
/// Type of ProfileId the pipeline uses
public abstract class DebugDisplayStats 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
}
///
/// Enable profiling recorders.
///
public abstract void EnableProfilingRecorders();
///
/// Disable all active profiling recorders.
///
public abstract void DisableProfilingRecorders();
///
/// Add display stats widgets to the list provided.
///
/// List to add the widgets to.
public abstract void RegisterDebugUI(List list);
///
/// Update the timing data displayed in Display Stats panel.
///
public abstract void Update();
///
/// Helper function to get all TProfilerId values of a given type to show in Detailed Stats section.
///
/// List of TProfileId values excluding ones marked with [HideInDebugUI]
protected List GetProfilerIdsToDisplay()
{
List 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;
}
///
/// Update the detailed stats
///
/// List of samplers to update
protected void UpdateDetailedStats(List 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[] m_AccumulatedTiming = { new(), new(), new() };
private float m_TimeSinceLastAvgValue = 0.0f;
private int m_AccumulatedFrames = 0;
private HashSet m_HiddenProfileIds = new();
private const float k_AccumulationTimeInSeconds = 1.0f;
/// Whether to display timings averaged over a second instead of updating every frame.
protected bool averageProfilerTimingsOverASecond = false;
/// Whether to hide empty scopes from UI.
protected bool hideEmptyScopes = true;
///
/// Helper function to build a list of sampler widgets for display stats
///
/// Title for the stats list foldout
/// List of samplers to display
/// Foldout containing the list of sampler widgets
protected DebugUI.Widget BuildDetailedStatsList(string title, List samplers)
{
var foldout = new DebugUI.Foldout(title, BuildProfilingSamplerWidgetList(samplers), k_DetailedStatsColumnLabels);
foldout.opened = true;
return foldout;
}
private void UpdateListOfAveragedProfilerTimings(bool needUpdatingAverages, List 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 BuildProfilingSamplerWidgetList(IEnumerable samplers)
{
var result = new ObservableList();
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()
.Select(e => CreateWidgetForSampler(samplerId, sampler, e)).ToArray()
});
}
return result;
}
}
}