Files
2026-11-01 23:39:55 +02:00

173 lines
7.5 KiB
C#

using NUnit.Framework;
namespace UnityEngine.Rendering.Tests
{
static class FrameTimingTestExtensions
{
public static BottleneckHistogram ToHistogram(this FrameTimeSample s)
{
var history = new BottleneckHistory(1);
history.AddBottleneckFromAveragedSample(s);
history.ComputeHistogram();
return history.Histogram;
}
public static string ToDebugString(this BottleneckHistogram h) =>
$"GPU={h.GPU}, CPU={h.CPU}, PresentLimited={h.PresentLimited}, Balanced={h.Balanced}";
}
class DebugFrameTimingTests
{
static FrameTimeSample MakeSample(
float fullFrameTime,
float gpu,
float mainCpu,
float renderCpu,
float presentWait = 0f,
float fps = 0f)
{
return new FrameTimeSample
{
FullFrameTime = fullFrameTime,
GPUFrameTime = gpu,
MainThreadCPUFrameTime = mainCpu,
RenderThreadCPUFrameTime = renderCpu,
MainThreadCPUPresentWaitTime = presentWait,
FramesPerSecond = fps
};
}
[Test]
public void Histogram_ZeroGPUFrameTime_ProducesIndeterminate()
{
var h = MakeSample(fullFrameTime: 10, gpu: 0, mainCpu: 5, renderCpu: 5).ToHistogram();
Assert.That(h.GPU + h.CPU + h.PresentLimited + h.Balanced, Is.EqualTo(0f),
$"Zero GPUFrameTime must classify as Indeterminate (all buckets 0). Got {h.ToDebugString()}.");
}
[Test]
public void Histogram_ZeroMainThreadCPUFrameTime_ProducesIndeterminate()
{
var h = MakeSample(fullFrameTime: 10, gpu: 5, mainCpu: 0, renderCpu: 5).ToHistogram();
Assert.That(h.GPU + h.CPU + h.PresentLimited + h.Balanced, Is.EqualTo(0f),
$"Zero MainThreadCPUFrameTime must classify as Indeterminate (all buckets 0). Got {h.ToDebugString()}.");
}
[Test]
public void Histogram_GPUBound_ProducesGPU()
{
// GPU close to FullFrameTime, CPU times are not.
var h = MakeSample(fullFrameTime: 10, gpu: 9, mainCpu: 2, renderCpu: 2).ToHistogram();
Assert.That(h.GPU, Is.EqualTo(1f),
$"GPU-dominant sample must put 100% in the GPU bucket. Got {h.ToDebugString()}.");
}
[Test]
public void Histogram_MainThreadCPUBound_ProducesCPU()
{
var h = MakeSample(fullFrameTime: 10, gpu: 2, mainCpu: 9, renderCpu: 2).ToHistogram();
Assert.That(h.CPU, Is.EqualTo(1f),
$"MainThread-dominant sample must put 100% in the CPU bucket. Got {h.ToDebugString()}.");
}
[Test]
public void Histogram_RenderThreadCPUBound_ProducesCPU()
{
var h = MakeSample(fullFrameTime: 10, gpu: 2, mainCpu: 2, renderCpu: 9).ToHistogram();
Assert.That(h.CPU, Is.EqualTo(1f),
$"RenderThread-dominant sample must put 100% in the CPU bucket. Got {h.ToDebugString()}.");
}
[Test]
public void Histogram_BalancedWithPresentWait_ProducesPresentLimited()
{
var h = MakeSample(fullFrameTime: 10, gpu: 2, mainCpu: 2, renderCpu: 2, presentWait: 1f).ToHistogram();
Assert.That(h.PresentLimited, Is.EqualTo(1f),
$"Non-dominant sample with present wait > 0.5ms must classify as PresentLimited. Got {h.ToDebugString()}.");
}
[Test]
public void Histogram_BalancedWithoutPresentWait_ProducesBalanced()
{
var h = MakeSample(fullFrameTime: 10, gpu: 5, mainCpu: 5, renderCpu: 5, presentWait: 0f).ToHistogram();
Assert.That(h.Balanced, Is.EqualTo(1f),
$"Non-dominant sample with no present wait must classify as Balanced. Got {h.ToDebugString()}.");
}
[Test]
public void Histogram_MixedSamples_RatiosMatchSampleCounts()
{
// 2x GPU, 2x CPU, 1x PresentLimited -> 0.4 / 0.4 / 0.2 / 0
var history = new BottleneckHistory(8);
history.AddBottleneckFromAveragedSample(MakeSample(10, 9, 2, 2));
history.AddBottleneckFromAveragedSample(MakeSample(10, 9, 2, 2));
history.AddBottleneckFromAveragedSample(MakeSample(10, 2, 9, 2));
history.AddBottleneckFromAveragedSample(MakeSample(10, 2, 9, 2));
history.AddBottleneckFromAveragedSample(MakeSample(10, 2, 2, 2, presentWait: 1f));
history.ComputeHistogram();
var h = history.Histogram;
Assert.That(h.GPU, Is.EqualTo(0.4f).Within(1e-6f), $"GPU bucket. Got {h.ToDebugString()}.");
Assert.That(h.CPU, Is.EqualTo(0.4f).Within(1e-6f), $"CPU bucket. Got {h.ToDebugString()}.");
Assert.That(h.PresentLimited, Is.EqualTo(0.2f).Within(1e-6f), $"PresentLimited bucket. Got {h.ToDebugString()}.");
Assert.That(h.Balanced, Is.EqualTo(0f), $"Balanced bucket. Got {h.ToDebugString()}.");
}
[Test]
public void Histogram_IndeterminateSamples_ReduceVisibleTotalBelowOne()
{
// 1 valid GPU sample + 4 indeterminate (GPU=0) -> visible total = 0.2.
var history = new BottleneckHistory(8);
history.AddBottleneckFromAveragedSample(MakeSample(10, 9, 2, 2));
for (int i = 0; i < 4; i++)
history.AddBottleneckFromAveragedSample(MakeSample(10, 0, 5, 5));
history.ComputeHistogram();
var h = history.Histogram;
Assert.That(h.GPU, Is.EqualTo(0.2f).Within(1e-6f), $"GPU bucket. Got {h.ToDebugString()}.");
Assert.That(h.GPU + h.CPU + h.PresentLimited + h.Balanced, Is.EqualTo(0.2f).Within(1e-6f),
$"Indeterminate samples must be excluded from visible buckets. Got {h.ToDebugString()}.");
}
[Test]
public void BottleneckHistory_DiscardOldSamples_KeepsAtMostHistorySize()
{
// With historySize=3, add 5 GPU-bound then 2 CPU-bound samples.
// Retained samples should be [GPU, CPU, CPU] -> GPU=1/3, CPU=2/3.
const int historySize = 3;
var history = new BottleneckHistory(historySize);
var gpuSample = MakeSample(10, 9, 2, 2);
var cpuSample = MakeSample(10, 2, 9, 2);
for (int i = 0; i < 5; i++)
{
history.DiscardOldSamples(historySize);
history.AddBottleneckFromAveragedSample(gpuSample);
}
for (int i = 0; i < 2; i++)
{
history.DiscardOldSamples(historySize);
history.AddBottleneckFromAveragedSample(cpuSample);
}
history.ComputeHistogram();
var h = history.Histogram;
Assert.That(h.GPU, Is.EqualTo(1f / 3f).Within(1e-6f), $"Only 3 newest samples should be retained. Got {h.ToDebugString()}.");
Assert.That(h.CPU, Is.EqualTo(2f / 3f).Within(1e-6f), $"Only 3 newest samples should be retained. Got {h.ToDebugString()}.");
}
[Test]
public void Reset_ClearsBottleneckHistogram()
{
var debug = new DebugFrameTiming();
debug.m_BottleneckHistory.AddBottleneckFromAveragedSample(MakeSample(10, 9, 2, 2));
debug.m_BottleneckHistory.ComputeHistogram();
debug.Reset();
var h = debug.m_BottleneckHistory.Histogram;
Assert.That(h.GPU + h.CPU + h.PresentLimited + h.Balanced, Is.EqualTo(0f), $"Reset must zero the histogram. Got {h.ToDebugString()}.");
}
}
}