227 lines
8.8 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|