Adaug proiectul Unity

This commit is contained in:
2026-11-01 23:39:55 +02:00
parent cb14d9d37d
commit 863eac8f2c
23463 changed files with 5412601 additions and 0 deletions
@@ -0,0 +1,5 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Unity.RenderPipelines.Core.Editor.Tests")]
[assembly: InternalsVisibleTo("SRPSmoke.Runtime.Tests")]
[assembly: InternalsVisibleTo("SRPSmoke.Editor.Tests")]
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 61de5e80a5bf01f40968ac86d656dd62
@@ -0,0 +1,214 @@
using NUnit.Framework;
using System;
using System.Collections.Generic;
using UnityEngine.TestTools.Constraints;
using Is = UnityEngine.TestTools.Constraints.Is;
namespace UnityEngine.Rendering.Tests
{
class TestData : ContextItem
{
public int x;
public float y;
public bool z;
public override void Reset()
{
x = 0;
y = 0f;
// Reuse z without clearing.
}
}
class OtherTestData : ContextItem
{
public List<int> list = new();
public override void Reset()
{
for (int i = 0; i < list.Count; i++)
{
list[i] = 0;
}
}
}
class ContextContainerTests
{
ContextContainer m_container = new();
[OneTimeSetUp]
public void FirstCreationOfData()
{
CreateMemoryAlloc();
m_container.Dispose();
}
[SetUp]
public void SetUp()
{
m_container.Dispose();
TestData data = m_container.Create<TestData>();
data.x = 0;
data.y = 0f;
data.z = false;
OtherTestData other = m_container.Create<OtherTestData>();
other.list.Clear();
m_container.Dispose();
}
[Test]
public void ReuseData()
{
{
TestData data = CreateTestData();
Assert.That(data.x, Is.EqualTo(0));
Assert.That(data.y, Is.EqualTo(0f));
Assert.That(data.z, Is.EqualTo(false));
data.x = -23;
data.y = 8.4f;
data.z = true;
m_container.Dispose();
}
{
TestData data = CreateTestData();
Assert.That(data.x, Is.EqualTo(0));
Assert.That(data.y, Is.EqualTo(0f));
Assert.That(data.z, Is.EqualTo(true));
m_container.Dispose();
}
}
[Test]
public void GetData()
{
Assert.Throws<InvalidOperationException>(() => m_container.Get<TestData>());
{
TestData data = CreateTestData();
Assert.That(data.x, Is.EqualTo(0));
Assert.That(data.y, Is.EqualTo(0f));
Assert.That(data.z, Is.EqualTo(false));
data.x = -23;
data.y = 8.4f;
data.z = true;
}
{
TestData data = m_container.Get<TestData>();
Assert.That(data.x, Is.EqualTo(-23));
Assert.That(data.y, Is.EqualTo(8.4f));
Assert.That(data.z, Is.EqualTo(true));
m_container.Dispose();
}
Assert.Throws<InvalidOperationException>(() => m_container.Get<TestData>());
}
[Test]
public void ContainsData()
{
// Initially does not container TestData:
Assert.False(m_container.Contains<TestData>());
Assert.Throws<InvalidOperationException>(() => m_container.Get<TestData>());
// Create TestData without error and the container should now contain TestData:
Assert.DoesNotThrow(() => CreateTestData());
Assert.True(m_container.Contains<TestData>());
// Create again should throw error:
Assert.Throws<InvalidOperationException>(() => CreateTestData());
Assert.True(m_container.Contains<TestData>());
Assert.DoesNotThrow(() => m_container.Get<TestData>());
// Clear the container from created items, TestData should no longer be contained:
m_container.Dispose();
Assert.False(m_container.Contains<TestData>());
Assert.Throws<InvalidOperationException>(() => m_container.Get<TestData>());
// GetOrCreate should create should set contains to true without error:
Assert.DoesNotThrow(() => GetOrCreateTestData());
Assert.True(m_container.Contains<TestData>());
Assert.DoesNotThrow(() => m_container.Get<TestData>());
// GetOrCreate should get without error and contains should still be true:
Assert.DoesNotThrow(() => GetOrCreateTestData());
Assert.True(m_container.Contains<TestData>());
Assert.DoesNotThrow(() => m_container.Get<TestData>());
}
[Test]
public void ReuseList()
{
{
OtherTestData other = CreateOtherTestData();
Assert.That(other.list, Is.EqualTo(new List<int>()));
other.list.Add(4);
other.list.Add(8);
other.list.Add(3);
other.list.Add(-7);
}
{
OtherTestData other = m_container.Get<OtherTestData>();
Assert.That(other.list, Is.EqualTo(new List<int>() { 4, 8, 3, -7 }));
m_container.Dispose();
}
{
OtherTestData other = CreateOtherTestData();
Assert.That(other.list, Is.EqualTo(new List<int>() { 0, 0, 0, 0 }));
}
}
// Need to have one create location to avoid debug allocations.
TestData CreateTestData()
{
return m_container.Create<TestData>();
}
// Need to have one create location to avoid debug allocations.
TestData GetOrCreateTestData()
{
return m_container.GetOrCreate<TestData>();
}
// Need to have one create location to avoid debug allocations.
OtherTestData CreateOtherTestData()
{
return m_container.Create<OtherTestData>();
}
#if CONTEXT_CONTAINER_ALLOCATOR_DEBUG
public void CreateMemoryAlloc()
{
//Alloc for Create TestData
Assert.That(() => { CreateTestData(); }, Is.AllocatingGCMemory());
m_container.Dispose();
Assert.That(() => { CreateTestData(); }, Is.Not.AllocatingGCMemory());
m_container.Dispose();
Assert.That(() => { GetOrCreateTestData(); }, Is.AllocatingGCMemory());
m_container.Dispose();
Assert.That(() => { GetOrCreateTestData(); }, Is.Not.AllocatingGCMemory());
//Alloc for Create OtherTestData
Assert.That(() => { CreateOtherTestData(); }, Is.AllocatingGCMemory());
m_container.Dispose();
Assert.That(() => { CreateOtherTestData(); }, Is.Not.AllocatingGCMemory());
}
#else
public void CreateMemoryAlloc()
{
//TestData
Assert.That(() => { CreateTestData(); }, Is.AllocatingGCMemory());
m_container.Dispose();
Assert.That(() => { GetOrCreateTestData(); }, Is.Not.AllocatingGCMemory());
m_container.Dispose();
Assert.That(() => { m_container.Create<TestData>(); }, Is.Not.AllocatingGCMemory());
//OtherTestData
Assert.That(() => { CreateOtherTestData(); }, Is.AllocatingGCMemory());
m_container.Dispose();
Assert.That(() => { m_container.Create<OtherTestData>(); }, Is.Not.AllocatingGCMemory());
}
#endif
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ff33f91d01e0f9f4d90c5619e5e2b70e
@@ -0,0 +1,19 @@
using UnityEngine;
using UnityEngine.Rendering;
using System.Collections.Generic;
namespace UnityEngine.Rendering.Tests
{
class CustomRenderPipelineAsset : RenderPipelineAsset<CustomRenderPipeline>
{
protected override RenderPipeline CreatePipeline()
=> new CustomRenderPipeline();
}
class CustomRenderPipeline : RenderPipeline
{
protected override void Render(ScriptableRenderContext context, List<Camera> cameras)
{
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b1ba908863884e868165e402aeb87de7
timeCreated: 1681827727
@@ -0,0 +1,172 @@
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()}.");
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3db890d1d31f2e8469e35a12ff97e3fe
@@ -0,0 +1,27 @@
using System;
using NUnit.Framework;
using UnityEngine.Rendering;
class DebugMangerTests
{
[Test]
public void WindowStateCallbackIsTriggerred()
{
bool called = false;
Action<DebugManager.UIMode, bool> action = (mode, open) =>
{
Assert.AreEqual(DebugManager.UIMode.EditorMode, mode);
Assert.AreEqual(true, open);
called = true;
};
DebugManager.windowStateChanged += action;
DebugManager.instance.displayEditorUI = true;
Assert.AreEqual(true, called);
DebugManager.windowStateChanged -= action;
DebugManager.instance.displayEditorUI = false;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3a191d0939f271d4b9b583b27f1ecd2d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6c7283cd37804624b873fb94203cd307
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,833 @@
#if ENABLE_UIELEMENTS_MODULE && UNITY_ENABLE_CHECKS
#define ENABLE_RENDERING_DEBUGGER_UI
#endif
using System;
using System.Collections.Generic;
using UnityEngine.UIElements;
#if UNITY_EDITOR
using UnityEditor;
#endif
using NameAndTooltip = UnityEngine.Rendering.DebugUI.Widget.NameAndTooltip;
namespace UnityEngine.Rendering.Tests
{
class DebugPanelDisplaySettings : DebugDisplaySettings<DebugPanelDisplaySettings>
{
const string k_PanelName = "Debug Panel";
#if UNITY_EDITOR
[InitializeOnLoadMethod]
public static void RegisterPanel()
{
var data = new DebugPanelDisplaySettings.DebugPanelDisplaySettingsData();
IDebugDisplaySettingsPanelDisposable disposableSettingsPanel = data.CreatePanel();
DebugUI.Widget[] panelWidgets = disposableSettingsPanel.Widgets;
DebugUI.Panel panel = DebugManager.instance.GetPanel(
displayName: disposableSettingsPanel.PanelName,
createIfNull: true,
groupIndex: (disposableSettingsPanel is DebugDisplaySettingsPanel debugDisplaySettingsPanel) ? debugDisplaySettingsPanel.Order : 0);
if (DocumentationUtils.TryGetHelpURL(disposableSettingsPanel.GetType(), out var documentationUrl))
panel.documentationUrl = documentationUrl;
ObservableList<DebugUI.Widget> panelChildren = panel.children;
panel.flags = disposableSettingsPanel.Flags;
panelChildren.Add(panelWidgets);
}
#endif
public class CustomProgressBarField : DebugUI.Field<float>
{
public Func<float> min = () => 0f;
public Func<float> max = () => 100f;
public string unit = "%";
public Color lowColor = Color.red;
public Color highColor = Color.green;
public bool isEditable = true;
#if ENABLE_RENDERING_DEBUGGER_UI
protected override VisualElement Create()
{
var container = new VisualElement();
container.AddToClassList("debug-progress-bar-container");
container.AddToClassList(UIElements.BaseField<float>.ussClassName);
container.AddToClassList("unity-inspector-element");
container.AddToClassList("unity-base-field__inspector-field");
container.style.flexDirection = FlexDirection.Row;
var label = new Label(displayName);
label.AddToClassList(UIElements.BaseField<float>.labelUssClassName);
container.Add(label);
var progressContainer = new VisualElement();
progressContainer.style.height = 20;
progressContainer.style.backgroundColor = new Color(0.2f, 0.2f, 0.2f);
progressContainer.style.borderBottomLeftRadius = 4;
progressContainer.style.borderBottomRightRadius = 4;
progressContainer.style.borderTopLeftRadius = 4;
progressContainer.style.borderTopRightRadius = 4;
progressContainer.style.flexGrow = 1;
progressContainer.AddToClassList(UIElements.BaseField<float>.inputUssClassName);
var progressBar = new VisualElement();
progressBar.style.backgroundColor = Color.green;
progressBar.style.height = Length.Percent(100);
var valueLabel = new Label();
valueLabel.style.position = Position.Absolute;
valueLabel.style.alignSelf = Align.Center;
valueLabel.style.color = Color.white;
progressContainer.Add(progressBar);
progressContainer.Add(valueLabel);
container.Add(progressContainer);
// Update every frame
container.schedule.Execute(() =>
{
float value = GetValue();
float normalized = Mathf.InverseLerp(min(), max(), value);
progressBar.style.width = Length.Percent(normalized * 100);
progressBar.style.backgroundColor = Color.Lerp(lowColor, highColor, normalized);
valueLabel.text = $"{value:F1}{unit}";
}).Every(16); // ~60fps
// Make the progress bar interactive
if (isEditable && !m_Context.IsAnyRuntimeContext())
{
progressContainer.style.cursor = StyleKeyword.Auto;
// Register click/drag handlers
progressContainer.RegisterCallback<PointerDownEvent>(evt =>
{
UpdateValueFromPosition(evt.localPosition.x, progressContainer.resolvedStyle.width);
progressContainer.CapturePointer(evt.pointerId);
evt.StopPropagation();
});
progressContainer.RegisterCallback<PointerMoveEvent>(evt =>
{
if (progressContainer.HasPointerCapture(evt.pointerId))
{
UpdateValueFromPosition(evt.localPosition.x, progressContainer.resolvedStyle.width);
evt.StopPropagation();
}
});
progressContainer.RegisterCallback<PointerUpEvent>(evt =>
{
if (progressContainer.HasPointerCapture(evt.pointerId))
{
progressContainer.ReleasePointer(evt.pointerId);
evt.StopPropagation();
}
});
progressContainer.RegisterCallback<PointerCancelEvent>(evt =>
{
if (progressContainer.HasPointerCapture(evt.pointerId))
{
progressContainer.ReleasePointer(evt.pointerId);
}
});
}
container.AddToClassList(UIElements.BaseField<float>.alignedFieldUssClassName);
return container;
}
#endif
private void UpdateValueFromPosition(float localX, float width)
{
if (width <= 0) return;
float normalized = Mathf.Clamp01(localX / width);
float minValue = min();
float maxValue = max();
float newValue = Mathf.Lerp(minValue, maxValue, normalized);
SetValue(newValue);
}
}
public class DebugPanelDisplaySettingsData : IDebugDisplaySettingsData, ISerializedDebugDisplaySettings
{
#region IDebugDisplaySettingsData
public bool AreAnySettingsActive => false;
public IDebugDisplaySettingsPanelDisposable CreatePanel()
{
return new SettingsPanel(this);
}
#endregion
// Numbers
public int intField { get; set; } = 13;
public int intMinMaxField { get; set; } = 13;
public uint uintField { get; set; } = 23;
public uint uintMinMaxField { get; set; } = 23;
public float floatField { get; set; } = 7.7f;
public float floatMinMaxField { get; set; } = 7.7f;
// Bools
public bool boolField { get; set; } = true;
public bool historyBoolField1 { get; set; } = true;
public bool historyBoolField2 { get; set; } = true;
public bool historyBoolField3 { get; set; } = true;
// Enums
public enum EnumValues
{
None = 0,
TypeA, TypeB, TypeC,
}
public EnumValues enumField { get; set; }
public EnumValues enumField1 { get; set; }
public EnumValues enumField2 { get; set; }
public EnumValues enumField3 { get; set; }
[Flags]
public enum BitFlags
{
None,
FlagA = 0x1,
FlagB = 0x2,
FlagC = 0x4,
FlagD = 0x8
}
public BitFlags bitField { get; set; }
// Vectors
public Color colorField { get; set; } = Color.darkMagenta;
public Vector2 vector2Field { get; set; }
public Vector3 vector3Field { get; set; }
public Vector4 vector4Field { get; set; }
// Object
public Object objectField { get; set; }
public Object[] objectListField { get; set; } = new Object[] { Camera.main, Camera.main, Camera.main };
public Object objectPopupField { get; set; }
public float value1 { get; set; } = 1.123456f;
public float value2 { get; set; } = 2.123456f;
public float value3 { get; set; } = 3.123456f;
public Camera camerasPopupField { get; set; }
public uint renderingLayersField { get; set; } = 0;
public Vector4[] renderingLayersColors = new Vector4[]
{
new Vector4(230, 159, 0) / 255,
new Vector4(86, 180, 233) / 255,
new Vector4(255, 182, 291) / 255,
new Vector4(0, 158, 115) / 255,
new Vector4(240, 228, 66) / 255,
new Vector4(0, 114, 178) / 255,
new Vector4(213, 94, 0) / 255,
new Vector4(170, 68, 170) / 255,
new Vector4(1.0f, 0.5f, 0.5f),
new Vector4(0.5f, 1.0f, 0.5f),
new Vector4(0.5f, 0.5f, 1.0f),
new Vector4(0.5f, 1.0f, 1.0f),
new Vector4(0.75f, 0.25f, 1.0f),
new Vector4(0.25f, 1.0f, 0.75f),
new Vector4(0.25f, 0.25f, 0.75f),
new Vector4(0.75f, 0.25f, 0.25f),
};
const int k_NumRows = 10;
const int k_NumColumns = 3;
public float customProgress { get; set; } = 13;
static class Strings
{
public static readonly NameAndTooltip IntField = new() { name = "Int Field", tooltip = "Debug Field for number int type." };
public static readonly NameAndTooltip IntMinMaxField = new() { name = "Int Min Max Field", tooltip = "Debug Field for number int type with min-max value." };
public static readonly NameAndTooltip UIntField = new() { name = "UInt Field", tooltip = "Debug Field for number uint type." };
public static readonly NameAndTooltip UIntMinMaxField = new() { name = "UInt Min Max Field", tooltip = "Debug Field for number uint type with min-max value." };
public static readonly NameAndTooltip FloatField = new() { name = "Float Field", tooltip = "Debug Field for number float type." };
public static readonly NameAndTooltip FloatMinMaxField = new() { name = "Float Min Max Field", tooltip = "Debug Field for number float type with min-max value." };
public static readonly NameAndTooltip BoolField = new() { name = "Bool Field", tooltip = "Debug Field for bool value." };
public static readonly NameAndTooltip HistoryBoolField = new() { name = "History Bool Field", tooltip = "Debug Field for history bool value." };
public static readonly NameAndTooltip EnumField = new() { name = "Enum Field", tooltip = "Debug Field for enum value." };
public static readonly NameAndTooltip HistoryEnumField = new() { name = "History Enum Field", tooltip = "Debug Field for history enum value." };
public static readonly NameAndTooltip BitField = new() { name = "Bit Field", tooltip = "Debug Field for bit field value." };
public static readonly NameAndTooltip ColorField = new() { name = "Color Field", tooltip = "Debug Field for Color field value." };
public static readonly NameAndTooltip Vector2Field = new() { name = "Vector2 Field", tooltip = "Debug Field for Vector2 field value." };
public static readonly NameAndTooltip Vector3Field = new() { name = "Vector3 Field", tooltip = "Debug Field for Vector3 field value." };
public static readonly NameAndTooltip Vector4Field = new() { name = "Vector4 Field", tooltip = "Debug Field for Vector4 field value." };
public static readonly NameAndTooltip ObjectField = new() { name = "Object Field", tooltip = "Debug Field for Object field value." };
public static readonly NameAndTooltip ObjectListField = new() { name = "Object List Field", tooltip = "Debug Field for Object List field value." };
public static readonly NameAndTooltip ObjectPopupField = new() { name = "Object Popup Field", tooltip = "Debug Field for Object Popup field value." };
public static readonly NameAndTooltip Value = new() { name = "Value", tooltip = "Value." };
public static readonly NameAndTooltip ValueTuple = new() { name = "Value Tuple", tooltip = "Value Tuple." };
public static readonly NameAndTooltip CamerasPopupField = new() { name = "Cameras Popup Field", tooltip = "Debug Field for Cameras Popup field value." };
public static readonly NameAndTooltip RenderingLayersField = new() { name = "Rendering Layers Field", tooltip = "Debug Field for Rendering Layers field value." };
public static readonly NameAndTooltip MessageBox = new() { name = "Message Box", tooltip = "Message Box." };
public static readonly NameAndTooltip DebugTable = new() { name = "Pollitos", tooltip = "Table." };
public static readonly NameAndTooltip Button = new() { name = "Button", tooltip = "Button." };
public static readonly NameAndTooltip ProgressBar = new() { name = "ProgressBar", tooltip = "ProgressBar." };
public static readonly NameAndTooltip NumberFields = new() { name = "Hidden", tooltip = "Hidden fields." };
public static readonly string TableRow = "Row";
}
internal static class WidgetFactory
{
internal static DebugUI.Widget CreateIntField(DebugPanelDisplaySettingsData data) => new DebugUI.IntField
{
nameAndTooltip = Strings.IntField,
getter = () => (int)data.intField,
setter = (value) => data.intField = value,
incStep = 1,
incStepMult = 10
};
internal static DebugUI.Widget CreateIntMinMaxField(DebugPanelDisplaySettingsData data) => new DebugUI.IntField
{
nameAndTooltip = Strings.IntMinMaxField,
getter = () => (int)data.intMinMaxField,
setter = (value) => data.intMinMaxField = value,
incStep = 1,
incStepMult = 10,
min = () => -100,
max = () => 100
};
internal static DebugUI.Widget CreateUIntField(DebugPanelDisplaySettingsData data) => new DebugUI.UIntField
{
nameAndTooltip = Strings.UIntField,
getter = () => (uint)data.uintField,
setter = (value) => data.uintField = value,
incStep = 1u,
incStepMult = 10u
};
internal static DebugUI.Widget CreateUIntMinMaxField(DebugPanelDisplaySettingsData data) => new DebugUI.UIntField
{
nameAndTooltip = Strings.UIntMinMaxField,
getter = () => (uint)data.uintMinMaxField,
setter = (value) => data.uintMinMaxField = value,
incStep = 1u,
incStepMult = 10u,
min = () => 1u,
max = () => 100u
};
internal static DebugUI.Widget CreateFloatField(DebugPanelDisplaySettingsData data) => new DebugUI.FloatField
{
nameAndTooltip = Strings.FloatField,
getter = () => (float)data.floatField,
setter = (value) => data.floatField = value,
incStep = 1.0f,
incStepMult = 10.0f
};
internal static DebugUI.Widget CreateFloatMinMaxField(DebugPanelDisplaySettingsData data) => new DebugUI.FloatField
{
nameAndTooltip = Strings.FloatMinMaxField,
getter = () => (float)data.floatMinMaxField,
setter = (value) => data.floatMinMaxField = value,
incStep = 1.0f,
incStepMult = 10.0f,
min = () => -100.0f,
max = () => 100.0f
};
internal static DebugUI.Widget CreateNumberHiddenFields(DebugPanelDisplaySettingsData data) => new DebugUI.Container()
{
isHiddenCallback = () => data.intMinMaxField < 0 && data.floatMinMaxField < 0,
displayName = Strings.NumberFields.name,
children =
{
WidgetFactory.CreateIntField(data),
}
};
internal static DebugUI.Widget CreateBoolField(DebugPanelDisplaySettingsData data) => new DebugUI.BoolField
{
nameAndTooltip = Strings.BoolField,
getter = () => (bool)data.boolField,
setter = (value) => data.boolField = value,
};
internal static DebugUI.Widget CreateBoolHiddenFields(DebugPanelDisplaySettingsData data) => new DebugUI.Container()
{
isHiddenCallback = () => data.boolField,
displayName = Strings.NumberFields.name,
children =
{
WidgetFactory.CreateBoolField(data),
}
};
internal static DebugUI.Widget CreateHistoryBoolField(DebugPanelDisplaySettingsData data) => new DebugUI.HistoryBoolField
{
nameAndTooltip = Strings.HistoryBoolField,
getter = () => (bool)data.historyBoolField1,
setter = (value) =>
{
data.historyBoolField1 = value;
data.historyBoolField2 = value;
data.historyBoolField3 = value;
},
historyGetter = new Func<bool>[]
{
() => data.historyBoolField1,
() => data.historyBoolField2,
() => data.historyBoolField3
}
};
internal static DebugUI.Widget CreateEnumField(DebugPanelDisplaySettingsData data) => new DebugUI.EnumField
{
nameAndTooltip = Strings.EnumField,
autoEnum = typeof(EnumValues),
getter = () => (int)data.enumField,
setter = (value) => data.enumField = (EnumValues)value,
getIndex = () => (int)data.enumField,
setIndex = (value) => data.enumField = (EnumValues)value
};
internal static DebugUI.Widget CreateEnumHiddenFields(DebugPanelDisplaySettingsData data) => new DebugUI.Container()
{
isHiddenCallback = () => data.enumField == EnumValues.None,
displayName = Strings.NumberFields.name,
children =
{
WidgetFactory.CreateEnumField(data),
}
};
internal static DebugUI.Widget CreateHistoryEnumField(DebugPanelDisplaySettingsData data) => new DebugUI.HistoryEnumField
{
nameAndTooltip = Strings.HistoryEnumField,
autoEnum = typeof(EnumValues),
getter = () => (int)data.enumField1,
setter = (value) =>
{
data.enumField1 = (EnumValues)value;
data.enumField1 = (EnumValues)value;
data.enumField1 = (EnumValues)value;
},
getIndex = () => (int)data.enumField1,
setIndex = (int a) => { },
historyIndexGetter = new Func<int>[]
{
() => (int)data.enumField1,
() => (int)data.enumField2,
() => (int)data.enumField3
}
};
internal static DebugUI.Widget CreateBitField(DebugPanelDisplaySettingsData data) => new DebugUI.BitField
{
nameAndTooltip = Strings.BitField,
getter = () => data.bitField,
setter = (value) => data.bitField = (BitFlags)value,
enumType = typeof(BitFlags),
};
internal static DebugUI.Widget CreateColorField(DebugPanelDisplaySettingsData data) => new DebugUI.ColorField
{
nameAndTooltip = Strings.ColorField,
getter = () => data.colorField,
setter = (value) => data.colorField = value,
};
internal static DebugUI.Widget CreateVector2Field(DebugPanelDisplaySettingsData data) => new DebugUI.Vector2Field
{
nameAndTooltip = Strings.Vector2Field,
getter = () => data.vector2Field,
setter = (value) => data.vector2Field = value,
};
internal static DebugUI.Widget CreateVector3Field(DebugPanelDisplaySettingsData data) => new DebugUI.Vector3Field
{
nameAndTooltip = Strings.Vector3Field,
getter = () => data.vector3Field,
setter = (value) => data.vector3Field = value,
};
internal static DebugUI.Widget CreateVector4Field(DebugPanelDisplaySettingsData data) => new DebugUI.Vector4Field
{
nameAndTooltip = Strings.Vector4Field,
getter = () => data.vector4Field,
setter = (value) => data.vector4Field = value,
};
internal static DebugUI.Widget CreateObjectField(DebugPanelDisplaySettingsData data) => new DebugUI.ObjectField
{
nameAndTooltip = Strings.ObjectField,
getter = () => data.objectField,
setter = (value) => data.objectField = value
};
internal static DebugUI.Widget CreateObjectListField(DebugPanelDisplaySettingsData data) => new DebugUI.ObjectListField
{
nameAndTooltip = Strings.ObjectListField,
getter = () => data.objectListField,
};
internal static DebugUI.Widget CreateObjectPopupField(DebugPanelDisplaySettingsData data) => new DebugUI.ObjectPopupField
{
nameAndTooltip = Strings.ObjectPopupField,
getter = () => data.objectPopupField,
setter = (value) => data.objectPopupField = value,
getObjects = () =>
{
List<Object> objects = new List<Object>();
return objects;
}
};
internal static DebugUI.Widget CreateValue(DebugPanelDisplaySettingsData data) => new DebugUI.Value
{
nameAndTooltip = Strings.Value,
getter = () => data.value1
};
internal static DebugUI.Widget CreateValueTuple(DebugPanelDisplaySettingsData data) => new DebugUI.ValueTuple
{
nameAndTooltip = Strings.ValueTuple,
values = new[]
{
new DebugUI.Value { refreshRate = 0.1f, formatString = null, getter = () => data.value1 },
new DebugUI.Value { refreshRate = 0.5f, formatString = "{0:F1}", getter = () => data.value2 },
new DebugUI.Value { refreshRate = 1.0f, formatString = "{0:F4}", getter = () => data.value3 },
}
};
internal static DebugUI.Widget CreateCamerasField(DebugPanelDisplaySettingsData data) => new DebugUI.CameraSelector
{
nameAndTooltip = Strings.CamerasPopupField,
getter = () => data.camerasPopupField,
setter = (value) => data.camerasPopupField = value as Camera,
};
internal static DebugUI.Widget CreateRenderingLayersField(DebugPanelDisplaySettingsData data) => new DebugUI.RenderingLayerField
{
nameAndTooltip = Strings.RenderingLayersField,
getter = () => data.renderingLayersField,
setter = (value) => data.renderingLayersField = value,
getRenderingLayerColor = index => data.renderingLayersColors[index],
setRenderingLayerColor = (value, index) => data.renderingLayersColors[index] = value,
};
internal static DebugUI.Widget CreateMessageBox(DebugPanelDisplaySettingsData data, DebugUI.MessageBox.Style style) => new DebugUI.MessageBox
{
displayName = Strings.MessageBox.name + " " + style.ToString(),
style = style
};
internal static DebugUI.Table CreateTable(DebugPanelDisplaySettingsData data)
{
var table = new DebugUI.Table()
{
displayName = Strings.DebugTable.name,
alternateRowColors = true,
isReadOnly = true
};
DebugUI.Table.Row[] rows = new DebugUI.Table.Row[k_NumRows];
for (int i = 0; i < k_NumRows; ++i)
{
rows[i] = new DebugUI.Table.Row { displayName = $"{Strings.TableRow} {i}" };
}
for (int i = 0; i < k_NumColumns; ++i)
{
for (int j = 0; j < k_NumRows; ++j)
{
DebugUI.Widget value = null;
switch (j)
{
case 0:
value = new DebugUI.Value(){ getter = () => "text" };
break;
case 1:
value = new DebugUI.BoolField(){
displayName = "",
getter = () => (bool)data.boolField,
};
break;
case 2:
value = new DebugUI.Value(){
displayName = "",
getter = () => data.boolField.ToString(),
};
break;
case 3:
value = new DebugUI.Value(){
displayName = "",
getter = () => data.intField.ToString(),
};
break;
case 4:
value = new DebugUI.Value(){
displayName = "",
getter = () => data.enumField.ToString(),
};
break;
case 5:
value = new DebugUI.Value(){
displayName = "",
getter = () => data.vector3Field.ToString(),
};
break;
case 6:
value = new DebugUI.Value(){
displayName = "",
getter = () => data.floatField.ToString(),
};
break;
case 7:
value = new DebugUI.ColorField(){
displayName = "color",
getter = () => data.colorField,
showPicker = false, };
break;
case 8:
value = new DebugUI.ObjectField(){
displayName = "",
getter = () => data.objectField, };
break;
default:
value = new DebugUI.Value(){ getter = () => "text" };
break;
}
rows[j].children.Add(value);
}
}
for (int i = 0; i < k_NumRows; ++i)
{
table.children.Add(rows[i]);
}
return table;
}
internal static DebugUI.Widget CreateButton(DebugPanelDisplaySettingsData data) => new DebugUI.Button
{
nameAndTooltip = Strings.Button,
action = () => { }
};
internal static DebugUI.Widget CreateProgressBar(DebugPanelDisplaySettingsData data) => new DebugUI.ProgressBarValue
{
displayName = Strings.ProgressBar.name,
getter = () => data.floatField,
min = 0f,
max = 100f,
};
}
[DisplayInfo(name = k_PanelName, order = int.MinValue)]
internal class SettingsPanel : DebugDisplaySettingsPanel<DebugPanelDisplaySettingsData>
{
public SettingsPanel(DebugPanelDisplaySettingsData data)
: base(data)
{
AddWidget(new DebugUI.Foldout
{
displayName = "Number Fields",
opened = true,
children =
{
WidgetFactory.CreateIntField(data),
WidgetFactory.CreateIntMinMaxField(data),
WidgetFactory.CreateUIntField(data),
WidgetFactory.CreateUIntMinMaxField(data),
WidgetFactory.CreateFloatField(data),
WidgetFactory.CreateFloatMinMaxField(data),
WidgetFactory.CreateNumberHiddenFields(data),
}
});
AddWidget(new DebugUI.Foldout
{
displayName = "Bool Fields",
opened = true,
children =
{
WidgetFactory.CreateBoolField(data),
WidgetFactory.CreateHistoryBoolField(data),
WidgetFactory.CreateBoolHiddenFields(data),
}
});
AddWidget(new DebugUI.Foldout
{
displayName = "Enum Fields",
opened = true,
children =
{
WidgetFactory.CreateEnumField(data),
WidgetFactory.CreateHistoryEnumField(data),
WidgetFactory.CreateBitField(data),
WidgetFactory.CreateEnumHiddenFields(data),
}
});
AddWidget(new DebugUI.Foldout
{
displayName = "Vector Fields",
opened = true,
children =
{
WidgetFactory.CreateColorField(data),
WidgetFactory.CreateVector2Field(data),
WidgetFactory.CreateVector3Field(data),
WidgetFactory.CreateVector4Field(data),
}
});
AddWidget(new DebugUI.Foldout
{
displayName = "Object Fields",
opened = true,
children =
{
WidgetFactory.CreateObjectField(data),
WidgetFactory.CreateObjectListField(data),
WidgetFactory.CreateObjectPopupField(data),
WidgetFactory.CreateCamerasField(data),
WidgetFactory.CreateRenderingLayersField(data),
}
});
AddWidget(new DebugUI.Foldout
{
displayName = "Values",
opened = true,
children =
{
WidgetFactory.CreateValue(data),
WidgetFactory.CreateValueTuple(data)
}
});
AddWidget(new DebugUI.Foldout
{
displayName = "Table",
opened = true,
children =
{
WidgetFactory.CreateTable(data)
}
});
AddWidget(new DebugUI.Foldout
{
displayName = "Documentation URL",
opened = true,
children =
{
},
documentationUrl = "test"
});
AddWidget(new DebugUI.Foldout
{
displayName = "Context Menu",
opened = true,
children =
{
},
contextMenuItems = new List<DebugUI.Foldout.ContextMenuItem> { new DebugUI.Foldout.ContextMenuItem { displayName = "Option" } }
});
AddWidget(new DebugUI.Foldout
{
displayName = "Documentation + Context Menu",
opened = true,
children =
{
},
documentationUrl = "test",
contextMenuItems = new List<DebugUI.Foldout.ContextMenuItem> { new DebugUI.Foldout.ContextMenuItem { displayName = "Option" } }
});
AddWidget(new DebugUI.Foldout
{
displayName = "Messages",
opened = true,
children =
{
WidgetFactory.CreateMessageBox(data, DebugUI.MessageBox.Style.None),
WidgetFactory.CreateMessageBox(data, DebugUI.MessageBox.Style.Info),
WidgetFactory.CreateMessageBox(data, DebugUI.MessageBox.Style.Warning),
WidgetFactory.CreateMessageBox(data, DebugUI.MessageBox.Style.Error),
}
});
AddWidget(new DebugUI.Foldout
{
displayName = "Buttons",
opened = true,
children =
{
WidgetFactory.CreateButton(data),
}
});
AddWidget(new DebugUI.Foldout
{
displayName = "Custom Fields/Widgets",
opened = true,
children =
{
WidgetFactory.CreateProgressBar(data),
new CustomProgressBarField()
{
displayName = "Progress Bar Editable",
getter = () => data.customProgress,
setter = (value) => data.customProgress = value,
isEditable = true
},
new CustomProgressBarField()
{
displayName = "Progress Bar",
getter = () => data.customProgress,
setter = (value) => data.customProgress = value,
lowColor = Color.lightYellow, highColor = Color.black,
isEditable = false
},
WidgetFactory.CreateFloatField(data)
}
});
}
}
}
public DebugPanelDisplaySettings()
{
Reset();
}
/// <inheritdoc/>
public override void Reset()
{
m_Settings.Clear();
// Add them in an unsorted way
Add(new DebugPanelDisplaySettingsData());
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 54809bef65bacbe47bbab9c13c271511
@@ -0,0 +1,268 @@
using System.Collections;
using System.Diagnostics;
using NUnit.Framework;
using Unity.Profiling.LowLevel;
using UnityEngine.TestTools;
namespace UnityEngine.Rendering.Tests
{
class ProfilingSamplerTests
{
const int k_FrameCount = 4;
ProfilingSampler m_Sampler;
[SetUp]
public void SetUp()
{
m_Sampler = new ProfilingSampler("TestSampler");
}
[TearDown]
public void TearDown()
{
m_Sampler.enableRecording = false;
}
static void WaitAtLeastMs(int milliseconds)
{
var sw = Stopwatch.StartNew();
while (sw.ElapsedMilliseconds < milliseconds)
{
#if !UNITY_WEBGL
System.Threading.Thread.Sleep(milliseconds);
#endif
}
}
[Test]
public void Ctor_Name_SetsNameProperty()
{
var sampler = new ProfilingSampler("MyMarker");
Assert.AreEqual("MyMarker", sampler.name);
}
[Test]
public void Ctor_Default_IsValid()
{
Assert.IsTrue(m_Sampler.IsValid());
}
[Test]
public void Create_ReturnsNonNull()
{
// Create() returns null in release builds; in editor/dev it must return a valid instance.
// Recorders are allocated lazily; none should be valid before enableRecording = true.
var sampler = ProfilingSampler.Create("CreatedMarker", MarkerFlags.Default);
Assert.IsNotNull(sampler);
Assert.IsTrue(sampler.IsValid());
#if USE_RECORDER
Assert.IsFalse(sampler.m_Recorder.Valid);
Assert.IsFalse(sampler.m_GpuRecorder.Valid);
Assert.IsFalse(sampler.m_InlineRecorder.Valid);
#endif
}
[Test]
public void CreateWithFlags_IsValid()
{
var sampler = ProfilingSampler.Create("FlagsMarker", MarkerFlags.VerbosityAdvanced);
Assert.IsTrue(sampler.IsValid());
#if USE_RECORDER
// Recorders are allocated lazily; none should be valid before enableRecording = true.
Assert.IsFalse(sampler.m_Recorder.Valid);
Assert.IsFalse(sampler.m_GpuRecorder.Valid);
Assert.IsFalse(sampler.m_InlineRecorder.Valid);
#endif
}
[Test]
public void Begin_End_NullCmd_DoNotThrow()
{
Assert.DoesNotThrow(() =>
{
m_Sampler.Begin(null);
m_Sampler.End(null);
});
}
[Test]
public void Begin_End_WithCmd_DoNotThrow()
{
var cmd = new CommandBuffer();
Assert.DoesNotThrow(() =>
{
m_Sampler.Begin(cmd);
m_Sampler.End(cmd);
});
cmd.Dispose();
}
[Test]
public void EnableRecording_Toggle_DoesNotThrow()
{
Assert.DoesNotThrow(() =>
{
m_Sampler.enableRecording = true;
m_Sampler.enableRecording = false;
});
}
[UnityTest]
public IEnumerator TimingProperties_ReturnZero_WhenNotRecording()
{
// Record a bit to ensure that we had non-zero state
m_Sampler.enableRecording = true;
for (int i = 0; i < 2; i++)
{
m_Sampler.Begin(null);
m_Sampler.End(null);
yield return null;
}
m_Sampler.enableRecording = false;
Assert.AreEqual(0.0f, m_Sampler.gpuElapsedTime);
Assert.AreEqual(0, m_Sampler.gpuSampleCount);
Assert.AreEqual(0.0f, m_Sampler.cpuElapsedTime);
Assert.AreEqual(0, m_Sampler.cpuSampleCount);
Assert.AreEqual(0.0f, m_Sampler.inlineCpuElapsedTime);
Assert.AreEqual(0, m_Sampler.inlineCpuSampleCount);
}
#if USE_RECORDER
// Bits 10-12 all zero → SampleGPU auto-added → GpuRecorder is created on first enableRecording = true
[Test]
public void SampleGPU_AutoAdded_ForUserFacingMarker()
{
var defaultSampler = ProfilingSampler.Create("UserDefault", MarkerFlags.Default);
defaultSampler.enableRecording = true;
Assert.IsTrue(defaultSampler.m_GpuRecorder.Valid,
"GpuRecorder should be valid after enableRecording = true for MarkerFlags.Default (SampleGPU auto-added)");
defaultSampler.enableRecording = false;
var warningsSampler = ProfilingSampler.Create("UserWarnings", MarkerFlags.Warning); // bit 4 only, not verbosity
warningsSampler.enableRecording = true;
Assert.IsTrue(warningsSampler.m_GpuRecorder.Valid,
"GpuRecorder should be valid after enableRecording = true for MarkerFlags.Warning (no verbosity bits → SampleGPU auto-added)");
warningsSampler.enableRecording = false;
}
// Any verbosity flag (bits 10-12) set → SampleGPU NOT auto-added → GpuRecorder never allocated
[TestCase(MarkerFlags.VerbosityDebug, TestName = "SampleGPU_NotAutoAdded_VerbosityDebug")]
[TestCase(MarkerFlags.VerbosityInternal, TestName = "SampleGPU_NotAutoAdded_VerbosityInternal")]
[TestCase(MarkerFlags.VerbosityExternal, TestName = "SampleGPU_NotAutoAdded_VerbosityExternal")]
[TestCase(MarkerFlags.VerbosityAdvanced, TestName = "SampleGPU_NotAutoAdded_VerbosityAdvanced")]
public void SampleGPU_NotAutoAdded_ForNonUserFacingMarker(MarkerFlags verbosityFlags)
{
var sampler = ProfilingSampler.Create("NonUserFacing", verbosityFlags);
sampler.enableRecording = true;
Assert.IsFalse(sampler.m_GpuRecorder.Valid,
$"GpuRecorder should not be valid for verbosity flags 0x{(int)verbosityFlags:X4} (SampleGPU was NOT auto-added)");
sampler.enableRecording = false;
}
[Test]
public void RecordersNotAllocated_BeforeFirstEnableRecording()
{
var sampler = new ProfilingSampler("LazyTest");
Assert.IsFalse(sampler.m_Recorder.Valid, "m_Recorder allocated before enableRecording = true");
Assert.IsFalse(sampler.m_GpuRecorder.Valid, "m_GpuRecorder allocated before enableRecording = true");
Assert.IsFalse(sampler.m_InlineRecorder.Valid, "m_InlineRecorder allocated before enableRecording = true");
}
[Test]
public void RecordersAllocated_AfterFirstEnableRecording()
{
var sampler = new ProfilingSampler("LazyAllocTest") { enableRecording = true };
Assert.IsTrue(sampler.m_Recorder.Valid, "m_Recorder not valid after enableRecording = true");
Assert.IsTrue(sampler.m_GpuRecorder.Valid, "m_GpuRecorder not valid (SampleGPU auto-added for default flags)");
Assert.IsTrue(sampler.m_InlineRecorder.Valid, "m_InlineRecorder not valid after enableRecording = true");
sampler.enableRecording = false;
sampler.Dispose();
}
[UnityTest]
public IEnumerator EnableRecording_InlineCpuElapsedTime_IsGreaterThanZero()
{
m_Sampler.enableRecording = true;
for (int i = 0; i < k_FrameCount; i++)
{
m_Sampler.Begin(null);
WaitAtLeastMs(1);
m_Sampler.End(null);
yield return null;
}
Assert.Greater(m_Sampler.inlineCpuElapsedTime, 0.0f);
Assert.Greater(m_Sampler.inlineCpuSampleCount, 0);
}
[UnityTest]
public IEnumerator ProfilingScope_InlineCpuElapsedTime_IsGreaterThanZero()
{
m_Sampler.enableRecording = true;
for (int i = 0; i < k_FrameCount; i++)
{
using (new ProfilingScope(m_Sampler))
{
WaitAtLeastMs(1);
}
yield return null;
}
Assert.Greater(m_Sampler.inlineCpuElapsedTime, 0.0f);
Assert.Greater(m_Sampler.inlineCpuSampleCount, 0);
}
[UnityTest]
public IEnumerator ProfilingScopeWithObject_InlineCpu_IsCapturedByRecorder()
{
m_Sampler.enableRecording = true;
var texture = new Texture2D(1, 1) { name = "TestTexture" };
for (int i = 0; i < k_FrameCount; i++)
{
using (new ProfilingScope(m_Sampler, texture))
{
WaitAtLeastMs(1);
}
yield return null;
}
UnityEngine.Object.DestroyImmediate(texture);
Assert.Greater(m_Sampler.inlineCpuElapsedTime, 0.0f);
Assert.Greater(m_Sampler.inlineCpuSampleCount, 0);
}
[Test]
public void ProfilingScopeWithNullObject_InlineCpu_DoesNotCrash()
{
Assert.DoesNotThrow(() =>
{
using (new ProfilingScope(m_Sampler, null))
{ }
});
}
[Test]
public void Dispose_DoesNotThrow()
{
var sampler = new ProfilingSampler("DisposeTest") { enableRecording = true };
Assert.DoesNotThrow(() => sampler.Dispose());
}
[Test]
public void Dispose_CalledTwice_DoesNotThrow()
{
var sampler = new ProfilingSampler("DisposeTwiceTest");
Assert.DoesNotThrow(() =>
{
sampler.Dispose();
sampler.Dispose(); // recorders are no longer .Valid after first dispose
});
}
#endif
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f07e7fe35721f2f40b35dd5f63c1be49
@@ -0,0 +1,144 @@
using System.Collections;
using NUnit.Framework;
using Unity.Profiling;
using UnityEngine.TestTools;
namespace UnityEngine.Rendering.Tests
{
class ProfilingSamplerWithCommandBufferTests
{
const int k_SampledFrames = 100;
Texture2D m_Texture;
[OneTimeSetUp]
public void SetUp()
{
m_Texture = new Texture2D(1, 1) { name = "TestTexture" };
}
[OneTimeTearDown]
public void TearDown()
{
UnityEngine.Object.DestroyImmediate(m_Texture);
}
static IEnumerator WaitForRecorderSample(CommandBuffer commandBuffer, ProfilerRecorder recorder, int maxFrames)
{
for (int i = 0; i < maxFrames; i++)
{
Graphics.ExecuteCommandBuffer(commandBuffer);
yield return null;
if (recorder.Count > 0 && recorder.GetSample(0).Count > 0)
yield break;
}
Assert.Fail($"Recorder sample count was 0 after {maxFrames} frames");
}
[UnityTest]
public IEnumerator CommandBufferBeginSample_IsCapturedByProfilerRecorder()
{
var sampler = new ProfilingSampler(nameof(CommandBufferBeginSample_IsCapturedByProfilerRecorder));
using var inlineRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Render, "Inl_" + sampler.name);
using var recorder = ProfilerRecorder.StartNew(ProfilerCategory.Render, sampler.name);
using var commandBuffer = new CommandBuffer();
using (new ProfilingScope(commandBuffer, sampler))
{ }
// The inline sampler should have recorded the sample even before the command buffer is executed, as it runs on the CPU immediately.
inlineRecorder.Stop();
Assert.AreEqual(1, inlineRecorder.Count);
Assert.AreEqual(1, inlineRecorder.GetSample(0).Count);
// The sampler should be recorded on the CPU after the command buffer is executed on render thread.
yield return WaitForRecorderSample(commandBuffer, recorder, k_SampledFrames);
}
[UnityTest]
public IEnumerator CommandBufferBeginSampleWithObject_IsCapturedByProfilerRecorder()
{
var sampler = new ProfilingSampler(nameof(CommandBufferBeginSampleWithObject_IsCapturedByProfilerRecorder));
using var inlineRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Render, "Inl_" + sampler.name);
using var recorder = ProfilerRecorder.StartNew(ProfilerCategory.Render, sampler.name);
using var commandBuffer = new CommandBuffer();
using (new ProfilingScope(commandBuffer, sampler, m_Texture))
{ }
// The inline sampler should have recorded the sample even before the command buffer is executed, as it runs on the CPU immediately.
inlineRecorder.Stop();
Assert.AreEqual(1, inlineRecorder.Count);
Assert.AreEqual(1, inlineRecorder.GetSample(0).Count);
// The sampler should be recorded on the CPU after the command buffer is executed on render thread.
yield return WaitForRecorderSample(commandBuffer, recorder, k_SampledFrames);
}
[Test]
public void CommandBufferBeginSampleWithNullObject_DoesNotCrash()
{
var sampler = new ProfilingSampler(nameof(CommandBufferBeginSampleWithNullObject_DoesNotCrash));
var commandBuffer = new CommandBuffer();
Assert.DoesNotThrow(() =>
{
using (new ProfilingScope(commandBuffer, sampler, null))
{ }
Graphics.ExecuteCommandBuffer(commandBuffer);
});
commandBuffer.Dispose();
}
[UnityTest]
[UnityPlatform(include = new[]
{
// RuntimePlatform.WindowsPlayer, Unstable on StandaloneWindows64: https://jira.unity3d.com/browse/UUM-138158
RuntimePlatform.WindowsEditor,
RuntimePlatform.PS5,
RuntimePlatform.Switch
})]
public IEnumerator CommandBufferBeginSampleWithObject_GpuSamples_ReturnsNonZeroCount()
{
if (!SystemInfo.supportsGpuRecorder)
yield break;
var sampler = new ProfilingSampler(nameof(CommandBufferBeginSampleWithObject_GpuSamples_ReturnsNonZeroCount));
using var recorder = ProfilerRecorder.StartNew(ProfilerCategory.Render, sampler.name, 1, ProfilerRecorderOptions.GpuRecorder | ProfilerRecorderOptions.Default);
using var commandBuffer = new CommandBuffer();
using (new ProfilingScope(commandBuffer, sampler, m_Texture))
{ }
// The sampler should be recorded on the GPU after the command buffer is executed on render thread.
// Need at least 4 frames of wait.
yield return WaitForRecorderSample(commandBuffer, recorder, k_SampledFrames);
}
[UnityTest]
[UnityPlatform(include = new[]
{
// RuntimePlatform.WindowsPlayer, Unstable on StandaloneWindows64: https://jira.unity3d.com/browse/UUM-138158
RuntimePlatform.WindowsEditor,
RuntimePlatform.PS5,
RuntimePlatform.Switch
})]
public IEnumerator CommandBufferBeginSample_GpuSamples_ReturnsNonZeroCount()
{
if (!SystemInfo.supportsGpuRecorder)
yield break;
var sampler = new ProfilingSampler(nameof(CommandBufferBeginSample_GpuSamples_ReturnsNonZeroCount));
using var recorder = ProfilerRecorder.StartNew(ProfilerCategory.Render, sampler.name, 1, ProfilerRecorderOptions.GpuRecorder | ProfilerRecorderOptions.Default);
using var commandBuffer = new CommandBuffer();
using (new ProfilingScope(commandBuffer, sampler))
{ }
// The sampler should be recorded on the GPU after the command buffer is executed on render thread.
// Need at least 4 frames of wait.
yield return WaitForRecorderSample(commandBuffer, recorder, k_SampledFrames);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b9a72268b9aad854a97135a16c412cb3
@@ -0,0 +1,178 @@
using NUnit.Framework;
namespace UnityEngine.Rendering.Tests
{
class LightUnitTests
{
GameObject go;
const float epsilon = 0.001f;
[SetUp]
public void Setup()
{
GameObject go = new GameObject("Light", typeof(Light));
}
[TearDown]
public void TearDown()
{
GameObject.Destroy(go);
}
[Test]
public void LightUnitSupport()
{
// Directional and box lights should only support Lux
LightType t = LightType.Directional;
Assert.False(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Lumen));
Assert.False(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Candela));
Assert.True(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Lux));
Assert.False(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Nits));
Assert.False(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Ev100));
t = LightType.Box;
Assert.False(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Lumen));
Assert.False(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Candela));
Assert.True(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Lux));
Assert.False(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Nits));
Assert.False(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Ev100));
// Point lights should only support Lumen, Candela, Lux, EV100
t = LightType.Point;
Assert.True(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Lumen));
Assert.True(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Candela));
Assert.True(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Lux));
Assert.False(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Nits));
Assert.True(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Ev100));
// Spot lights should only support Lumen, Candela, Lux, EV100
t = LightType.Spot;
Assert.True(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Lumen));
Assert.True(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Candela));
Assert.True(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Lux));
Assert.False(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Nits));
Assert.True(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Ev100));
// Area lights should only support Lumen, Nits, EV100
t = LightType.Rectangle;
Assert.True(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Lumen));
Assert.False(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Candela));
Assert.False(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Lux));
Assert.True(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Nits));
Assert.True(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Ev100));
t = LightType.Disc;
Assert.True(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Lumen));
Assert.False(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Candela));
Assert.False(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Lux));
Assert.True(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Nits));
Assert.True(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Ev100));
t = LightType.Tube;
Assert.True(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Lumen));
Assert.False(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Candela));
Assert.False(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Lux));
Assert.True(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Nits));
Assert.True(LightUnitUtils.IsLightUnitSupported(t, LightUnit.Ev100));
}
[Test]
public void DirectionalAndBoxLightUnitConversion()
{
Light l = GameObject.FindAnyObjectByType<Light>().GetComponent<Light>();
l.type = LightType.Directional;
Assert.AreEqual(3f, LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lux, LightUnit.Lux));
l.type = LightType.Box;
Assert.AreEqual(3f, LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lux, LightUnit.Lux));
}
[Test]
public void PointLightUnitConversion()
{
// Point light
Light l = GameObject.FindAnyObjectByType<Light>().GetComponent<Light>();
l.type = LightType.Point;
l.luxAtDistance = 3f;
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Candela, LightUnit.Candela), Is.EqualTo(3f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lumen, LightUnit.Candela), Is.EqualTo(0.238732412f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Candela, LightUnit.Lumen), Is.EqualTo(37.6991119f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lumen, LightUnit.Lux), Is.EqualTo(0.0265258234f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lux, LightUnit.Lumen), Is.EqualTo(339.292023f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lumen, LightUnit.Ev100), Is.EqualTo(0.933466434f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Ev100, LightUnit.Lumen), Is.EqualTo(12.566371f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Candela, LightUnit.Lux), Is.EqualTo(0.333333343f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lux, LightUnit.Candela), Is.EqualTo(27.0f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Candela, LightUnit.Ev100), Is.EqualTo(4.58496237f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Ev100, LightUnit.Candela), Is.EqualTo(1f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lux, LightUnit.Ev100), Is.EqualTo(7.75488758f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Ev100, LightUnit.Lux), Is.EqualTo(0.111111112f).Within(epsilon));
}
[Test]
public void SpotLightUnitConversion()
{
Light l = GameObject.FindAnyObjectByType<Light>().GetComponent<Light>();
l.type = LightType.Spot;
l.enableSpotReflector = false;
l.luxAtDistance = 3f;
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Candela, LightUnit.Candela), Is.EqualTo(3f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lumen, LightUnit.Candela), Is.EqualTo(0.238732412f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Candela, LightUnit.Lumen), Is.EqualTo(37.6991119f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lumen, LightUnit.Lux), Is.EqualTo(0.0265258234f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lux, LightUnit.Lumen), Is.EqualTo(339.292023f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lumen, LightUnit.Ev100), Is.EqualTo(0.933466434f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Ev100, LightUnit.Lumen), Is.EqualTo(12.566371f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Candela, LightUnit.Lux), Is.EqualTo(0.333333343f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lux, LightUnit.Candela), Is.EqualTo(27.0f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Candela, LightUnit.Ev100), Is.EqualTo(4.58496237f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Ev100, LightUnit.Candela), Is.EqualTo(1f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lux, LightUnit.Ev100), Is.EqualTo(7.75488758f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Ev100, LightUnit.Lux), Is.EqualTo(0.111111112f).Within(epsilon));
l.enableSpotReflector = true;
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Candela, LightUnit.Candela), Is.EqualTo(3f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lumen, LightUnit.Candela), Is.EqualTo(14.0125141f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Candela, LightUnit.Lumen), Is.EqualTo(0.642283022f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lumen, LightUnit.Lux), Is.EqualTo(1.55694604f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lux, LightUnit.Lumen), Is.EqualTo(5.78054714f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lumen, LightUnit.Ev100), Is.EqualTo(6.80864382f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Ev100, LightUnit.Lumen), Is.EqualTo(0.214094341f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Candela, LightUnit.Lux), Is.EqualTo(0.333333343f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lux, LightUnit.Candela), Is.EqualTo(27.0f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Candela, LightUnit.Ev100), Is.EqualTo(4.58496237f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Ev100, LightUnit.Candela), Is.EqualTo(1f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lux, LightUnit.Ev100), Is.EqualTo(7.75488758f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Ev100, LightUnit.Lux), Is.EqualTo(0.111111112f).Within(epsilon));
}
[Test]
public void AreaLightUnitConversion()
{
Light l = GameObject.FindAnyObjectByType<Light>().GetComponent<Light>();
l.areaSize = new Vector2(4f, 5f);
l.type = LightType.Rectangle;
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lumen, LightUnit.Nits), Is.EqualTo(0.0477464795f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Nits, LightUnit.Lumen), Is.EqualTo(188.495575f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lumen, LightUnit.Ev100), Is.EqualTo(-1.38846159f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Ev100, LightUnit.Lumen), Is.EqualTo(62.8318558f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Nits, LightUnit.Ev100), Is.EqualTo(4.58496237f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Ev100, LightUnit.Nits), Is.EqualTo(1f).Within(epsilon));
l.type = LightType.Disc;
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lumen, LightUnit.Nits), Is.EqualTo(0.0189977214f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Nits, LightUnit.Lumen), Is.EqualTo(473.741028f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lumen, LightUnit.Ev100), Is.EqualTo(-2.71802998f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Ev100, LightUnit.Lumen), Is.EqualTo(157.913681f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Nits, LightUnit.Ev100), Is.EqualTo(4.58496237f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Ev100, LightUnit.Nits), Is.EqualTo(1f).Within(epsilon));
l.type = LightType.Tube;
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lumen, LightUnit.Nits), Is.EqualTo(0.0596831031f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Nits, LightUnit.Lumen), Is.EqualTo(150.796448f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Lumen, LightUnit.Ev100), Is.EqualTo(-1.06653357f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Ev100, LightUnit.Lumen), Is.EqualTo(50.2654839f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Nits, LightUnit.Ev100), Is.EqualTo(4.58496237f).Within(epsilon));
Assert.That(LightUnitUtils.ConvertIntensity(l, 3f, LightUnit.Ev100, LightUnit.Nits), Is.EqualTo(1f).Within(epsilon));
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6b0549ef5aba7f74ebb69af4844d9a1d
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 810f21d4e5b50a9489b040b4763c01ef
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,112 @@
#if UNITY_EDITOR
using NUnit.Framework;
using System;
using Unity.Mathematics;
using UnityEngine.PathTracing.Lightmapping;
namespace UnityEngine.PathTracing.Tests
{
internal class ChartIdenticationTests
{
[Test]
public void TestUnionFind()
{
uint vertexCount = 8;
var vertexChartIds = new UInt32[vertexCount];
var triangleIndices = new UInt32[]
{
0, 1, 7,
4, 5, 6,
2, 6, 4,
0, 3, 7
};
ChartIdentification.InitializeRepresentatives(vertexChartIds);
ChartIdentification.UnionTriangleEdges(triangleIndices, vertexChartIds);
ChartIdentification.FindRepresentatives(vertexChartIds);
Assert.AreEqual(0, vertexChartIds[0]);
Assert.AreEqual(0, vertexChartIds[1]);
Assert.AreEqual(2, vertexChartIds[2]);
Assert.AreEqual(0, vertexChartIds[3]);
Assert.AreEqual(2, vertexChartIds[4]);
Assert.AreEqual(2, vertexChartIds[5]);
Assert.AreEqual(2, vertexChartIds[6]);
Assert.AreEqual(0, vertexChartIds[7]);
}
[Test]
public void TestCompaction()
{
var vertexChartIds = new UInt32[]
{
3, 3, 3,
3, 2, 2,
9, 9, 0,
0, 3, 3
};
ChartIdentification.Compact(vertexChartIds, out uint chartCount);
Assert.AreEqual(4, chartCount);
Assert.AreEqual(0, vertexChartIds[0]);
Assert.AreEqual(0, vertexChartIds[1]);
Assert.AreEqual(0, vertexChartIds[2]);
Assert.AreEqual(0, vertexChartIds[3]);
Assert.AreEqual(1, vertexChartIds[4]);
Assert.AreEqual(1, vertexChartIds[5]);
Assert.AreEqual(2, vertexChartIds[6]);
Assert.AreEqual(2, vertexChartIds[7]);
Assert.AreEqual(3, vertexChartIds[8]);
Assert.AreEqual(3, vertexChartIds[9]);
Assert.AreEqual(0, vertexChartIds[10]);
Assert.AreEqual(0, vertexChartIds[11]);
}
[Test]
public void TestDeduplication()
{
uint vertexCount = 6;
var vertexChartIds = new UInt32[vertexCount];
var inputVertexUvs = new float2[]
{
new(-1.0f, 0.0f),
new(0.0f, 1.0f),
new(0.0f, -1.0f),
new(0.0f, 1.0f),
new(1.0f, 0.0f),
new(0.0f, -1.0f)
};
var inputVertexNormals = new float3[]
{
new(-1.0f, 0.0f, 0.0f),
new(0.0f, 1.0f, 0.0f),
new(0.0f, -1.0f, 0.0f),
new(0.0f, 1.0f, 0.0f),
new(1.0f, 0.0f, 0.0f),
new(0.0f, -1.0f, 0.0f)
};
var inputVertexPositions = new float3[]
{
new(-1.0f, 0.0f, 0.0f),
new(0.0f, 1.0f, 0.0f),
new(0.0f, -1.0f, 0.0f),
new(0.0f, 1.0f, 0.0f),
new(1.0f, 0.0f, 0.0f),
new(0.0f, -2.0f, 0.0f)
};
ChartIdentification.InitializeRepresentatives(vertexChartIds);
ChartIdentification.UnionDuplicateVertices(inputVertexUvs, inputVertexPositions, inputVertexNormals, vertexChartIds, true);
ChartIdentification.FindRepresentatives(vertexChartIds);
Assert.AreEqual(0, vertexChartIds[0]);
Assert.AreEqual(3, vertexChartIds[1]);
Assert.AreEqual(2, vertexChartIds[2]);
Assert.AreEqual(3, vertexChartIds[3]);
Assert.AreEqual(4, vertexChartIds[4]);
Assert.AreEqual(5, vertexChartIds[5]);
}
}
}
#endif
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a6482f5100464a5abfcfce1521a3baf0
@@ -0,0 +1,71 @@
#define UNIFIED_RT_GROUP_SIZE_X 16
#define UNIFIED_RT_GROUP_SIZE_Y 8
#define UNIFIED_RT_RAYGEN_FUNC TestRadianceEstimation
#include "Packages/com.unity.render-pipelines.core/Runtime/PathTracing/Shaders/PathTracing.hlsl"
// PS5 is excluded right now as compiling the shader times out on this platform. See UUM-142110
#pragma exclude_renderers switch switch2 webgpu ps5
uint g_SampleCount;
struct TestRay
{
float3 origin;
float3 direction;
};
StructuredBuffer<TestRay> _InputRay;
RWStructuredBuffer<float3> _Output;
float3 EstimateIncomingRadiance(UnifiedRT::DispatchInfo dispatchInfo, float3 rayOrigin, float3 rayDirection, uint sampleCount, uint bounceCount, UnifiedRT::RayTracingAccelStruct accelStruct, StructuredBuffer<UnifiedRT::InstanceData> instanceList)
{
PathTracingSampler rngState;
rngState.Init(uint2(0, 0), 0);
PathIterator pathIter;
float3 sampleSum = 0;
for (uint sampleIdx = 0; sampleIdx < sampleCount; ++sampleIdx)
{
UnifiedRT::Ray ray;
ray.origin = rayOrigin;
ray.direction = rayDirection;
ray.tMin = 0;
ray.tMax = FLT_INF;
InitPathIterator(pathIter, ray);
int transparencyBounce = 0;
const int maxTransparencyBounces = 6;
for (uint bounceIndex = 0; bounceIndex <= bounceCount && transparencyBounce < maxTransparencyBounces; bounceIndex++)
{
uint traceResult = TraceBounceRayAndAddRadiance(pathIter, bounceIndex, RayMask(bounceIndex == 0), ShadowRayMask(), dispatchInfo, accelStruct, instanceList, rngState);
if (traceResult == TRACE_MISS)
{
break;
}
if (traceResult == TRACE_TRANSMISSION)
{
bounceIndex--;
transparencyBounce++;
continue;
}
if (!Scatter(pathIter, rngState))
break;
rngState.NextBounce();
}
sampleSum += pathIter.radianceSample;
}
return sampleSum / float(sampleCount);
}
void TestRadianceEstimation(UnifiedRT::DispatchInfo dispatchInfo)
{
UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(g_SceneAccelStruct);
TestRay ray = _InputRay[0];
_Output[0] = EstimateIncomingRadiance(dispatchInfo, ray.origin, ray.direction, g_SampleCount, g_BounceCount, accelStruct, g_AccelStructInstanceList);
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 6e0b547bab30a804c8123974120d583f
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 11500000, guid: 42d537a8a4089e448a99fc57a06d74a9, type: 3}
@@ -0,0 +1,243 @@
// These tests relies on RayTracingResources.Load() so cannot run in a player.
#if UNITY_EDITOR
using System.Runtime.InteropServices;
using NUnit.Framework;
using Unity.Mathematics;
using UnityEditor;
using UnityEngine.PathTracing.Core;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Sampling;
using UnityEngine.Rendering.UnifiedRayTracing;
namespace UnityEngine.PathTracing.Tests
{
internal class PathIteratorTests
{
private static class MeshUtil
{
internal static Mesh CreateQuadMesh()
{
Mesh mesh = new Mesh();
Vector3[] vertices =
{
new(-0.5f, -0.5f, 0.0f),
new(0.5f, -0.5f, 0.0f),
new(-0.5f, 0.5f, 0.0f),
new(0.5f, 0.5f, 0.0f)
};
mesh.vertices = vertices;
Vector3[] normals =
{
Vector3.forward,
Vector3.forward,
Vector3.forward,
Vector3.forward
};
mesh.normals = normals;
Vector2[] uv =
{
new(0, 0),
new(1, 0),
new(0, 1),
new(1, 1)
};
mesh.uv = uv;
int[] tris =
{
0, 2, 1,
2, 3, 1
};
mesh.triangles = tris;
return mesh;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct TestRay
{
public float3 Origin;
public float3 Direction;
}
private World _world;
private RayTracingContext _rayTracingContext;
private CommandBuffer _cmd;
private SamplingResources _samplingResources;
private RTHandle _emptyExposureTexture;
private const RayTracingBackend _backend = RayTracingBackend.Compute;
[SetUp]
public void Setup()
{
if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLCore ||
SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLES3)
{
Assert.Ignore("PathTracingCore is incompatible with OpenGL as it places a harsh limitation of buffer count.");
return;
}
var resources = new RayTracingResources();
resources.Load();
_rayTracingContext = new RayTracingContext(_backend, resources);
_cmd = new CommandBuffer();
_cmd.name = "PathTracingCore/PathIteratorTests";
var worldResources = new WorldResourceSet();
worldResources.LoadFromAssetDatabase();
_world = new World();
_world.Init(_rayTracingContext, worldResources);
_samplingResources = new SamplingResources();
_samplingResources.Load((uint)SamplingResources.ResourceType.All);
_emptyExposureTexture = RTHandles.Alloc(1, 1, enableRandomWrite: true, name: "Empty EV100 Exposure");
}
[TearDown]
public void TearDown()
{
_world?.Dispose();
_rayTracingContext?.Dispose();
_cmd?.Dispose();
_emptyExposureTexture?.Release();
_samplingResources?.Dispose();
}
[Test]
[TestCase(1, 1, 1, 1u)]
[TestCase(10, 20, 30, 1u)]
[TestCase(10, 20, 30, 8u)]
[TestCase(0, 42, 0, 1u)]
[TestCase(0, 42, 0, 8u)]
[Ignore("Unstable: https://jira.unity3d.com/browse/UUM-134752")]
public void EmptyWorldWithEnvironmentLight_ShouldOutputEnvironmentLight(float envRed, float envGreen, float envBlue, uint sampleCount)
{
using var deviceInputBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 1, Marshal.SizeOf<TestRay>());
{
var inputHost = new Ray[1];
inputHost[0].origin = float3.zero;
inputHost[0].direction = new float3(0, 1, 0);
deviceInputBuffer.SetData(inputHost);
}
using var deviceOutputBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 1, sizeof(float) * 3);
var cubemapMat = new Material(Shader.Find("PathIteratorTesting/UniformCubemap"));
cubemapMat.SetVector("_Radiance", new Vector4(envRed, envGreen, envBlue, 1.0f));
_world.SetEnvironmentMaterial(cubemapMat);
GraphicsBuffer buildScratchBuffer = null;
_world.Build(new Bounds(), _cmd, ref buildScratchBuffer, _samplingResources, true, 8, 64 * 64 * 64);
var shader = _rayTracingContext.LoadRayTracingShader("Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/PathIteratorTest.urtshader");
Util.BindWorld(_cmd, shader, _world);
Util.BindPathTracingInputs(_cmd, shader, 1, false, 4, 1.0f, RenderedGameObjectsFilter.OnlyStatic, _samplingResources, _emptyExposureTexture);
shader.SetIntParam(_cmd, Shader.PropertyToID("g_SampleCount"), (int)sampleCount);
shader.SetBufferParam(_cmd, Shader.PropertyToID("_Output"), deviceOutputBuffer);
shader.SetBufferParam(_cmd, Shader.PropertyToID("_InputRay"), deviceInputBuffer);
GraphicsBuffer traceScratchBuffer = null;
RayTracingHelper.ResizeScratchBufferForTrace(shader, 1, 1, 1, ref traceScratchBuffer);
shader.Dispatch(_cmd, traceScratchBuffer, 1, 1, 1);
Graphics.ExecuteCommandBuffer(_cmd);
var hostOutputBuffer = new float3[1];
deviceOutputBuffer.GetData(hostOutputBuffer);
const float tolerance = 0.001f;
Assert.AreEqual(envRed, hostOutputBuffer[0].x, tolerance);
Assert.AreEqual(envGreen, hostOutputBuffer[0].y, tolerance);
Assert.AreEqual(envBlue, hostOutputBuffer[0].z, tolerance);
CoreUtils.Destroy(cubemapMat);
buildScratchBuffer.Dispose();
traceScratchBuffer.Dispose();
}
[Test]
[TestCase(1, 0, 0, 1u)]
[TestCase(0, 1, 0, 1u)]
[TestCase(0, 0, 1, 1u)]
[TestCase(0, 0, 1, 8u)]
public void RayHittingPlaneLitByWhiteEnvironmentLight_ShouldMatchAnalyticDerivation(float albedoRed, float albedoGreen, float albedoBlue, uint sampleCount)
{
Assert.Ignore("Fails on MacEditor ARM64, Windows x64 and other platforms, probably due to it looking for output in the wrong place. See https://jira.unity3d.com/browse/GFXLIGHT-1796");
using var deviceInputBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 1, Marshal.SizeOf<TestRay>());
{
var hostInputBuffer = new Ray[1];
hostInputBuffer[0].origin = new float3(0.1f, 0.1f, -1); // We avoid numerical precision issues at the diagonal.
hostInputBuffer[0].direction = new float3(0, 0, 1);
deviceInputBuffer.SetData(hostInputBuffer);
}
using var deviceOutputBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 1, sizeof(float) * 3);
var cubemapMaterial = new Material(Shader.Find("PathIteratorTesting/UniformCubemap"));
cubemapMaterial.SetVector("_Radiance", new Vector4(1.0f, 1.0f, 1.0f, 1.0f));
GraphicsBuffer buildScratchBuffer = null;
var mesh = MeshUtil.CreateQuadMesh();
var material = new Material(Shader.Find("PathIteratorTesting/UniformAlbedoMetaPass"));
material.SetColor("_Albedo", new Color(albedoRed, albedoGreen, albedoBlue, 1));
var matDesc = MaterialPool.ConvertUnityMaterialToMaterialDescriptor(material, EmissionMode.Baked);
_world.SetEnvironmentMaterial(cubemapMaterial);
var matHandle = _world.AddMaterial(matDesc, UVChannel.UV0);
var mask = World.GetInstanceMask(ShadowCastingMode.On, true, RenderedGameObjectsFilter.OnlyStatic);
_world.AddInstance(
mesh,
new [] { matHandle },
new[] { mask },
1,
Matrix4x4.identity,
new Bounds(Vector3.zero, Vector3.one * 20.0f),
true,
RenderedGameObjectsFilter.OnlyStatic,
true);
_world.Build(new Bounds(), _cmd, ref buildScratchBuffer, _samplingResources, true, 8, 64 * 64 * 64);
var shader = _rayTracingContext.LoadRayTracingShader("Packages/com.unity.render-pipelines.core/Tests/Runtime/PathTracing/PathIteratorTest.urtshader");
Util.BindWorld(_cmd, shader, _world);
Util.BindPathTracingInputs(_cmd, shader, 1, false, 4, 1.0f, RenderedGameObjectsFilter.OnlyStatic, _samplingResources, _emptyExposureTexture);
shader.SetIntParam(_cmd, Shader.PropertyToID("g_SampleCount"), (int)sampleCount);
shader.SetBufferParam(_cmd, Shader.PropertyToID("_Output"), deviceOutputBuffer);
shader.SetBufferParam(_cmd, Shader.PropertyToID("_InputRay"), deviceInputBuffer);
GraphicsBuffer traceScratchBuffer = null;
RayTracingHelper.ResizeScratchBufferForTrace(shader, 1, 1, 1, ref traceScratchBuffer);
shader.Dispatch(_cmd, traceScratchBuffer, 1, 1, 1);
Graphics.ExecuteCommandBuffer(_cmd);
var outputHost = new float3[1];
deviceOutputBuffer.GetData(outputHost);
// We have a non-emissive lambertian plane with albedo c which receives light from a uniform environment light with radiance 1. How much light is reflected in the direction of the normal of the plane?
// The incoming light L_i is 1 and the BRDF is c/π.
// L_o = ∫ c/π L_i(ω) cos(θ) dω = c/π ∫ cos(θ) dω = c.
// The last equality holds because the hemispherical integral of cos(θ) is π.
Assert.AreEqual(albedoRed, outputHost[0].x);
Assert.AreEqual(albedoGreen, outputHost[0].y);
Assert.AreEqual(albedoBlue, outputHost[0].z);
CoreUtils.Destroy(cubemapMaterial);
CoreUtils.Destroy(material);
buildScratchBuffer.Dispose();
traceScratchBuffer.Dispose();
}
}
}
#endif
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3745144d19d6f469e96d89ae8647a702
@@ -0,0 +1,68 @@
Shader "PathIteratorTesting/UniformAlbedoMetaPass"
{
Properties {
_Albedo ("Albedo", Color) = (.5, .5, .5, 1.0)
}
SubShader
{
Pass
{
Name "META"
Tags { "LightMode" = "Meta" }
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma shader_feature _EMISSION
#include "UnityCG.cginc"
#include "UnityMetaPass.cginc"
float4 _Albedo;
struct appdata
{
float4 vertex : POSITION;
float2 uv1 : TEXCOORD1;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv1 : TEXCOORD1;
};
v2f vert(appdata v)
{
v2f o;
if (unity_MetaVertexControl.x)
{
o.pos.xy = v.uv1 * unity_LightmapST.xy + unity_LightmapST.zw;
o.pos.zw = 0;
}
else
{
o.pos = mul(UNITY_MATRIX_VP, float4(v.vertex.xyz, 1.0));
}
o.uv1 = v.uv1;
return o;
}
float4 frag(v2f i) : SV_Target
{
if (unity_MetaFragmentControl.x)
{
return _Albedo;
}
if (unity_MetaFragmentControl.y)
{
return 0;
}
return 0;
}
ENDCG
}
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 3990aa41cea4e4fa79ec155970b62e32
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,43 @@
Shader "PathIteratorTesting/UniformCubemap" {
Properties {
_Radiance ("Radiance", Vector) = (.5, .5, .5, 1.0)
}
SubShader {
Tags { "Queue"="Background" "RenderType"="Background" "PreviewType"="Skybox" }
Cull Off ZWrite Off
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
half4 _Radiance;
struct appdata_t {
float4 vertex : POSITION;
};
struct v2f {
float4 vertex : SV_POSITION;
};
v2f vert (appdata_t v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex.xyz);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(_Radiance.xyz, 1.0);
}
ENDCG
}
}
Fallback Off
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 397e4e1ab7fcb41b9842c2e464a0d58a
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,23 @@
{
"name": "Unity.PathTracing.Runtime.Tests",
"rootNamespace": "",
"references": [
"GUID:214c0945bb158c940aada223f3223ee8",
"GUID:c49c619b6af2be941af9bcbca2641964",
"GUID:d8b63aba1907145bea998dd612889d6b",
"GUID:df380645f10b7bc4b97d4f5eb6303d95"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll"
],
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 7c4d0a73126b14e2693931e0c68b75e7
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,123 @@
using System.Collections;
using UnityEngine.TestTools;
using NUnit.Framework;
#if ENABLE_VR && ENABLE_XR_MODULE
using UnityEngine.XR;
#endif
namespace UnityEngine.Rendering.Tests
{
class RuntimeProfilerTestBase
{
protected const int k_NumWarmupFrames = 10;
protected const int k_NumFramesToRender = 30;
protected DebugFrameTiming m_DebugFrameTiming;
protected GameObject m_ToCleanup;
[SetUp]
public void Setup()
{
if (!FrameTimingManager.IsFeatureEnabled())
Assert.Ignore("Frame timing stats are disabled in Player Settings, skipping test.");
if (Application.isBatchMode)
Assert.Ignore("Frame timing tests are not supported in batch mode, skipping test.");
if (GraphicsSettings.currentRenderPipeline == null)
Assert.Ignore("No active Render Pipeline is set, skipping test.");
// HACK #1 - really shouldn't have to do this here, but previous tests are leaking gameobjects
#pragma warning disable CS0618 // Type or member is obsolete
var objects = GameObject.FindObjectsByType<GameObject>(FindObjectsSortMode.InstanceID);
#pragma warning restore CS0618 // Type or member is obsolete
foreach (var o in objects)
{
// HACK #2 - must not destroy DebugUpdater
if (o.GetComponent<DebugUpdater>() == null)
CoreUtils.Destroy(o);
}
m_DebugFrameTiming = new DebugFrameTiming();
}
[TearDown]
public void TearDown()
{
if (m_ToCleanup != null)
CoreUtils.Destroy(m_ToCleanup);
}
protected IEnumerator Warmup()
{
for (int i = 0; i < k_NumWarmupFrames; i++)
yield return null;
m_DebugFrameTiming.Reset();
}
}
class RuntimeProfilerTests : RuntimeProfilerTestBase
{
[UnityTest]
public IEnumerator RuntimeProfilerGivesNonZeroOutput()
{
// GPU Frame Time support is partial (https://docs.unity3d.com/6000.6/Documentation/Manual/frame-timing-manager.html),
// so adding exceptions here.
bool supportsGpuFrameTime = true;
if ((Application.platform == RuntimePlatform.LinuxPlayer || Application.platform == RuntimePlatform.LinuxEditor) && SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLCore)
supportsGpuFrameTime = false; // Linux + OpenGLCore
if (Application.platform == RuntimePlatform.WebGLPlayer)
supportsGpuFrameTime = false; // WebGL/WebGPU
if ((Application.platform == RuntimePlatform.EmbeddedLinuxArm64 || Application.platform == RuntimePlatform.EmbeddedLinuxX64) && SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLES3)
supportsGpuFrameTime = false; // EmbeddedLinux GLES (no GL_EXT_disjoint_timer_query)
#if ENABLE_VR && ENABLE_XR_MODULE
if (XRSettings.enabled)
supportsGpuFrameTime = false; // XR
#endif
yield return Warmup();
m_ToCleanup = new GameObject();
var camera = m_ToCleanup.AddComponent<Camera>();
for (int i = 0; i < k_NumFramesToRender; i++)
{
m_DebugFrameTiming.UpdateFrameTiming();
var rr = new UnityEngine.Rendering.RenderPipeline.StandardRequest();
rr.destination = RenderTexture.GetTemporary(128, 128, 24, UnityEngine.Experimental.Rendering.GraphicsFormat.R8G8B8A8_SRGB);
rr.mipLevel = 0;
rr.slice = 0;
rr.face = CubemapFace.Unknown;
UnityEngine.Rendering.RenderPipeline.SubmitRenderRequest(camera, rr);
RenderTexture.ReleaseTemporary(rr.destination);
yield return null;
}
// After k_NumFramesToRender frames, we should have a valid average for every headline counter.
// A failure means the counter was zero on every captured frame. RenderThreadCPUFrameTime and
// MainThreadCPUPresentWaitTime are excluded because they can legitimately be zero (modes without
// a separate render thread; no vsync / no frame rate cap). GPUFrameTime is only asserted on
// platforms known to report it (see supportsGpuFrameTime above).
var avg = m_DebugFrameTiming.m_FrameHistory.SampleAverage;
var zeroAvg = new System.Collections.Generic.List<string>();
if (avg.FramesPerSecond <= 0f)
zeroAvg.Add(nameof(avg.FramesPerSecond));
if (avg.FullFrameTime <= 0f)
zeroAvg.Add(nameof(avg.FullFrameTime));
if (avg.MainThreadCPUFrameTime <= 0f)
zeroAvg.Add(nameof(avg.MainThreadCPUFrameTime));
if (supportsGpuFrameTime && avg.GPUFrameTime <= 0f)
zeroAvg.Add(nameof(avg.GPUFrameTime));
Assert.That(zeroAvg, Is.Empty,
$"After {k_NumFramesToRender} frames the following SampleAverage counters were zero: [{string.Join(", ", zeroAvg)}]. " +
$"All averages: FramesPerSecond={avg.FramesPerSecond}, FullFrameTime={avg.FullFrameTime}, " +
$"MainThreadCPUFrameTime={avg.MainThreadCPUFrameTime}, RenderThreadCPUFrameTime={avg.RenderThreadCPUFrameTime}, " +
$"GPUFrameTime={avg.GPUFrameTime} (asserted={supportsGpuFrameTime}), " +
$"MainThreadCPUPresentWaitTime={avg.MainThreadCPUPresentWaitTime}.");
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 40a73c4a33c05924f813df35844d8c71
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,19 @@
using UnityEngine;
using UnityEngine.Rendering;
using System.Collections.Generic;
namespace UnityEngine.Rendering.Tests
{
class SecondCustomRenderPipelineAsset : RenderPipelineAsset<SecondCustomRenderPipeline>
{
protected override RenderPipeline CreatePipeline()
=> new SecondCustomRenderPipeline();
}
class SecondCustomRenderPipeline : RenderPipeline
{
protected override void Render(ScriptableRenderContext context, List<Camera> cameras)
{
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9e8aba00446d421694464bffe924e9b2
timeCreated: 1681827840
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e824bfe8a04b4926aed8619992f32c52
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,109 @@
using System;
using NUnit.Framework;
using UnityEngine.Rendering.UnifiedRayTracing;
namespace UnityEngine.Rendering.Tests
{
[TestFixture("Compute")]
[TestFixture("Hardware")]
internal class SurfaceCacheUniformEnvLightTests
{
readonly RayTracingBackend m_Backend;
TestHarness m_Harness;
public SurfaceCacheUniformEnvLightTests(string backendAsString)
=> m_Backend = Enum.Parse<RayTracingBackend>(backendAsString);
[SetUp]
public void SetUp()
{
// Assert.Ignore (not Assume.That): UTR reports Ignore as Skipped but maps Inconclusive to failure.
if (SystemInfo.renderingThreadingMode == RenderingThreadingMode.NativeGraphicsJobsSplitThreading)
Assert.Ignore("Skipping under NativeGraphicsJobsSplitThreading; tracked in GFXLIGHT-2292.");
if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLCore ||
SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLES3)
Assert.Ignore("Skipping on OpenGL; SurfaceCache requires Vulkan/D3D12/Metal (SSBO binding budget).");
TestHarness.EnsureSupportedOrIgnore(m_Backend);
m_Harness = new TestHarness(m_Backend);
}
[TearDown]
public void TearDown() => m_Harness?.Dispose();
[Test]
public void Env_WhenNoLights_ThenZeroIrradiance()
{
m_Harness.SetPatches(
worldPositions: new[] { Vector3.zero },
worldNormals: new[] { Vector3.up },
cellIndices: new uint[] { 0 },
irradiances: new SHRGBL1[1]);
m_Harness.BeginFrame();
m_Harness.CommitWorld();
m_Harness.Estimate();
m_Harness.EndFrame();
for (uint i = 1; i < 16; i++)
{
m_Harness.BeginFrame();
m_Harness.Estimate();
m_Harness.EndFrame();
}
SHRGBL1 actual = m_Harness.ReadPatchIrradiance(patchIndex: 0);
TestHarness.AssertL0IrradianceApproximatelyEqual(
expected: default, actual, epsilon: 1e-4f);
}
[Test]
public void Env_WhenArbitraryNormalAndIntensity_ThenL0EqualsRadianceTimesPiToThe3Over2()
{
var radiance = new Color(0.7f, 2.0f, 0.3f);
m_Harness.World.SetEnvironmentColor(radiance);
Vector3 arbitraryNormal = new Vector3(0.42f, 0.7f, 0.39f).normalized;
m_Harness.SetPatches(
worldPositions: new[] { Vector3.zero },
worldNormals: new[] { arbitraryNormal },
cellIndices: new uint[] { 0 },
irradiances: new SHRGBL1[1]);
m_Harness.BeginFrame();
m_Harness.CommitWorld();
m_Harness.Estimate();
m_Harness.EndFrame();
for (uint i = 1; i < 16; i++)
{
m_Harness.BeginFrame();
m_Harness.Estimate();
m_Harness.EndFrame();
}
// The radiance SHL0 coefficient should be
// L_0 = ∫_H Y_0(w) L_env dw
// = 1/2 1/sqrt(π) L_env ∫_H dw
// = 1/2 1/sqrt(π) L_env 2π
// = sqrt(π) L_env
// where H is any hemisphere and L_env is uniform environment radiance.
// This value is converted to irradiance using Ramamoorthi's technique
// (https://cseweb.ucsd.edu/~ravir/papers/envmap/envmap.pdf),
// E_0 = A_hat_0 L_0 = π sqrt(π) L_env = π^(3/2) L_env,
// where E_0 denotes the L0 irradiance term.
float k = Mathf.Pow(Mathf.PI, 1.5f);
var expected = new SHRGBL1
{
L0 = new Vector3(radiance.r, radiance.g, radiance.b) * k,
L10 = Vector3.zero,
L11 = Vector3.zero,
L12 = Vector3.zero,
};
SHRGBL1 actual = m_Harness.ReadPatchIrradiance(patchIndex: 0);
TestHarness.AssertL0IrradianceApproximatelyEqual(expected, actual, epsilon: 1e-2f);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 15eae3b12d394b568db003d9e7f1862c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,260 @@
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using NUnit.Framework;
using UnityEngine.PathTracing.Core;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.UnifiedRayTracing;
namespace UnityEngine.Rendering.Tests
{
[StructLayout(LayoutKind.Sequential)]
internal struct SHRGBL1
{
public Vector3 L0;
public Vector3 L10;
public Vector3 L11;
public Vector3 L12;
}
internal sealed class TestHarness : IDisposable
{
sealed class WorldUpdatePassData
{
internal SurfaceCacheWorld World;
internal uint EnvCubemapResolution;
internal UnityEngine.Light Sun;
}
readonly RayTracingContext _rtContext;
readonly SurfaceCacheResourceSet _coreResources;
readonly SurfaceCache _cache;
readonly SurfaceCacheWorld _world;
readonly RenderGraph _renderGraph;
CommandBuffer _cmd;
GraphicsBuffer _worldScratch;
uint _frameIdx;
uint _outputIrradianceBufferIdx;
public SurfaceCacheWorld World => _world;
public SurfaceCacheResourceSet Resources => _coreResources;
// Call from [SetUp] before constructing the harness, so a skip fires before any GPU resource is allocated.
public static void EnsureSupportedOrIgnore(RayTracingBackend backend = RayTracingBackend.Compute)
{
// No UnifiedRayTracing compute backend on this platform; matches UnifiedRayTracing_RuntimeTests.
if (backend == RayTracingBackend.Compute && Application.platform == RuntimePlatform.PS5)
Assert.Ignore("SurfaceCache Core tests do not support the Compute ray-tracing backend on this platform.");
if (!RayTracingContext.IsBackendSupported(backend))
Assert.Ignore($"SurfaceCache Core tests require ray-tracing backend '{backend}' to be supported.");
if (SystemInfo.computeSubGroupSize <= 0)
Assert.Ignore("SurfaceCache Core tests require real wave-size / sub-group support.");
}
static RayTracingResources CreateRayTracingResources(TestResourceAsset testResources) => new RayTracingResources
{
geometryPoolKernels = testResources.geometryPoolKernels,
copyBuffer = testResources.copyBuffer,
copyPositions = testResources.copyPositions,
bitHistogram = testResources.bitHistogram,
blockReducePart = testResources.blockReducePart,
blockScan = testResources.blockScan,
buildHlbvh = testResources.buildHlbvh,
restructureBvh = testResources.restructureBvh,
scatter = testResources.scatter,
};
static WorldResourceSet CreateWorldResourceSet(TestResourceAsset testResources) => new WorldResourceSet
{
BlitCubemap = testResources.blitCubemap,
BlitGrayScaleCookie = testResources.blitGrayScaleCookie,
SetAlphaChannelShader = testResources.setAlphaChannelShader,
EnvironmentImportanceSamplingBuild = testResources.environmentImportanceSamplingBuild,
SkyBoxMesh = testResources.skyBoxMesh,
SixFaceSkyBoxMesh = testResources.sixFaceSkyBoxMesh,
BuildLightGridShader = testResources.buildLightGridShader,
SolidColorShader = testResources.solidColorShader,
};
public TestHarness(RayTracingBackend backend = RayTracingBackend.Compute)
{
var volParams = new SurfaceCacheVolumeParameterSet
{
Resolution = 4,
CascadeCount = 1,
Size = 1f
};
var testResources = UnityEngine.Resources.Load<TestResourceAsset>("TestShaders");
if (testResources == null)
Assert.Ignore(
"TestShaders.asset is not bundled in this project; the SurfaceCache tests run only where "
+ "the asset is bundled (the SurfaceCache_Core project). Skipping.");
_rtContext = new RayTracingContext(backend, CreateRayTracingResources(testResources));
Object punctualLightSamplingUnifiedObj;
Object estimationUnifiedObj;
if (backend == RayTracingBackend.Compute)
{
punctualLightSamplingUnifiedObj = testResources.punctualLightSamplingComputeShader;
estimationUnifiedObj = testResources.estimationComputeShader;
}
else
{
punctualLightSamplingUnifiedObj = testResources.punctualLightSamplingRayTracingShader;
estimationUnifiedObj = testResources.estimationRayTracingShader;
}
IRayTracingShader punctualLightSamplingShader = _rtContext.CreateRayTracingShader(punctualLightSamplingUnifiedObj);
IRayTracingShader estimationShader = _rtContext.CreateRayTracingShader(estimationUnifiedObj);
_coreResources = new SurfaceCacheResourceSet((uint)SystemInfo.computeSubGroupSize);
_coreResources.Load(
testResources.scrolling, testResources.eviction, testResources.patchAllocation,
testResources.spatialFiltering, testResources.temporalFiltering, testResources.defrag,
punctualLightSamplingShader, estimationShader);
foreach (var field in typeof(SurfaceCacheResourceSet).GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{
var value = field.GetValue(_coreResources);
if (typeof(Object).IsAssignableFrom(field.FieldType) || typeof(IRayTracingShader).IsAssignableFrom(field.FieldType))
Assert.That(value, Is.Not.Null, $"SurfaceCacheResourceSet.{field.Name} is null after Load().");
else if (field.FieldType == typeof(int) && field.Name.EndsWith("Kernel"))
Assert.That((int)value, Is.GreaterThanOrEqualTo(0), $"SurfaceCacheResourceSet.{field.Name} is -1 after Load().");
}
_cache = new SurfaceCache(_coreResources, volParams);
_cache.SetEstimationParams(new SurfaceCacheEstimationParameterSet
{
SampleCount = 1,
MultiBounce = false,
BouncePatchAllocation = false
});
_cache.SetPatchFilteringParams(new SurfaceCachePatchFilteringParameterSet
{
TemporalSmoothing = 0f,
SpatialFilterEnabled = false,
SpatialFilterSampleCount = 0,
SpatialFilterRadius = 0f,
TemporalPostFilterEnabled = false
});
_world = new SurfaceCacheWorld();
_world.Init(_rtContext, CreateWorldResourceSet(testResources));
_renderGraph = new RenderGraph("TestHarness");
}
public void BeginFrame()
{
// Fresh CommandBuffer per frame avoids a cross-frame buffer race under -force-gfx-jobs split.
_cmd = new CommandBuffer { name = $"TestHarness Frame {_frameIdx}" };
var rgParams = new RenderGraphParameters
{
commandBuffer = _cmd,
invalidContextForTesting = true,
currentFrameIndex = (int)_frameIdx,
};
_renderGraph.BeginRecording(rgParams);
}
public void CommitWorld(UnityEngine.Light sun = null)
{
using (var builder = _renderGraph.AddUnsafePass("Surface Cache World Update", out WorldUpdatePassData passData))
{
passData.World = _world;
passData.EnvCubemapResolution = 32;
passData.Sun = sun;
builder.AllowGlobalStateModification(true);
builder.SetRenderFunc((WorldUpdatePassData data, UnsafeGraphContext graphCtx) => UpdateWorld(data, graphCtx, ref _worldScratch));
}
}
public void Estimate()
{
_cache.RecordPreparation(_renderGraph, _frameIdx);
_outputIrradianceBufferIdx = _cache.RecordPatchUpdate(_renderGraph, _frameIdx, _world);
}
public void EndFrame()
{
_renderGraph.EndRecordingAndExecute();
Graphics.ExecuteCommandBuffer(_cmd);
_cmd.Dispose();
_cmd = null;
_frameIdx++;
}
static void UpdateWorld(WorldUpdatePassData data, UnsafeGraphContext graphCtx, ref GraphicsBuffer scratch)
{
var cmd = CommandBufferHelpers.GetNativeCommandBuffer(graphCtx.cmd);
data.World.Commit(cmd, ref scratch, data.EnvCubemapResolution, data.Sun, out _);
}
public SHRGBL1 ReadPatchIrradiance(uint patchIndex)
{
var data = new SHRGBL1[1];
_cache.Patches.Irradiances[_outputIrradianceBufferIdx].GetData(
data, 0, (int)patchIndex, 1);
return data[0];
}
// Seeds geometry, ring config, and irradiance buffers 0 and 2 directly.
public void SetPatches(
Vector3[] worldPositions,
Vector3[] worldNormals,
uint[] cellIndices,
SHRGBL1[] irradiances)
{
Debug.Assert(worldPositions.Length == worldNormals.Length);
Debug.Assert(worldPositions.Length == cellIndices.Length);
Debug.Assert(worldPositions.Length == irradiances.Length);
int count = worldPositions.Length;
var geometries = new float[count * 6];
for (int i = 0; i < count; ++i)
{
geometries[i * 6 + 0] = worldPositions[i].x;
geometries[i * 6 + 1] = worldPositions[i].y;
geometries[i * 6 + 2] = worldPositions[i].z;
geometries[i * 6 + 3] = worldNormals[i].x;
geometries[i * 6 + 4] = worldNormals[i].y;
geometries[i * 6 + 5] = worldNormals[i].z;
}
_cache.Patches.Geometries.SetData(geometries, 0, 0, geometries.Length);
_cache.Patches.CellIndices.SetData(cellIndices, 0, 0, count);
for (int i = 0; i < count; ++i)
_cache.Volume.CellPatchIndices.SetData(new[] { (uint)i }, 0, (int)cellIndices[i], 1);
_cache.RingConfig.Buffer.SetData(
new uint[] { (uint)count, 0, (uint)count }, 0, (int)_cache.RingConfig.OffsetA, 3);
_cache.Patches.Irradiances[0].SetData(irradiances, 0, 0, count);
_cache.Patches.Irradiances[2].SetData(irradiances, 0, 0, count);
}
public static void AssertL0IrradianceApproximatelyEqual(SHRGBL1 expected, SHRGBL1 actual, float epsilon)
{
AssertVector3ApproximatelyEqual(expected.L0, actual.L0, epsilon, "L0");
}
static void AssertVector3ApproximatelyEqual(Vector3 expected, Vector3 actual, float epsilon, string label)
{
Assert.AreEqual(expected.x, actual.x, epsilon, label + ".x");
Assert.AreEqual(expected.y, actual.y, epsilon, label + ".y");
Assert.AreEqual(expected.z, actual.z, epsilon, label + ".z");
}
public void Dispose()
{
_renderGraph.Cleanup();
_cache.Dispose();
_world.Dispose();
_rtContext.Dispose();
_worldScratch?.Dispose();
_cmd?.Dispose();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 27b9cb2c818b48b7b05f0d16b20b3f16
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,37 @@
namespace UnityEngine.Rendering.Tests
{
[CreateAssetMenu(menuName = "SurfaceCache/Test Shaders", fileName = "TestShaders")]
internal sealed class TestResourceAsset : ScriptableObject
{
public ComputeShader scrolling;
public ComputeShader eviction;
public ComputeShader patchAllocation;
public ComputeShader spatialFiltering;
public ComputeShader temporalFiltering;
public ComputeShader defrag;
public ComputeShader punctualLightSamplingComputeShader;
public RayTracingShader punctualLightSamplingRayTracingShader;
public ComputeShader estimationComputeShader;
public RayTracingShader estimationRayTracingShader;
public ComputeShader geometryPoolKernels;
public ComputeShader copyBuffer;
public ComputeShader copyPositions;
public ComputeShader bitHistogram;
public ComputeShader blockReducePart;
public ComputeShader blockScan;
public ComputeShader buildHlbvh;
public ComputeShader restructureBvh;
public ComputeShader scatter;
public ComputeShader blitCubemap;
public ComputeShader blitGrayScaleCookie;
public ComputeShader setAlphaChannelShader;
public ComputeShader environmentImportanceSamplingBuild;
public Mesh skyBoxMesh;
public Mesh sixFaceSkyBoxMesh;
public ComputeShader buildLightGridShader;
public Shader solidColorShader;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 81b438b79c4c45b47a4828b11e40fec6
@@ -0,0 +1,37 @@
{
"name": "Unity.SurfaceCache.Tests",
"rootNamespace": "",
"references": [
"GUID:b5b1bcf1950c2204f8e3959f64726eab",
"GUID:214c0945bb158c940aada223f3223ee8",
"GUID:c49c619b6af2be941af9bcbca2641964",
"GUID:d8b63aba1907145bea998dd612889d6b",
"GUID:df380645f10b7bc4b97d4f5eb6303d95"
],
"includePlatforms": [
"Android",
"Editor",
"LinuxStandalone64",
"macOSStandalone",
"PS5",
"WindowsStandalone64"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll"
],
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [
{
"name": "com.unity.modules.terrain",
"expression": "1.0.0",
"define": "ENABLE_TERRAIN_MODULE"
}
],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 3949f2582b2c4bce9fde2d52b829b107
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cdb007ea9cfd17c49ae406263e8c346d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,268 @@
using System.IO;
using System.Linq;
using NUnit.Framework;
using UnityEngine.TestTools;
using UnityEngine;
using UnityEngine.Rendering;
#if UNITY_EDITOR
using UnityEditor;
#endif
#if UNITY_ANDROID
using Unity.TestUtilities;
#endif
class ThreadingEmulationFunctionTests : IPrebuildSetup
{
private const string kPathToWaveShader = "Packages/com.unity.render-pipelines.core/Tests/Runtime/Threading/FunctionTestsWave.compute";
private const string kPathToGroupShader = "Packages/com.unity.render-pipelines.core/Tests/Runtime/Threading/FunctionTestsGroup.compute";
private static int kMaxTestBlockSize = 1024;
private ComputeShader m_waveCs;
private ComputeShader m_groupCs;
private GraphicsBuffer m_inputBuffer;
private GraphicsBuffer m_outputBuffer;
private int[] m_sequenceBufferData;
private int[] m_zeroedBufferData;
public enum Kernel
{
AllEqual,
AllTrue,
AnyTrue,
Ballot,
BitAnd,
BitOr,
BitXor,
CountBits,
Max,
Min,
Product,
Sum,
GetCount,
GetIndex,
IsFirst,
PrefixCountBits,
PrefixProduct,
PrefixSum,
ReadAtBroadcast,
ReadAtShuffle,
ReadFirst,
}
public enum WaveSizeKeyword
{
EMULATE_WAVE_SIZE_8,
EMULATE_WAVE_SIZE_16,
EMULATE_WAVE_SIZE_32,
EMULATE_WAVE_SIZE_64,
EMULATE_WAVE_SIZE_128,
WAVE_SIZE_NATIVE
}
public enum GroupSizeKeyword
{
GROUP_SIZE_1 = 1,
GROUP_SIZE_2 = 2,
GROUP_SIZE_4 = 4,
GROUP_SIZE_8 = 8,
GROUP_SIZE_16 = 16,
GROUP_SIZE_32 = 32,
GROUP_SIZE_64 = 64,
GROUP_SIZE_128 = 128,
GROUP_SIZE_256 = 256,
GROUP_SIZE_512 = 512,
GROUP_SIZE_1024 = 1024
}
string GetTestResourceName(string srcPath)
{
return "_" + Path.GetFileNameWithoutExtension(srcPath);
}
#if UNITY_EDITOR
void CopyTestResource(string srcPath)
{
string dstPath = Path.Join("Assets/Resources", GetTestResourceName(srcPath) + Path.GetExtension(srcPath));
AssetDatabase.CopyAsset(srcPath, dstPath);
}
#endif
// Note: We don't currently attempt to clean up these resources because other tests could be using
// the resources folder.
public void Setup()
{
#if UNITY_EDITOR
if (!AssetDatabase.IsValidFolder("Assets/Resources"))
{
AssetDatabase.CreateFolder("Assets", "Resources");
}
CopyTestResource(kPathToWaveShader);
CopyTestResource(kPathToGroupShader);
AssetDatabase.Refresh();
#endif
}
[OneTimeSetUp]
public void OneTimeSetup()
{
if (!SystemInfo.supportsComputeShaders)
Assert.Ignore("Compute shaders are a prerequisite for the threading tests and they aren't supported on the current device");
m_waveCs = Resources.Load<ComputeShader>(GetTestResourceName(kPathToWaveShader));
Assert.IsNotNull(m_waveCs, "Failed to find threading wave test compute shader kernel");
m_groupCs = Resources.Load<ComputeShader>(GetTestResourceName(kPathToGroupShader));
Assert.IsNotNull(m_groupCs, "Failed to find threading group test compute shader kernel");
m_sequenceBufferData = Enumerable.Range(0, kMaxTestBlockSize).ToArray();
m_zeroedBufferData = Enumerable.Repeat(0, kMaxTestBlockSize).ToArray();
m_inputBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Raw, kMaxTestBlockSize, 4);
m_outputBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Raw, kMaxTestBlockSize, 4);
m_inputBuffer.SetData(m_sequenceBufferData);
}
[OneTimeTearDown]
public void OneTimeCleanUp()
{
m_inputBuffer.Release();
m_outputBuffer.Release();
}
[SetUp]
public void SetUp()
{
// Zero the output buffer before each test case
m_outputBuffer.SetData(m_zeroedBufferData);
}
private static int[] GetBufferData(GraphicsBuffer buffer)
{
int[] result = null;
var cmdReadBack = CommandBufferPool.Get("Readback");
cmdReadBack.RequestAsyncReadback(buffer, req =>
{
if (req.done)
{
var data = req.GetData<int>();
result = data.ToArray();
}
});
cmdReadBack.WaitAllAsyncReadbackRequests();
Graphics.ExecuteCommandBuffer(cmdReadBack);
CommandBufferPool.Release(cmdReadBack);
return result;
}
private void ValidateDeviceComputeGroupSize(int requiredComputeGroupSizeX)
{
int deviceMaxComputeGroupSizeX = SystemInfo.maxComputeWorkGroupSizeX;
if (deviceMaxComputeGroupSizeX < requiredComputeGroupSizeX)
Assert.Ignore($"The device's max compute group size X dimension ({deviceMaxComputeGroupSizeX}) does not meet the minimum requirement ({requiredComputeGroupSizeX}) for this test");
}
[SetUp]
public void SetupIgnores()
{
if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.WebGPU)
{
Assert.Ignore("These tests are not supported on WebGPU");
}
}
[Test]
[Ignore("Unstable: https://jira.unity3d.com/browse/UUM-111743")]
public void WaveTest([Values]Kernel kernel, [Values]WaveSizeKeyword waveSizeKeyword)
{
#if UNITY_ANDROID
// ReadAtBroadcast with the native wave size returns 0 on the Samsung Galaxy S20 FE
// (Adreno 650, Vulkan). However, Quest2 is passing even though it has the same GPU,
// so it seems to be the S20 FE driver specifically, not Adreno 650 in general. Suspected
// driver/cross-compiler bug around WaveReadLaneAt with a WaveGetLaneCount()-derived
// (dynamically-uniform but not literal-constant) lane index — the shuffle variant works.
if (AndroidUtilities.AndroidDeviceId == "r8q" // r8q == Samsung Galaxy S20 FE
&& SystemInfo.graphicsDeviceType == GraphicsDeviceType.Vulkan
&& kernel == Kernel.ReadAtBroadcast
&& waveSizeKeyword == WaveSizeKeyword.WAVE_SIZE_NATIVE)
{
Assert.Ignore("ReadAtBroadcast/native wave broken on Adreno 650 / Samsung Galaxy S20 FE (Vulkan)");
}
#endif
int groupSize = (int)GroupSizeKeyword.GROUP_SIZE_128;
// Ensure that the wave tests are capable of running on the current device
ValidateDeviceComputeGroupSize(groupSize);
// When emulating wave sizes, a special shader keyword must be activated.
bool hasEmulatedWaveSize = waveSizeKeyword != WaveSizeKeyword.WAVE_SIZE_NATIVE;
var cmd = CommandBufferPool.Get("Threading Wave Test");
if (hasEmulatedWaveSize)
{
cmd.SetKeyword(m_waveCs, new LocalKeyword(m_waveCs, waveSizeKeyword.ToString()), true);
}
int kernelIndex = (int)kernel;
cmd.SetComputeBufferParam(m_waveCs, kernelIndex, "_Input", m_inputBuffer);
cmd.SetComputeBufferParam(m_waveCs, kernelIndex, "_Output", m_outputBuffer);
cmd.DispatchCompute(m_waveCs, kernelIndex, 1, 1, 1);
if (hasEmulatedWaveSize)
{
cmd.SetKeyword(m_waveCs, new LocalKeyword(m_waveCs, waveSizeKeyword.ToString()), false);
}
Graphics.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
// Read back and compare the results.
var result = GetBufferData(m_outputBuffer);
Assert.That(result, Has.Exactly(groupSize).EqualTo(1).And.Exactly(kMaxTestBlockSize - groupSize).EqualTo(0));
}
[Test]
[Ignore("Unstable: https://jira.unity3d.com/browse/UUM-111743")]
public void GroupTest([Values]Kernel kernel, [Values]GroupSizeKeyword groupSizeKeyword)
{
int groupSize = (int)groupSizeKeyword;
// Ensure that the group tests are capable of running on the current device
ValidateDeviceComputeGroupSize(groupSize);
var cmd = CommandBufferPool.Get("Threading Group Test");
cmd.SetKeyword(m_groupCs, new LocalKeyword(m_groupCs, groupSizeKeyword.ToString()), true);
int kernelIndex = (int)kernel;
cmd.SetComputeBufferParam(m_groupCs, kernelIndex, "_Input", m_inputBuffer);
cmd.SetComputeBufferParam(m_groupCs, kernelIndex, "_Output", m_outputBuffer);
cmd.DispatchCompute(m_groupCs, kernelIndex, 1, 1, 1);
cmd.SetKeyword(m_groupCs, new LocalKeyword(m_groupCs, groupSizeKeyword.ToString()), false);
Graphics.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
// Read back and compare the results.
var result = GetBufferData(m_outputBuffer);
Assert.That(result, Has.Exactly(groupSize).EqualTo(1).And.Exactly(kMaxTestBlockSize - groupSize).EqualTo(0));
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 59c8a12d341e781489ab4c7b1815b241
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,255 @@
#pragma multi_compile GROUP_SIZE_1 GROUP_SIZE_2 GROUP_SIZE_4 GROUP_SIZE_8 GROUP_SIZE_16 GROUP_SIZE_32 GROUP_SIZE_64 GROUP_SIZE_128 GROUP_SIZE_256 GROUP_SIZE_512 GROUP_SIZE_1024
// Warning: Keep this kernel order in sync with WaveKernel enum in WaveEmulationTests.cs
#pragma kernel kAllEqual
#pragma kernel kAllTrue
#pragma kernel kAnyTrue
#pragma kernel kBallot
#pragma kernel kBitAnd
#pragma kernel kBitOr
#pragma kernel kBitXor
#pragma kernel kCountBits
#pragma kernel kMax
#pragma kernel kMin
#pragma kernel kProduct
#pragma kernel kSum
#pragma kernel kGetThreadCount
#pragma kernel kGetThreadIndex
#pragma kernel kIsFirstThread
#pragma kernel kPrefixCountBits
#pragma kernel kPrefixProduct
#pragma kernel kPrefixSum
#pragma kernel kReadThreadAtBroadcast
#pragma kernel kReadThreadAtShuffle
#pragma kernel kReadThreadFirst
// Required to define before using the threading library.
#if defined(GROUP_SIZE_1)
#define THREADING_BLOCK_SIZE 1
#elif defined(GROUP_SIZE_2)
#define THREADING_BLOCK_SIZE 2
#elif defined(GROUP_SIZE_4)
#define THREADING_BLOCK_SIZE 4
#elif defined(GROUP_SIZE_8)
#define THREADING_BLOCK_SIZE 8
#elif defined(GROUP_SIZE_16)
#define THREADING_BLOCK_SIZE 16
#elif defined(GROUP_SIZE_32)
#define THREADING_BLOCK_SIZE 32
#elif defined(GROUP_SIZE_64)
#define THREADING_BLOCK_SIZE 64
#elif defined(GROUP_SIZE_128)
#define THREADING_BLOCK_SIZE 128
#elif defined(GROUP_SIZE_256)
#define THREADING_BLOCK_SIZE 256
#elif defined(GROUP_SIZE_512)
#define THREADING_BLOCK_SIZE 512
#elif defined(GROUP_SIZE_1024)
#define THREADING_BLOCK_SIZE 1024
#endif
#define THREADING_FORCE_WAVE_EMULATION
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Threading.hlsl"
typedef Threading::Group Group;
typedef Threading::GroupBallot GroupBallot;
ByteAddressBuffer _Input;
RWByteAddressBuffer _Output;
// Macros
#define DATA ((uint)_Input.Load(group.dispatchID.x << 2))
#define OUTPUT_ADDR ((uint)(group.groupIndex << 2))
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kAllEqual(Group group)
{
const uint result = (uint)group.AllEqual(DATA - group.groupIndex);
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kAllTrue(Group group)
{
const uint result = (uint)group.AllTrue(DATA == group.groupIndex);
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kAnyTrue(Group group)
{
const uint result = (uint)group.AnyTrue((DATA & (group.GetThreadCount() - 1)) == (group.GetThreadCount() / 2));
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kBallot(Group group)
{
GroupBallot ballot = group.Ballot(DATA == group.groupIndex);
uint numBits = ballot.CountBits();
const uint result = (numBits == group.GetThreadCount());
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kBitAnd(Group group)
{
const uint result = group.And(DATA == group.groupIndex);
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kBitOr(Group group)
{
const uint result = (group.Or(DATA != group.groupIndex) == 0);
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kBitXor(Group group)
{
const uint result = (group.Xor(DATA != group.groupIndex) == 0);
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kCountBits(Group group)
{
const uint result = (group.CountBits(DATA == group.groupIndex) == group.GetThreadCount());
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kMax(Group group)
{
const uint result = (group.Max(DATA & (group.GetThreadCount() - 1)) == (group.GetThreadCount() - 1));
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kMin(Group group)
{
const uint result = (group.Min(DATA & (group.GetThreadCount() - 1)) == 0);
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kProduct(Group group)
{
const uint result = group.Product((uint)(DATA == group.groupIndex));
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kSum(Group group)
{
const uint result = (group.Sum((uint)(DATA == group.groupIndex)) == group.GetThreadCount());
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kGetThreadCount(Group group)
{
#if defined(THREADING_WAVE_SIZE)
const uint result = (THREADING_BLOCK_SIZE == group.GetThreadCount());
#else
// If we don't know the group size at compile time, we don't really have any way of verifying this functionality.
// Unconditionally return 1 so this case always passes.
const uint result = 1;
#endif
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kGetThreadIndex(Group group)
{
const uint result = ((DATA & (group.GetThreadCount() - 1)) == group.GetThreadIndex());
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kIsFirstThread(Group group)
{
const uint result = (group.GetThreadIndex() != 0) ^ group.IsFirstThread();
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kPrefixCountBits(Group group)
{
const uint result = (group.PrefixCountBits(DATA == group.groupIndex) == group.GetThreadIndex());
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kPrefixProduct(Group group)
{
const uint value = ((group.GetThreadIndex() % 64) == 0) ? 2u : 1u;
const uint expectedValue = (1u << ((group.GetThreadIndex() / 64u) + 1));
const uint result = ((group.PrefixProduct(value) * value) == expectedValue);
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kPrefixSum(Group group)
{
const uint value = group.GetThreadIndex();
const uint expectedValue = (group.GetThreadIndex() * (group.GetThreadIndex() + 1u)) / 2u;
const uint result = ((group.PrefixSum(value) + value) == expectedValue);
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kReadThreadAtBroadcast(Group group)
{
const uint laneValue = DATA & (group.GetThreadCount() - 1);
const uint result = (group.ReadThreadAt(laneValue, (group.GetThreadCount() - 1)) == (group.GetThreadCount() - 1));
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kReadThreadAtShuffle(Group group)
{
const uint laneValue = DATA & (group.GetThreadCount() - 1);
const uint result = (group.ReadThreadShuffle(laneValue, laneValue) == laneValue);
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kReadThreadFirst(Group group)
{
const uint result = (group.ReadThreadFirst(DATA & (group.GetThreadCount() - 1)) == 0);
_Output.Store(OUTPUT_ADDR, result);
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 77f6c3e00d2daf84ba7b57e205d5dd64
ComputeShaderImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,299 @@
#pragma multi_compile _ EMULATE_WAVE_SIZE_8 EMULATE_WAVE_SIZE_16 EMULATE_WAVE_SIZE_32 EMULATE_WAVE_SIZE_64 EMULATE_WAVE_SIZE_128
#pragma multi_compile _ UNITY_DEVICE_SUPPORTS_WAVE_ANY
// Warning: Keep this kernel order in sync with Kernel enum in FunctionTests.cs
#pragma kernel kAllEqual
#pragma kernel kAllTrue
#pragma kernel kAnyTrue
#pragma kernel kBallot
#pragma kernel kBitAnd
#pragma kernel kBitOr
#pragma kernel kBitXor
#pragma kernel kCountBits
#pragma kernel kMax
#pragma kernel kMin
#pragma kernel kProduct
#pragma kernel kSum
#pragma kernel kGetLaneCount
#pragma kernel kGetLaneIndex
#pragma kernel kIsFirstLane
#pragma kernel kPrefixCountBits
#pragma kernel kPrefixProduct
#pragma kernel kPrefixSum
#pragma kernel kReadLaneAtBroadcast
#pragma kernel kReadLaneAtShuffle
#pragma kernel kReadLaneFirst
#if EMULATE_WAVE_SIZE_8
#define THREADING_WAVE_SIZE 8
#elif EMULATE_WAVE_SIZE_16
#define THREADING_WAVE_SIZE 16
#elif EMULATE_WAVE_SIZE_32
#define THREADING_WAVE_SIZE 32
#elif EMULATE_WAVE_SIZE_64
#define THREADING_WAVE_SIZE 64
#elif EMULATE_WAVE_SIZE_128
#define THREADING_WAVE_SIZE 128
#endif
// Required to define before using the threading library.
#define THREADING_BLOCK_SIZE 128
// BUG: There are currently a few issues preventing SM6 intrinsics from working on all platforms.
// For now, we avoid testing SM6 intrinsics on those platforms. Once the issues are resolved, this workaround should be removed to improve test coverage.
//
// Issues:
// 1. The BitXor, Product, and PrefixProduct SM6 intrinsics fail to compile on Xbox One because they aren't supported.
// We could potentially emulate these if necessary, but they aren't particularly useful.
// For now, we just skip the native path tests on Xbox One.
//
// 2. On some platforms, we probably cannot access SM6 instrinsics when using the HLSLcc pipeline
#define ENABLE_SM6_WORKAROUND (defined(SHADER_API_GAMECORE_XBOXONE) || defined(SHADER_API_XBOXONE) || defined(SHADER_API_SWITCH2))
// Force emulation on whenever a specific wave size is provided
#if defined(THREADING_WAVE_SIZE) || ENABLE_SM6_WORKAROUND
#define THREADING_FORCE_WAVE_EMULATION
#endif
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Threading.hlsl"
typedef Threading::Wave Wave;
typedef Threading::Group Group;
ByteAddressBuffer _Input;
RWByteAddressBuffer _Output;
// Macros
#define DATA ((uint)_Input.Load(group.dispatchID.x << 2))
#define OUTPUT_ADDR ((uint)(group.groupIndex << 2))
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kAllEqual(Group group)
{
Wave wave = group.GetWave();
const uint result = (uint)wave.AllEqual(DATA - group.groupIndex);
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kAllTrue(Group group)
{
Wave wave = group.GetWave();
const uint result = (uint)wave.AllTrue(DATA == group.groupIndex);
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kAnyTrue(Group group)
{
Wave wave = group.GetWave();
const uint result = (uint)wave.AnyTrue((DATA & (wave.GetLaneCount() - 1)) == (wave.GetLaneCount() / 2));
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kBallot(Group group)
{
Wave wave = group.GetWave();
uint4 ballot = wave.Ballot(DATA == group.groupIndex);
uint numBits = (countbits(ballot.x) + countbits(ballot.y) + countbits(ballot.z) + countbits(ballot.w));
const uint result = (numBits == wave.GetLaneCount());
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kBitAnd(Group group)
{
Wave wave = group.GetWave();
const uint result = wave.And(DATA == group.groupIndex);
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kBitOr(Group group)
{
Wave wave = group.GetWave();
const uint result = (wave.Or(DATA != group.groupIndex) == 0);
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kBitXor(Group group)
{
Wave wave = group.GetWave();
const uint result = (wave.Xor(DATA != group.groupIndex) == 0);
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kCountBits(Group group)
{
Wave wave = group.GetWave();
const uint result = (wave.CountBits(DATA == group.groupIndex) == wave.GetLaneCount());
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kMax(Group group)
{
Wave wave = group.GetWave();
const uint result = (wave.Max(DATA & (wave.GetLaneCount() - 1)) == (wave.GetLaneCount() - 1));
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kMin(Group group)
{
Wave wave = group.GetWave();
const uint result = (wave.Min(DATA & (wave.GetLaneCount() - 1)) == 0);
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kProduct(Group group)
{
Wave wave = group.GetWave();
const uint result = wave.Product((uint)(DATA == group.groupIndex));
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kSum(Group group)
{
Wave wave = group.GetWave();
const uint result = (wave.Sum((uint)(DATA == group.groupIndex)) == wave.GetLaneCount());
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kGetLaneCount(Group group)
{
#if defined(THREADING_WAVE_SIZE)
Wave wave = group.GetWave();
const uint result = (THREADING_WAVE_SIZE == wave.GetLaneCount());
#else
// If we don't know the wave size at compile time, we don't really have any way of verifying this functionality.
// Unconditionally return 1 so this case always passes.
const uint result = 1;
#endif
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kGetLaneIndex(Group group)
{
Wave wave = group.GetWave();
const uint result = ((DATA & (wave.GetLaneCount() - 1)) == wave.GetLaneIndex());
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kIsFirstLane(Group group)
{
Wave wave = group.GetWave();
const uint result = (wave.GetLaneIndex() != 0) ^ wave.IsFirstLane();
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kPrefixCountBits(Group group)
{
Wave wave = group.GetWave();
const uint result = (wave.PrefixCountBits(DATA == group.groupIndex) == wave.GetLaneIndex());
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kPrefixProduct(Group group)
{
Wave wave = group.GetWave();
const uint value = ((wave.GetLaneIndex() % 8) == 0) ? 2u : 1u;
const uint expectedValue = (1u << ((wave.GetLaneIndex() / 8u) + 1));
const uint result = ((wave.PrefixProduct(value) * value) == expectedValue);
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kPrefixSum(Group group)
{
Wave wave = group.GetWave();
const uint value = wave.GetLaneIndex();
const uint expectedValue = (wave.GetLaneIndex() * (wave.GetLaneIndex() + 1u)) / 2u;
const uint result = ((wave.PrefixSum(value) + value) == expectedValue);
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kReadLaneAtBroadcast(Group group)
{
Wave wave = group.GetWave();
const uint laneValue = DATA & (wave.GetLaneCount() - 1);
const uint result = (wave.ReadLaneAt(laneValue, (wave.GetLaneCount() - 1)) == (wave.GetLaneCount() - 1));
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kReadLaneAtShuffle(Group group)
{
Wave wave = group.GetWave();
const uint laneValue = DATA & (wave.GetLaneCount() - 1);
const uint result = (wave.ReadLaneAt(laneValue, laneValue) == laneValue);
_Output.Store(OUTPUT_ADDR, result);
}
[numthreads(THREADING_BLOCK_SIZE, 1, 1)]
void kReadLaneFirst(Group group)
{
Wave wave = group.GetWave();
const uint result = (wave.ReadLaneFirst(DATA & (wave.GetLaneCount() - 1)) == 0);
_Output.Store(OUTPUT_ADDR, result);
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 7cce01e3b2c07a34b8a6cfa8dcd99d0b
ComputeShaderImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,35 @@
{
"name": "Unity.RenderPipelines.Core.Runtime.Tests",
"rootNamespace": "",
"references": [
"GUID:df380645f10b7bc4b97d4f5eb6303d95",
"GUID:27619889b8ba8c24980f49ee34dbb44a",
"GUID:0acc523941302664db1f4e527237feb3",
"GUID:4fd6538c1c56b409fb53fdf0183170ec",
"Unity.TestUtilities"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll"
],
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [
{
"name": "com.unity.modules.xr",
"expression": "1.0.0",
"define": "ENABLE_XR_MODULE"
},
{
"name": "com.unity.modules.uielements",
"expression": "1.0.0",
"define": "ENABLE_UIELEMENTS_MODULE"
}
],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: bf043f86dbf1bda4398ec83eebe40b8c
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,136 @@
#if ENABLE_VR && ENABLE_XR_MODULE
using System;
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using UnityEngine.Experimental.Rendering;
using UnityEngine.XR;
namespace UnityEngine.Rendering.Tests
{
class XRDisplaySubsystemCoreTests
{
// Utils
protected IEnumerator WaitOneFrame()
{
yield return null;
}
Camera m_Camera;
XRDisplaySubsystem m_XRDisplay;
private RenderPipelineAsset m_RenderPipelineAsset;
[SetUp]
public void Setup()
{
var camObj = new GameObject();
camObj.AddComponent<Camera>();
m_Camera = camObj.GetComponent<Camera>();
camObj.tag = "MainCamera";
var displays = new List<XRDisplaySubsystem>();
SubsystemManager.GetSubsystems(displays);
// Skip tests
if (displays.Count == 0)
Assert.Ignore("No active XR provider found, skipping XRSystem core tests.");
m_XRDisplay = displays[0];
}
[TearDown]
public void TearDown()
{
if (m_Camera)
{
Object.Destroy(m_Camera.gameObject);
}
}
[UnityTest]
public IEnumerator SetOcclusionMeshScaleTest()
{
XRSRPSettings.occlusionMeshScale = 1.0f;
yield return WaitOneFrame();
// Set scale
float targetScale = 0.5f;
XRSRPSettings.occlusionMeshScale = targetScale;
yield return WaitOneFrame();
// Examine XRPass
float occlusionScale = 0;
var xrLayout = XRSystem.NewLayout();
xrLayout.AddCamera(m_Camera, true);
if (xrLayout.GetActivePasses().Count > 0)
{
(Camera _, XRPass xrPass) = xrLayout.GetActivePasses()[0];
{
occlusionScale = xrPass.occlusionMeshScale;
}
}
Assert.AreEqual(targetScale, occlusionScale);
yield return WaitOneFrame();
// Reset to default 1.0f
XRSRPSettings.occlusionMeshScale = 1.0f;
yield return WaitOneFrame();
}
[UnityTest]
public IEnumerator SetMirrorViewModeTest()
{
XRSRPSettings.mirrorViewMode = XRMirrorViewBlitMode.SideBySide;
yield return WaitOneFrame();
// Check XRDisplay mirror blit mode
int mirrorBlitMode = m_XRDisplay.GetPreferredMirrorBlitMode();
Assert.AreEqual(mirrorBlitMode, XRMirrorViewBlitMode.SideBySide);
// Repeat for Left mode
XRSRPSettings.mirrorViewMode = XRMirrorViewBlitMode.LeftEye;
yield return WaitOneFrame();
// Check XRDisplay mirror blit mode
mirrorBlitMode = m_XRDisplay.GetPreferredMirrorBlitMode();
Assert.AreEqual(mirrorBlitMode, XRMirrorViewBlitMode.LeftEye);
}
[UnityTest]
public IEnumerator CalculateViewCornersTest()
{
yield return WaitOneFrame();
// Retrieve XRPass
var xrLayout = XRSystem.NewLayout();
xrLayout.AddCamera(m_Camera, true);
XRPass firstPass = XRSystem.emptyPass;
if (xrLayout.GetActivePasses().Count > 0)
{
(Camera _, XRPass xrPass) = xrLayout.GetActivePasses()[0];
{
firstPass = xrPass;
}
}
// z 0 case: all corners should be zero
float z0 = 0;
Vector3[] corners = CoreUtils.CalculateViewSpaceCorners(firstPass.GetProjMatrix(0), z0);
Assert.AreEqual(Vector3.zero, corners[0]);
Assert.AreEqual(Vector3.zero, corners[1]);
Assert.AreEqual(Vector3.zero, corners[2]);
Assert.AreEqual(Vector3.zero, corners[3]);
// z2 corners = z1 corners * z2/z1
float z1 = 1;
float z2 = 2;
Vector3[] cornersZ1 = CoreUtils.CalculateViewSpaceCorners(firstPass.GetProjMatrix(0), z1 /*z*/);
Vector3[] cornersZ2 = CoreUtils.CalculateViewSpaceCorners(firstPass.GetProjMatrix(0), z2 /*z*/);
Assert.AreEqual(cornersZ1[0] * z2 / z1, cornersZ2[0]);
Assert.AreEqual(cornersZ1[1] * z2 / z1, cornersZ2[1]);
Assert.AreEqual(cornersZ1[2] * z2 / z1, cornersZ2[2]);
Assert.AreEqual(cornersZ1[3] * z2 / z1, cornersZ2[3]);
}
}
}
#endif
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0cf9c2a50a752824ba2300f36a07abed
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: