This commit is contained in:
2024-09-20 20:30:10 +02:00
commit 4fabf1a6fd
29169 changed files with 1706941 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
namespace UnityEngine.Rendering
{
/// <summary>
/// Represents a system bottleneck, meaning the factor that is most dominant in determining
/// the total frame time.
/// </summary>
internal enum PerformanceBottleneck
{
Indeterminate, // Cannot be determined
PresentLimited, // Limited by presentation (vsync or framerate cap)
CPU, // Limited by CPU (main and/or render thread)
GPU, // Limited by GPU
Balanced, // Limited by both CPU and GPU, i.e. well balanced
}
/// <summary>
/// BottleneckHistogram represents the distribution of bottlenecks over the Bottleneck History Window,
/// the size of which is determined by <see cref="DebugFrameTiming.bottleneckHistorySize"/>.
/// </summary>
internal struct BottleneckHistogram
{
internal float PresentLimited;
internal float CPU;
internal float GPU;
internal float Balanced;
};
/// <summary>
/// Container class for bottleneck history with helper to calculate histogram.
/// </summary>
internal class BottleneckHistory
{
public BottleneckHistory(int initialCapacity)
{
m_Bottlenecks.Capacity = initialCapacity;
}
List<PerformanceBottleneck> m_Bottlenecks = new();
internal BottleneckHistogram Histogram;
internal void DiscardOldSamples(int historySize)
{
Debug.Assert(historySize > 0, "Invalid sampleHistorySize");
while (m_Bottlenecks.Count >= historySize)
m_Bottlenecks.RemoveAt(0);
m_Bottlenecks.Capacity = historySize;
}
internal void AddBottleneckFromAveragedSample(FrameTimeSample frameHistorySampleAverage)
{
var bottleneck = DetermineBottleneck(frameHistorySampleAverage);
m_Bottlenecks.Add(bottleneck);
}
internal void ComputeHistogram()
{
var stats = new BottleneckHistogram();
for (int i = 0; i < m_Bottlenecks.Count; i++)
{
switch (m_Bottlenecks[i])
{
case PerformanceBottleneck.Balanced:
stats.Balanced++;
break;
case PerformanceBottleneck.CPU:
stats.CPU++;
break;
case PerformanceBottleneck.GPU:
stats.GPU++;
break;
case PerformanceBottleneck.PresentLimited:
stats.PresentLimited++;
break;
}
}
stats.Balanced /= m_Bottlenecks.Count;
stats.CPU /= m_Bottlenecks.Count;
stats.GPU /= m_Bottlenecks.Count;
stats.PresentLimited /= m_Bottlenecks.Count;
Histogram = stats;
}
static PerformanceBottleneck DetermineBottleneck(FrameTimeSample s)
{
const float kNearFullFrameTimeThresholdPercent = 0.2f;
const float kNonZeroPresentWaitTimeMs = 0.5f;
if (s.GPUFrameTime == 0 || s.MainThreadCPUFrameTime == 0) // In direct mode, render thread doesn't exist
return PerformanceBottleneck.Indeterminate; // Missing data
float fullFrameTimeWithMargin = (1f - kNearFullFrameTimeThresholdPercent) * s.FullFrameTime;
// GPU time is close to frame time, CPU times are not
if (s.GPUFrameTime > fullFrameTimeWithMargin &&
s.MainThreadCPUFrameTime < fullFrameTimeWithMargin &&
s.RenderThreadCPUFrameTime < fullFrameTimeWithMargin)
return PerformanceBottleneck.GPU;
// One of the CPU times is close to frame time, GPU is not
if (s.GPUFrameTime < fullFrameTimeWithMargin &&
(s.MainThreadCPUFrameTime > fullFrameTimeWithMargin ||
s.RenderThreadCPUFrameTime > fullFrameTimeWithMargin))
return PerformanceBottleneck.CPU;
// Main thread waited due to Vsync or target frame rate
if (s.MainThreadCPUPresentWaitTime > kNonZeroPresentWaitTimeMs)
{
// None of the times are close to frame time
if (s.GPUFrameTime < fullFrameTimeWithMargin &&
s.MainThreadCPUFrameTime < fullFrameTimeWithMargin &&
s.RenderThreadCPUFrameTime < fullFrameTimeWithMargin)
return PerformanceBottleneck.PresentLimited;
}
return PerformanceBottleneck.Balanced;
}
internal void Clear()
{
m_Bottlenecks.Clear();
Histogram = new BottleneckHistogram();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 331aa10d229f4c2fb9a9240812fb45d6
timeCreated: 1629185832

View File

@@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
namespace UnityEngine.Rendering
{
/// <summary>
/// Represents timing data captured from a single frame.
/// </summary>
internal struct FrameTimeSample
{
internal float FramesPerSecond;
internal float FullFrameTime;
internal float MainThreadCPUFrameTime;
internal float MainThreadCPUPresentWaitTime;
internal float RenderThreadCPUFrameTime;
internal float GPUFrameTime;
internal FrameTimeSample(float initValue)
{
FramesPerSecond = initValue;
FullFrameTime = initValue;
MainThreadCPUFrameTime = initValue;
MainThreadCPUPresentWaitTime = initValue;
RenderThreadCPUFrameTime = initValue;
GPUFrameTime = initValue;
}
};
/// <summary>
/// Container class for sample history with helpers to calculate min, max and average in one pass.
/// </summary>
class FrameTimeSampleHistory
{
public FrameTimeSampleHistory(int initialCapacity)
{
m_Samples.Capacity = initialCapacity;
}
List<FrameTimeSample> m_Samples = new();
internal FrameTimeSample SampleAverage;
internal FrameTimeSample SampleMin;
internal FrameTimeSample SampleMax;
internal void Add(FrameTimeSample sample)
{
m_Samples.Add(sample);
}
// Helper functions
static Func<float, float, float> s_SampleValueAdd = (float value, float other) =>
{
return value + other;
};
static Func<float, float, float> s_SampleValueMin = (float value, float other) =>
{
return other > 0 ? Mathf.Min(value, other) : value;
};
static Func<float, float, float> s_SampleValueMax = (float value, float other) =>
{
return Mathf.Max(value, other);
};
static Func<float, float, float> s_SampleValueCountValid = (float value, float other) =>
{
return other > 0 ? value + 1 : value;
};
static Func<float, float, float> s_SampleValueEnsureValid = (float value, float other) =>
{
return other > 0 ? value : 0;
};
static Func<float, float, float> s_SampleValueDivide = (float value, float other) =>
{
return other > 0 ? value / other : 0;
};
internal void ComputeAggregateValues()
{
void ForEachSampleMember(ref FrameTimeSample aggregate, FrameTimeSample sample, Func<float, float, float> func)
{
aggregate.FramesPerSecond = func(aggregate.FramesPerSecond, sample.FramesPerSecond);
aggregate.FullFrameTime = func(aggregate.FullFrameTime, sample.FullFrameTime);
aggregate.MainThreadCPUFrameTime = func(aggregate.MainThreadCPUFrameTime, sample.MainThreadCPUFrameTime);
aggregate.MainThreadCPUPresentWaitTime = func(aggregate.MainThreadCPUPresentWaitTime, sample.MainThreadCPUPresentWaitTime);
aggregate.RenderThreadCPUFrameTime = func(aggregate.RenderThreadCPUFrameTime, sample.RenderThreadCPUFrameTime);
aggregate.GPUFrameTime = func(aggregate.GPUFrameTime, sample.GPUFrameTime);
};
FrameTimeSample average = new();
FrameTimeSample min = new(float.MaxValue);
FrameTimeSample max = new(float.MinValue);
FrameTimeSample numValidSamples = new(); // Using the struct to record how many valid samples each field has
for (int i = 0; i < m_Samples.Count; i++)
{
var s = m_Samples[i];
ForEachSampleMember(ref min, s, s_SampleValueMin);
ForEachSampleMember(ref max, s, s_SampleValueMax);
ForEachSampleMember(ref average, s, s_SampleValueAdd);
ForEachSampleMember(ref numValidSamples, s, s_SampleValueCountValid);
}
ForEachSampleMember(ref min, numValidSamples, s_SampleValueEnsureValid);
ForEachSampleMember(ref max, numValidSamples, s_SampleValueEnsureValid);
ForEachSampleMember(ref average, numValidSamples, s_SampleValueDivide);
SampleAverage = average;
SampleMin = min;
SampleMax = max;
}
internal void DiscardOldSamples(int sampleHistorySize)
{
Debug.Assert(sampleHistorySize > 0, "Invalid sampleHistorySize");
while (m_Samples.Count >= sampleHistorySize)
m_Samples.RemoveAt(0);
m_Samples.Capacity = sampleHistorySize;
}
internal void Clear()
{
m_Samples.Clear();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d7cc2267acce47fdae2ece838f34df48
timeCreated: 1629185717