diff --git a/TestProjects/HDRP_Tests/ProjectSettings/ProjectSettings.asset b/TestProjects/HDRP_Tests/ProjectSettings/ProjectSettings.asset index 29561b261c5..f9ccf028def 100644 --- a/TestProjects/HDRP_Tests/ProjectSettings/ProjectSettings.asset +++ b/TestProjects/HDRP_Tests/ProjectSettings/ProjectSettings.asset @@ -153,7 +153,7 @@ PlayerSettings: enable360StereoCapture: 0 isWsaHolographicRemotingEnabled: 0 protectGraphicsMemory: 0 - enableFrameTimingStats: 0 + enableFrameTimingStats: 1 useHDRDisplay: 0 m_ColorGamuts: 00000000 targetPixelDensity: 30 diff --git a/com.unity.render-pipelines.core/CHANGELOG.md b/com.unity.render-pipelines.core/CHANGELOG.md index 3f27285ea34..3d874c801aa 100644 --- a/com.unity.render-pipelines.core/CHANGELOG.md +++ b/com.unity.render-pipelines.core/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Added support for high performant unsafe (uint only) Radix, Merge and Insertion sort algorithms on CoreUnsafeUtils. +- Added DebugFrameTiming class that can be used by render pipelines to display CPU/GPU frame timings and bottlenecks in Rendering Debugger. +- Added new DebugUI widget types: ProgressBarValue and ValueTuple ## [13.1.0] - 2021-09-24 diff --git a/com.unity.render-pipelines.core/Editor/Debugging/DebugUIDrawer.Builtins.cs b/com.unity.render-pipelines.core/Editor/Debugging/DebugUIDrawer.Builtins.cs index 6c035f5fdca..9e9b2839b3c 100644 --- a/com.unity.render-pipelines.core/Editor/Debugging/DebugUIDrawer.Builtins.cs +++ b/com.unity.render-pipelines.core/Editor/Debugging/DebugUIDrawer.Builtins.cs @@ -21,7 +21,70 @@ public override bool OnGUI(DebugUI.Widget widget, DebugState state) { var w = Cast(widget); var rect = PrepareControlRect(); - EditorGUI.LabelField(rect, EditorGUIUtility.TrTextContent(w.displayName), EditorGUIUtility.TrTextContent(w.GetValue().ToString())); + var value = w.GetValue(); + EditorGUI.LabelField(rect, EditorGUIUtility.TrTextContent(w.displayName), EditorGUIUtility.TrTextContent(w.FormatString(value))); + return true; + } + } + + /// + /// Builtin Drawer for ValueTuple Debug Items. + /// + [DebugUIDrawer(typeof(DebugUI.ValueTuple))] + public sealed class DebugUIDrawerValueTuple : DebugUIDrawer + { + /// + /// OnGUI implementation for ValueTuple DebugUIDrawer. + /// + /// DebugUI Widget. + /// Debug State associated with the Debug Item. + /// The state of the widget. + public override bool OnGUI(DebugUI.Widget widget, DebugState state) + { + var w = Cast(widget); + + var labelRect = PrepareControlRect(); + EditorGUI.PrefixLabel(labelRect, EditorGUIUtility.TrTextContent(w.displayName)); + + // Following layout should match DebugUIDrawerFoldout to make column labels align + Rect drawRect = GUILayoutUtility.GetLastRect(); + int indent = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; //be at left of rects + for (int i = 0; i < w.numElements; i++) + { + var columnRect = drawRect; + columnRect.x += EditorGUIUtility.labelWidth + i * DebugWindow.Styles.foldoutColumnWidth; + columnRect.width = DebugWindow.Styles.foldoutColumnWidth; + var value = w.values[i].GetValue(); + EditorGUI.LabelField(columnRect, w.values[i].FormatString(value)); + } + EditorGUI.indentLevel = indent; + + return true; + } + } + + /// + /// Builtin Drawer for ProgressBarValue Debug Items. + /// + [DebugUIDrawer(typeof(DebugUI.ProgressBarValue))] + public sealed class DebugUIDrawerProgressBarValue : DebugUIDrawer + { + /// + /// OnGUI implementation for Value DebugUIDrawer. + /// + /// DebugUI Widget. + /// Debug State associated with the Debug Item. + /// The state of the widget. + public override bool OnGUI(DebugUI.Widget widget, DebugState state) + { + var w = Cast(widget); + + var labelRect = PrepareControlRect(); + var progressBarRect = EditorGUI.PrefixLabel(labelRect, EditorGUIUtility.TrTextContent(w.displayName)); + float value = (float)w.GetValue(); + EditorGUI.ProgressBar(progressBarRect, value, w.FormatString(value)); + return true; } } @@ -435,14 +498,13 @@ public override void Begin(DebugUI.Widget widget, DebugState state) Rect drawRect = GUILayoutUtility.GetLastRect(); if (w.columnLabels != null && value) { - const int oneColumnWidth = 70; int indent = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; //be at left of rects for (int i = 0; i < w.columnLabels.Length; i++) { var columnRect = drawRect; - columnRect.x += EditorGUIUtility.labelWidth + i * oneColumnWidth; - columnRect.width = oneColumnWidth; + columnRect.x += EditorGUIUtility.labelWidth + i * DebugWindow.Styles.foldoutColumnWidth; + columnRect.width = DebugWindow.Styles.foldoutColumnWidth; string label = w.columnLabels[i] ?? ""; string tooltip = w.columnTooltips?.ElementAtOrDefault(i) ?? ""; EditorGUI.LabelField(columnRect, EditorGUIUtility.TrTextContent(label, tooltip), EditorStyles.miniBoldLabel); @@ -461,8 +523,8 @@ public override void Begin(DebugUI.Widget widget, DebugState state) /// The state of the widget. public override bool OnGUI(DebugUI.Widget widget, DebugState state) { - var s = Cast(state); - return s.value; + var w = Cast(widget); + return w.opened; } /// diff --git a/com.unity.render-pipelines.core/Editor/Debugging/DebugWindow.cs b/com.unity.render-pipelines.core/Editor/Debugging/DebugWindow.cs index 988c0059b46..09c64db15be 100644 --- a/com.unity.render-pipelines.core/Editor/Debugging/DebugWindow.cs +++ b/com.unity.render-pipelines.core/Editor/Debugging/DebugWindow.cs @@ -130,11 +130,6 @@ static void Init() { var window = GetWindow(); window.titleContent = Styles.windowTitle; - - if (OnDebugWindowToggled == null) - OnDebugWindowToggled += DebugManager.instance.ToggleEditorUI; - - open = true; } [MenuItem("Window/Analysis/Rendering Debugger", validate = true)] @@ -145,6 +140,11 @@ static bool ValidateMenuItem() void OnEnable() { + if (OnDebugWindowToggled == null) + OnDebugWindowToggled += DebugManager.instance.ToggleEditorUI; + + open = true; + DebugManager.instance.refreshEditorRequested = false; hideFlags = HideFlags.HideAndDontSave; @@ -564,6 +564,8 @@ public class Styles public readonly GUIStyle sectionHeader = new GUIStyle(EditorStyles.largeLabel); public readonly Color skinBackgroundColor; + public static int foldoutColumnWidth = 70; + public Styles() { Color textColorDarkSkin = new Color32(210, 210, 210, 255); diff --git a/com.unity.render-pipelines.core/Runtime/AssemblyInfo.cs b/com.unity.render-pipelines.core/Runtime/AssemblyInfo.cs index e25fbf83701..8866fe3ef3a 100644 --- a/com.unity.render-pipelines.core/Runtime/AssemblyInfo.cs +++ b/com.unity.render-pipelines.core/Runtime/AssemblyInfo.cs @@ -5,3 +5,4 @@ #endif [assembly: InternalsVisibleTo("Unity.RenderPipelines.Core.Editor")] +[assembly: InternalsVisibleTo("Unity.RenderPipelines.Core.Runtime.Tests")] diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/DebugDisplaySettingsPanel.cs b/com.unity.render-pipelines.core/Runtime/Debugging/DebugDisplaySettingsPanel.cs index 714632efb76..1a997149291 100644 --- a/com.unity.render-pipelines.core/Runtime/Debugging/DebugDisplaySettingsPanel.cs +++ b/com.unity.render-pipelines.core/Runtime/Debugging/DebugDisplaySettingsPanel.cs @@ -8,6 +8,7 @@ public abstract class DebugDisplaySettingsPanel : IDebugDisplaySettingsPanelDisp public abstract string PanelName { get; } public DebugUI.Widget[] Widgets => m_Widgets.ToArray(); + public virtual DebugUI.Flags Flags => DebugUI.Flags.None; protected void AddWidget(DebugUI.Widget widget) { diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/DebugDisplaySettingsUI.cs b/com.unity.render-pipelines.core/Runtime/Debugging/DebugDisplaySettingsUI.cs index 6eba122fd48..658eca3f1d0 100644 --- a/com.unity.render-pipelines.core/Runtime/Debugging/DebugDisplaySettingsUI.cs +++ b/com.unity.render-pipelines.core/Runtime/Debugging/DebugDisplaySettingsUI.cs @@ -39,6 +39,7 @@ public void RegisterDebug(IDebugDisplaySettings settings) DebugUI.Panel panel = debugManager.GetPanel(panelId, true); ObservableList panelChildren = panel.children; + panel.flags = disposableSettingsPanel.Flags; panels.Add(disposableSettingsPanel); panelChildren.Add(panelWidgets); }; diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/DebugFrameTiming.cs b/com.unity.render-pipelines.core/Runtime/Debugging/DebugFrameTiming.cs new file mode 100644 index 00000000000..65023880049 --- /dev/null +++ b/com.unity.render-pipelines.core/Runtime/Debugging/DebugFrameTiming.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +//#define RTPROFILER_DEBUG + +namespace UnityEngine.Rendering +{ + public class DebugFrameTiming + { + const string k_FpsFormatString = "{0:F1}"; + const string k_MsFormatString = "{0:F2}ms"; + const float k_RefreshRate = 1f / 5f; + + internal FrameTimeSampleHistory m_FrameHistory = new(); + internal BottleneckHistory m_BottleneckHistory = new(); + + /// + /// Size of the Bottleneck History Window in number of samples. + /// + public int bottleneckHistorySize { get; set; } = 60; + + /// + /// Size of the Sample History Window in number of samples. + /// + public int sampleHistorySize { get; set; } = 30; + + FrameTiming[] m_Timing = new FrameTiming[1]; + FrameTimeSample m_Sample = new FrameTimeSample(); + + /// + /// Update timing data from profiling counters. + /// + public void UpdateFrameTiming() + { + m_Timing[0] = default; + m_Sample = default; + FrameTimingManager.CaptureFrameTimings(); + FrameTimingManager.GetLatestTimings(1, m_Timing); + + if (m_Timing.Length > 0) + { + m_Sample.FullFrameTime = (float)m_Timing.First().cpuFrameTime; + m_Sample.FramesPerSecond = m_Sample.FullFrameTime > 0f ? 1000f / m_Sample.FullFrameTime : 0f; + m_Sample.MainThreadCPUFrameTime = (float)m_Timing.First().cpuMainThreadFrameTime; + m_Sample.MainThreadCPUPresentWaitTime = (float)m_Timing.First().cpuMainThreadPresentWaitTime; + m_Sample.RenderThreadCPUFrameTime = (float)m_Timing.First().cpuRenderThreadFrameTime; + m_Sample.GPUFrameTime = (float)m_Timing.First().gpuFrameTime; + } + + m_FrameHistory.DiscardOldSamples(sampleHistorySize); + m_FrameHistory.Add(m_Sample); + m_FrameHistory.ComputeAggregateValues(); + + m_BottleneckHistory.DiscardOldSamples(bottleneckHistorySize); + m_BottleneckHistory.AddBottleneckFromAveragedSample(m_FrameHistory.SampleAverage); + m_BottleneckHistory.ComputeHistogram(); + } + + /// + /// Add frame timing data widgets to debug UI. + /// + /// List of widgets to add the stats. + public void RegisterDebugUI(List list) + { + list.Add(new DebugUI.Foldout() + { + displayName = "Frame Stats", + opened = true, + columnLabels = new string[] { "Avg", "Min", "Max" }, + children = + { + new DebugUI.ValueTuple + { + displayName = "Frame Rate (FPS)", + values = new[] + { + new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_FpsFormatString, getter = () => m_FrameHistory.SampleAverage.FramesPerSecond }, + new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_FpsFormatString, getter = () => m_FrameHistory.SampleMin.FramesPerSecond }, + new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_FpsFormatString, getter = () => m_FrameHistory.SampleMax.FramesPerSecond }, + } + }, + new DebugUI.ValueTuple + { + displayName = "Frame Time", + values = new[] + { + new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleAverage.FullFrameTime }, + new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMin.FullFrameTime }, + new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMax.FullFrameTime }, + } + }, + new DebugUI.ValueTuple + { + displayName = "CPU Main Thread Frame", + values = new[] + { + new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleAverage.MainThreadCPUFrameTime }, + new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMin.MainThreadCPUFrameTime }, + new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMax.MainThreadCPUFrameTime }, + } + }, + new DebugUI.ValueTuple + { + displayName = "CPU Render Thread Frame", + values = new[] + { + new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleAverage.RenderThreadCPUFrameTime }, + new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMin.RenderThreadCPUFrameTime }, + new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMax.RenderThreadCPUFrameTime }, + } + }, + new DebugUI.ValueTuple + { + displayName = "CPU Present Wait", + values = new[] + { + new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleAverage.MainThreadCPUPresentWaitTime }, + new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMin.MainThreadCPUPresentWaitTime }, + new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMax.MainThreadCPUPresentWaitTime }, + } + }, + new DebugUI.ValueTuple + { + displayName = "GPU Frame", + values = new[] + { + new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleAverage.GPUFrameTime }, + new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMin.GPUFrameTime }, + new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_MsFormatString, getter = () => m_FrameHistory.SampleMax.GPUFrameTime }, + } + } + } + }); + + list.Add(new DebugUI.Foldout + { + displayName = "Bottlenecks", + children = + { +#if UNITY_EDITOR + new DebugUI.Container { displayName = "Not supported in Editor" } +#else + new DebugUI.ProgressBarValue { displayName = "CPU", getter = () => m_BottleneckHistory.Histogram.CPU }, + new DebugUI.ProgressBarValue { displayName = "GPU", getter = () => m_BottleneckHistory.Histogram.GPU }, + new DebugUI.ProgressBarValue { displayName = "Present limited", getter = () => m_BottleneckHistory.Histogram.PresentLimited }, + new DebugUI.ProgressBarValue { displayName = "Balanced", getter = () => m_BottleneckHistory.Histogram.Balanced }, +#endif + } + }); +#if RTPROFILER_DEBUG + list.Add(new DebugUI.Foldout + { + displayName = "Realtime Profiler Debug", + children = + { + new DebugUI.IntField + { + displayName = "Frame Time Sample History Size", + getter = () => SampleHistorySize, + setter = (value) => { SampleHistorySize = value; }, + min = () => 1, + max = () => 100 + }, + new DebugUI.IntField + { + displayName = "Bottleneck History Size", + getter = () => BottleneckHistorySize, + setter = (value) => { BottleneckHistorySize = value; }, + min = () => 1, + max = () => 100 + }, + new DebugUI.IntField + { + displayName = "Force VSyncCount", + min = () => - 1, + max = () => 4, + getter = () => QualitySettings.vSyncCount, + setter = (value) => { QualitySettings.vSyncCount = value; } + }, + new DebugUI.IntField + { + displayName = "Force TargetFrameRate", + min = () => - 1, + max = () => 1000, + getter = () => Application.targetFrameRate, + setter = (value) => { Application.targetFrameRate = value; } + }, + } + }); +#endif + } + + internal void Reset() + { + m_BottleneckHistory.Clear(); + m_FrameHistory.Clear(); + } + } +} diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/DebugFrameTiming.cs.meta b/com.unity.render-pipelines.core/Runtime/Debugging/DebugFrameTiming.cs.meta new file mode 100644 index 00000000000..70cbc1ceaa5 --- /dev/null +++ b/com.unity.render-pipelines.core/Runtime/Debugging/DebugFrameTiming.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5b966c0b6fb41a743ad89f19c17430eb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/DebugManager.Actions.cs b/com.unity.render-pipelines.core/Runtime/Debugging/DebugManager.Actions.cs index 467424842fb..94defb5feec 100644 --- a/com.unity.render-pipelines.core/Runtime/Debugging/DebugManager.Actions.cs +++ b/com.unity.render-pipelines.core/Runtime/Debugging/DebugManager.Actions.cs @@ -263,12 +263,14 @@ internal bool GetActionToggleDebugMenuWithTouch() var touchCount = touches.Count; InputSystem.TouchPhase? expectedTouchPhase = null; #else - var touches = Input.touches; var touchCount = Input.touchCount; TouchPhase? expectedTouchPhase = TouchPhase.Began; #endif if (touchCount == 3) { +#if !USE_INPUT_SYSTEM + var touches = Input.touches; // Causes an allocation, which is why this is inside the condition +#endif foreach (var touch in touches) { // Gesture: 3-finger double-tap diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/DebugManager.cs b/com.unity.render-pipelines.core/Runtime/Debugging/DebugManager.cs index a6f98db28f7..58b6e72c91f 100644 --- a/com.unity.render-pipelines.core/Runtime/Debugging/DebugManager.cs +++ b/com.unity.render-pipelines.core/Runtime/Debugging/DebugManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; using UnityEngine.Assertions; using UnityEngine.Rendering.UI; using UnityEngine.UI; @@ -165,13 +166,28 @@ public bool displayPersistentRuntimeUI } } - DebugManager() + /// + /// Is any debug window or UI currently active. + /// + public bool isAnyDebugUIActive { - if (!Debug.isDebugBuild) - return; + get + { + return + displayRuntimeUI || displayPersistentRuntimeUI +#if UNITY_EDITOR + || displayEditorUI +#endif + ; + } + } + DebugManager() + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR RegisterInputs(); RegisterActions(); +#endif } /// @@ -265,20 +281,34 @@ void EnsurePersistentCanvas() } } - internal void TogglePersistent(DebugUI.Widget widget) + internal void TogglePersistent(DebugUI.Widget widget, int? forceTupleIndex = null) { if (widget == null) return; - var valueWidget = widget as DebugUI.Value; - if (valueWidget == null) + EnsurePersistentCanvas(); + + switch (widget) { - Debug.Log("Only DebugUI.Value items can be made persistent."); - return; + case DebugUI.Value value: + m_RootUIPersistentCanvas.Toggle(value); + break; + case DebugUI.ValueTuple valueTuple: + m_RootUIPersistentCanvas.Toggle(valueTuple, forceTupleIndex); + break; + case DebugUI.Container container: + // When container is toggled, we make sure that if there are ValueTuples, they all get the same element index. + int pinnedIndex = container.children.Max(w => (w as DebugUI.ValueTuple)?.pinnedElementIndex ?? -1); + foreach (var child in container.children) + { + if (child is DebugUI.Value || child is DebugUI.ValueTuple) + TogglePersistent(child, pinnedIndex); + } + break; + default: + Debug.Log("Only readonly items can be made persistent."); + break; } - - EnsurePersistentCanvas(); - m_RootUIPersistentCanvas.Toggle(valueWidget); } void OnPanelDirty(DebugUI.Panel panel) diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/DebugUI.Containers.cs b/com.unity.render-pipelines.core/Runtime/Debugging/DebugUI.Containers.cs index 1d14b81611b..0c36ebfe81e 100644 --- a/com.unity.render-pipelines.core/Runtime/Debugging/DebugUI.Containers.cs +++ b/com.unity.render-pipelines.core/Runtime/Debugging/DebugUI.Containers.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; namespace UnityEngine.Rendering { @@ -26,8 +27,9 @@ internal set m_Panel = value; // Bubble down - foreach (var child in children) - child.panel = value; + int numChildren = children.Count; + for (int i = 0; i < numChildren; i++) + children[i].panel = value; } } @@ -53,14 +55,19 @@ public Container(string displayName, ObservableList children) this.children = children; children.ItemAdded += OnItemAdded; children.ItemRemoved += OnItemRemoved; + + // Call OnAdded callback for already existing items to ensure their panel & parent are set + for (int i = 0; i < this.children.Count; i++) + OnItemAdded(this.children, new ListChangedEventArgs(i, this.children[i])); } internal override void GenerateQueryPath() { base.GenerateQueryPath(); - foreach (var child in children) - child.GenerateQueryPath(); + int numChildren = children.Count; + for (int i = 0; i < numChildren; i++) + children[i].GenerateQueryPath(); } /// @@ -106,8 +113,9 @@ public override int GetHashCode() int hash = 17; hash = hash * 23 + queryPath.GetHashCode(); - foreach (var child in children) - hash = hash * 23 + child.GetHashCode(); + int numChildren = children.Count; + for (int i = 0; i < numChildren; i++) + hash = hash * 23 + children[i].GetHashCode(); return hash; } diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/DebugUI.Panel.cs b/com.unity.render-pipelines.core/Runtime/Debugging/DebugUI.Panel.cs index aacb2402369..8d506f78e11 100644 --- a/com.unity.render-pipelines.core/Runtime/Debugging/DebugUI.Panel.cs +++ b/com.unity.render-pipelines.core/Runtime/Debugging/DebugUI.Panel.cs @@ -101,8 +101,9 @@ protected virtual void OnItemRemoved(ObservableList sender, ListChangedE /// public void SetDirty() { - foreach (var child in children) - child.GenerateQueryPath(); + int numChildren = children.Count; + for (int i = 0; i < numChildren; i++) + children[i].GenerateQueryPath(); onSetDirty(this); } @@ -116,8 +117,10 @@ public override int GetHashCode() int hash = 17; hash = hash * 23 + displayName.GetHashCode(); - foreach (var child in children) - hash = hash * 23 + child.GetHashCode(); + + int numChildren = children.Count; + for (int i = 0; i < numChildren; i++) + hash = hash * 23 + children[i].GetHashCode(); return hash; } diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/DebugUI.cs b/com.unity.render-pipelines.core/Runtime/Debugging/DebugUI.cs index 95bdf09128f..e685cb51ea2 100644 --- a/com.unity.render-pipelines.core/Runtime/Debugging/DebugUI.cs +++ b/com.unity.render-pipelines.core/Runtime/Debugging/DebugUI.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using UnityEngine.Assertions; namespace UnityEngine.Rendering @@ -223,6 +224,14 @@ public class Value : Widget /// public float refreshRate = 0.1f; + /// + /// Optional C# numeric format string, using following syntax: "{0[:numericFormatString]}" + /// See https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings + /// and https://docs.microsoft.com/en-us/dotnet/standard/base-types/composite-formatting + /// Example: 123.45678 with formatString "{0:F2} ms" --> "123.45 ms". + /// + public string formatString = null; + /// /// Constructor. /// @@ -235,11 +244,83 @@ public Value() /// Returns the value of the widget. /// /// The value of the widget. - public object GetValue() + public virtual object GetValue() { Assert.IsNotNull(getter); return getter(); } + + /// + /// Returns the formatted value string for display purposes. + /// + /// Value to be formatted. + /// The formatted value string. + public virtual string FormatString(object value) + { + return string.IsNullOrEmpty(formatString) ? $"{value}" : string.Format(formatString, value); + } + } + + /// + /// Progress bar value. + /// + public class ProgressBarValue : Value + { + /// + /// Minimum value. + /// + public float min = 0f; + /// + /// Maximum value. + /// + public float max = 1f; + + /// + /// Get the current progress string, remapped to [0, 1] range, representing the progress between min and max. + /// + /// Value to be formatted. + /// Formatted progress percentage string between 0% and 100%. + public override string FormatString(object value) + { + static float Remap01(float v, float x0, float y0) => (v - x0) / (y0 - x0); + + float clamped = Mathf.Clamp((float)value, min, max); + float percentage = Remap01(clamped, min, max); + return $"{percentage:P1}"; + } + } + + /// + /// Tuple of Value widgets for creating tabular UI. + /// + public class ValueTuple : Widget + { + /// + /// Number of elements in the tuple. + /// + public int numElements + { + get + { + Assert.IsTrue(values.Length > 0); + return values.Length; + } + } + + /// + /// Value widgets. + /// + public Value[] values; + + /// + /// Refresh rate for the read-only values (runtime only) + /// + public float refreshRate => values.FirstOrDefault()?.refreshRate ?? 0.1f; + + /// + /// The currently pinned element index, or -1 if none are pinned. + /// + public int pinnedElementIndex = -1; } } } diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/DebugUpdater.cs b/com.unity.render-pipelines.core/Runtime/Debugging/DebugUpdater.cs index 755f778679f..c89b18e3e45 100644 --- a/com.unity.render-pipelines.core/Runtime/Debugging/DebugUpdater.cs +++ b/com.unity.render-pipelines.core/Runtime/Debugging/DebugUpdater.cs @@ -6,6 +6,7 @@ #endif using System; using System.Collections; +using System.Diagnostics; using UnityEngine.EventSystems; namespace UnityEngine.Rendering @@ -18,11 +19,9 @@ class DebugUpdater : MonoBehaviour bool m_RuntimeUiWasVisibleLastFrame = false; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] + [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] static void RuntimeInit() { - if (!Debug.isDebugBuild) - return; - if (DebugManager.instance.enableRuntimeUI) EnableRuntime(); } diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/FrameTiming.meta b/com.unity.render-pipelines.core/Runtime/Debugging/FrameTiming.meta new file mode 100644 index 00000000000..737ed23bd72 --- /dev/null +++ b/com.unity.render-pipelines.core/Runtime/Debugging/FrameTiming.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0f240cc177d08124a9694021c19c0dc4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/FrameTiming/FrameTimeBottleneck.cs b/com.unity.render-pipelines.core/Runtime/Debugging/FrameTiming/FrameTimeBottleneck.cs new file mode 100644 index 00000000000..9a1bf32251c --- /dev/null +++ b/com.unity.render-pipelines.core/Runtime/Debugging/FrameTiming/FrameTimeBottleneck.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; + +namespace UnityEngine.Rendering +{ + /// + /// Represents a system bottleneck, meaning the factor that is most dominant in determining + /// the total frame time. + /// + internal enum PerformanceBottleneck + { + Indeterminate, // Cannot be determined + PresentLimited, // Limited by presentation (vsync or framerate cap) + CPU, // Limited by CPU (main and/or render thread) + GPU, // Limited by GPU + Balanced, // Limited by both CPU and GPU, i.e. well balanced + } + + /// + /// BottleneckHistogram represents the distribution of bottlenecks over the Bottleneck History Window, + /// the size of which is determined by . + /// + internal struct BottleneckHistogram + { + internal float PresentLimited; + internal float CPU; + internal float GPU; + internal float Balanced; + }; + + /// + /// Container class for bottleneck history with helper to calculate histogram. + /// + internal class BottleneckHistory + { + List m_Bottlenecks = new(); + + internal BottleneckHistogram Histogram; + + internal void DiscardOldSamples(int historySize) + { + while (m_Bottlenecks.Count >= historySize) + m_Bottlenecks.RemoveAt(0); + } + + internal void AddBottleneckFromAveragedSample(FrameTimeSample frameHistorySampleAverage) + { + var bottleneck = DetermineBottleneck(frameHistorySampleAverage); + m_Bottlenecks.Add(bottleneck); + } + + internal void ComputeHistogram() + { + var stats = new BottleneckHistogram(); + for (int i = 0; i < m_Bottlenecks.Count; i++) + { + switch (m_Bottlenecks[i]) + { + case PerformanceBottleneck.Balanced: + stats.Balanced++; + break; + case PerformanceBottleneck.CPU: + stats.CPU++; + break; + case PerformanceBottleneck.GPU: + stats.GPU++; + break; + case PerformanceBottleneck.PresentLimited: + stats.PresentLimited++; + break; + } + } + + stats.Balanced /= m_Bottlenecks.Count; + stats.CPU /= m_Bottlenecks.Count; + stats.GPU /= m_Bottlenecks.Count; + stats.PresentLimited /= m_Bottlenecks.Count; + + Histogram = stats; + } + + static PerformanceBottleneck DetermineBottleneck(FrameTimeSample s) + { + const float kNearFullFrameTimeThresholdPercent = 0.2f; + const float kNonZeroPresentWaitTimeMs = 0.5f; + + if (s.GPUFrameTime == 0 || s.MainThreadCPUFrameTime == 0) // In direct mode, render thread doesn't exist + return PerformanceBottleneck.Indeterminate; // Missing data + float fullFrameTimeWithMargin = (1f - kNearFullFrameTimeThresholdPercent) * s.FullFrameTime; + + // GPU time is close to frame time, CPU times are not + if (s.GPUFrameTime > fullFrameTimeWithMargin && + s.MainThreadCPUFrameTime < fullFrameTimeWithMargin && + s.RenderThreadCPUFrameTime < fullFrameTimeWithMargin) + return PerformanceBottleneck.GPU; + + // One of the CPU times is close to frame time, GPU is not + if (s.GPUFrameTime < fullFrameTimeWithMargin && + (s.MainThreadCPUFrameTime > fullFrameTimeWithMargin || + s.RenderThreadCPUFrameTime > fullFrameTimeWithMargin)) + return PerformanceBottleneck.CPU; + + // Main thread waited due to Vsync or target frame rate + if (s.MainThreadCPUPresentWaitTime > kNonZeroPresentWaitTimeMs) + { + // None of the times are close to frame time + if (s.GPUFrameTime < fullFrameTimeWithMargin && + s.MainThreadCPUFrameTime < fullFrameTimeWithMargin && + s.RenderThreadCPUFrameTime < fullFrameTimeWithMargin) + return PerformanceBottleneck.PresentLimited; + } + + return PerformanceBottleneck.Balanced; + } + + internal void Clear() + { + m_Bottlenecks.Clear(); + Histogram = new BottleneckHistogram(); + } + } +} diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/FrameTiming/FrameTimeBottleneck.cs.meta b/com.unity.render-pipelines.core/Runtime/Debugging/FrameTiming/FrameTimeBottleneck.cs.meta new file mode 100644 index 00000000000..f073804d990 --- /dev/null +++ b/com.unity.render-pipelines.core/Runtime/Debugging/FrameTiming/FrameTimeBottleneck.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 331aa10d229f4c2fb9a9240812fb45d6 +timeCreated: 1629185832 \ No newline at end of file diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/FrameTiming/FrameTimeSample.cs b/com.unity.render-pipelines.core/Runtime/Debugging/FrameTiming/FrameTimeSample.cs new file mode 100644 index 00000000000..ac4f04177d3 --- /dev/null +++ b/com.unity.render-pipelines.core/Runtime/Debugging/FrameTiming/FrameTimeSample.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; + +namespace UnityEngine.Rendering +{ + /// + /// Represents timing data captured from a single frame. + /// + internal struct FrameTimeSample + { + internal float FramesPerSecond; + internal float FullFrameTime; + internal float MainThreadCPUFrameTime; + internal float MainThreadCPUPresentWaitTime; + internal float RenderThreadCPUFrameTime; + internal float GPUFrameTime; + + internal FrameTimeSample(float initValue) + { + FramesPerSecond = initValue; + FullFrameTime = initValue; + MainThreadCPUFrameTime = initValue; + MainThreadCPUPresentWaitTime = initValue; + RenderThreadCPUFrameTime = initValue; + GPUFrameTime = initValue; + } + }; + + /// + /// Container class for sample history with helpers to calculate min, max and average in one pass. + /// + class FrameTimeSampleHistory + { + List m_Samples = new(); + + internal FrameTimeSample SampleAverage; + internal FrameTimeSample SampleMin; + internal FrameTimeSample SampleMax; + + internal void Add(FrameTimeSample sample) + { + m_Samples.Add(sample); + } + + // Helper functions + + static Func s_SampleValueAdd = (float value, float other) => + { + return value + other; + }; + + static Func s_SampleValueMin = (float value, float other) => + { + return other > 0 ? Mathf.Min(value, other) : value; + }; + + static Func s_SampleValueMax = (float value, float other) => + { + return Mathf.Max(value, other); + }; + + static Func s_SampleValueCountValid = (float value, float other) => + { + return other > 0 ? value + 1 : value; + }; + + static Func s_SampleValueEnsureValid = (float value, float other) => + { + return other > 0 ? value : 0; + }; + + static Func s_SampleValueDivide = (float value, float other) => + { + return other > 0 ? value / other : 0; + }; + + internal void ComputeAggregateValues() + { + void ForEachSampleMember(ref FrameTimeSample aggregate, FrameTimeSample sample, Func func) + { + aggregate.FramesPerSecond = func(aggregate.FramesPerSecond, sample.FramesPerSecond); + aggregate.FullFrameTime = func(aggregate.FullFrameTime, sample.FullFrameTime); + aggregate.MainThreadCPUFrameTime = func(aggregate.MainThreadCPUFrameTime, sample.MainThreadCPUFrameTime); + aggregate.MainThreadCPUPresentWaitTime = func(aggregate.MainThreadCPUPresentWaitTime, sample.MainThreadCPUPresentWaitTime); + aggregate.RenderThreadCPUFrameTime = func(aggregate.RenderThreadCPUFrameTime, sample.RenderThreadCPUFrameTime); + aggregate.GPUFrameTime = func(aggregate.GPUFrameTime, sample.GPUFrameTime); + }; + + FrameTimeSample average = new(); + FrameTimeSample min = new(float.MaxValue); + FrameTimeSample max = new(float.MinValue); + FrameTimeSample numValidSamples = new(); // Using the struct to record how many valid samples each field has + + for (int i = 0; i < m_Samples.Count; i++) + { + var s = m_Samples[i]; + + ForEachSampleMember(ref min, s, s_SampleValueMin); + ForEachSampleMember(ref max, s, s_SampleValueMax); + ForEachSampleMember(ref average, s, s_SampleValueAdd); + ForEachSampleMember(ref numValidSamples, s, s_SampleValueCountValid); + } + + ForEachSampleMember(ref min, numValidSamples, s_SampleValueEnsureValid); + ForEachSampleMember(ref max, numValidSamples, s_SampleValueEnsureValid); + ForEachSampleMember(ref average, numValidSamples, s_SampleValueDivide); + + SampleAverage = average; + SampleMin = min; + SampleMax = max; + } + + internal void DiscardOldSamples(int sampleHistorySize) + { + while (m_Samples.Count >= sampleHistorySize) + m_Samples.RemoveAt(0); + } + + internal void Clear() + { + m_Samples.Clear(); + } + } +} diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/FrameTiming/FrameTimeSample.cs.meta b/com.unity.render-pipelines.core/Runtime/Debugging/FrameTiming/FrameTimeSample.cs.meta new file mode 100644 index 00000000000..332166e0f19 --- /dev/null +++ b/com.unity.render-pipelines.core/Runtime/Debugging/FrameTiming/FrameTimeSample.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d7cc2267acce47fdae2ece838f34df48 +timeCreated: 1629185717 \ No newline at end of file diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/IDebugDisplaySettingsPanel.cs b/com.unity.render-pipelines.core/Runtime/Debugging/IDebugDisplaySettingsPanel.cs index a80f0b88b45..6a0522247e6 100644 --- a/com.unity.render-pipelines.core/Runtime/Debugging/IDebugDisplaySettingsPanel.cs +++ b/com.unity.render-pipelines.core/Runtime/Debugging/IDebugDisplaySettingsPanel.cs @@ -16,6 +16,11 @@ public interface IDebugDisplaySettingsPanel /// Widgets used by this panel. /// DebugUI.Widget[] Widgets { get; } + + /// + /// Flags to be applied to the top-level panel. + /// + DebugUI.Flags Flags { get; } } /// diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Resources/DebugUICanvas.prefab b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Resources/DebugUICanvas.prefab index bfe79e7f0ef..d58d527add4 100644 --- a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Resources/DebugUICanvas.prefab +++ b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Resources/DebugUICanvas.prefab @@ -179,3 +179,9 @@ MonoBehaviour: - type: UnityEngine.Rendering.DebugUI+MessageBox, Unity.RenderPipelines.Core.Runtime, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null prefab: {fileID: 224053494956566916, guid: 10a25524b0986f9488b430e2829bbbe8, type: 3} + - type: UnityEngine.Rendering.DebugUI+ProgressBarValue, Unity.RenderPipelines.Core.Runtime, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + prefab: {fileID: 224720214277421396, guid: d3770aaa3bbd8384aabab9ddd383e21e, type: 3} + - type: UnityEngine.Rendering.DebugUI+ValueTuple, Unity.RenderPipelines.Core.Runtime, + Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + prefab: {fileID: 224720214277421396, guid: a2148203dd960814ca5db0c293ceda35, type: 3} diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Resources/DebugUIPersistentCanvas.prefab b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Resources/DebugUIPersistentCanvas.prefab index 29d9048eeba..5e12d0fa277 100644 --- a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Resources/DebugUIPersistentCanvas.prefab +++ b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Resources/DebugUIPersistentCanvas.prefab @@ -30,6 +30,7 @@ RectTransform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 224556897823040040} m_RootOrder: 0 @@ -62,11 +63,11 @@ MonoBehaviour: m_Material: {fileID: 0} m_Color: {r: 0.1, g: 0.1, b: 0.1, a: 0.8509804} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, - Version=1.0.0.0, Culture=neutral, PublicKeyToken=null m_Sprite: {fileID: 21300000, guid: 127279d577f25ac4ea17dae3782e5074, type: 3} m_Type: 0 m_PreserveAspect: 0 @@ -102,6 +103,7 @@ MonoBehaviour: m_ChildControlHeight: 0 m_ChildScaleWidth: 0 m_ChildScaleHeight: 0 + m_ReverseArrangement: 0 --- !u!114 &114307594989265542 MonoBehaviour: m_ObjectHideFlags: 0 @@ -130,7 +132,7 @@ GameObject: - component: {fileID: 114213191034542798} - component: {fileID: 114605181728370468} m_Layer: 5 - m_Name: DebugUI Persistent Canvas + m_Name: DebugUIPersistentCanvas m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 @@ -146,6 +148,7 @@ RectTransform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 224499400523491650} m_Father: {fileID: 0} @@ -174,6 +177,7 @@ Canvas: m_OverridePixelPerfect: 0 m_SortingBucketNormalizedSize: 0 m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 m_SortingLayerID: 0 m_SortingOrder: 0 m_TargetDisplay: 0 @@ -199,6 +203,7 @@ MonoBehaviour: m_FallbackScreenDPI: 96 m_DefaultSpriteDPI: 96 m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 --- !u!114 &114213191034542798 MonoBehaviour: m_ObjectHideFlags: 0 @@ -229,5 +234,4 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: panel: {fileID: 224499400523491650} - valuePrefab: {fileID: 224720214277421396, guid: dc0f88987826e6e48b1fe9c7c2b53a53, - type: 3} + valuePrefab: {fileID: 224720214277421396, guid: 8778e6b26b51c6a4999b94d7cacd8b5d, type: 3} diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerEnumHistory.cs b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerEnumHistory.cs index e61e6e9fc76..c0fe205dbdb 100644 --- a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerEnumHistory.cs +++ b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerEnumHistory.cs @@ -9,17 +9,18 @@ namespace UnityEngine.Rendering.UI public class DebugUIHandlerEnumHistory : DebugUIHandlerEnumField { Text[] historyValues; - const float xDecal = 60f; + const float k_XOffset = 230f; internal override void SetWidget(DebugUI.Widget widget) { int historyDepth = (widget as DebugUI.HistoryEnumField)?.historyDepth ?? 0; historyValues = new Text[historyDepth]; + float columnOffset = historyDepth > 0 ? k_XOffset / (float)historyDepth : 0f; for (int index = 0; index < historyDepth; ++index) { var historyValue = Instantiate(valueLabel, transform); Vector3 pos = historyValue.transform.position; - pos.x += (index + 1) * xDecal; + pos.x += (index + 1) * columnOffset; historyValue.transform.position = pos; var text = historyValue.GetComponent(); text.color = new Color32(110, 110, 110, 255); diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerFoldout.cs b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerFoldout.cs index b2c862b1db2..40f4de781a2 100644 --- a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerFoldout.cs +++ b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerFoldout.cs @@ -15,8 +15,8 @@ public class DebugUIHandlerFoldout : DebugUIHandlerWidget DebugUI.Foldout m_Field; DebugUIHandlerContainer m_Container; - const float xDecal = 60f; - const float xDecalInit = 230f; + const float k_FoldoutXOffset = 215f; + const float k_XOffset = 230f; internal override void SetWidget(DebugUI.Widget widget) { @@ -26,6 +26,7 @@ internal override void SetWidget(DebugUI.Widget widget) nameLabel.text = m_Field.displayName; int columnNumber = m_Field.columnLabels?.Length ?? 0; + float columnOffset = columnNumber > 0 ? k_XOffset / (float)columnNumber : 0f; for (int index = 0; index < columnNumber; ++index) { var column = Instantiate(nameLabel.gameObject, GetComponent().contentHolder); @@ -35,7 +36,7 @@ internal override void SetWidget(DebugUI.Widget widget) rectTransform.anchorMax = rectTransform.anchorMin = new Vector2(0, 1); rectTransform.sizeDelta = new Vector2(100, 26); Vector3 pos = originalTransform.anchoredPosition; - pos.x += (index + 1) * xDecal + xDecalInit; + pos.x += (index + 1) * columnOffset + k_FoldoutXOffset; rectTransform.anchoredPosition = pos; rectTransform.pivot = new Vector2(0, 0.5f); rectTransform.eulerAngles = new Vector3(0, 0, 13); diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerPersistentCanvas.cs b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerPersistentCanvas.cs index 2babbcee88e..bf08422c75a 100644 --- a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerPersistentCanvas.cs +++ b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerPersistentCanvas.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using UnityEngine.Rendering; @@ -10,7 +11,8 @@ class DebugUIHandlerPersistentCanvas : MonoBehaviour List m_Items = new List(); - internal void Toggle(DebugUI.Value widget) + // Toggles persistent value widget on/off. + internal void Toggle(DebugUI.Value widget, string displayName = null) { int index = m_Items.FindIndex(x => x.GetWidget() == widget); @@ -25,17 +27,62 @@ internal void Toggle(DebugUI.Value widget) // Add var go = Instantiate(valuePrefab, panel, false).gameObject; - go.name = widget.displayName; var uiHandler = go.GetComponent(); uiHandler.SetWidget(widget); + uiHandler.nameLabel.text = string.IsNullOrEmpty(displayName) ? widget.displayName : displayName; m_Items.Add(uiHandler); } - internal void Clear() + List m_ValueTupleWidgets = new(); + + // For ValueTuples (multiple values on one row), we cycle through the columns, and turn the widget + // off after the last column. + internal void Toggle(DebugUI.ValueTuple widget, int? forceTupleIndex = null) { - if (m_Items == null) - return; + var val = m_ValueTupleWidgets.Find(x => x == widget); + int tupleIndex = val?.pinnedElementIndex ?? -1; + + // Clear old widget + if (val != null) + { + m_ValueTupleWidgets.Remove(val); + Toggle(widget.values[tupleIndex]); + } + + if (forceTupleIndex != null) + tupleIndex = forceTupleIndex.Value; + // Enable next widget (unless at the last index) + if (tupleIndex + 1 < widget.numElements) + { + widget.pinnedElementIndex = tupleIndex + 1; + // Add column to name + string displayName = widget.displayName; + if (widget.parent is DebugUI.Foldout) + { + var columnLabels = (widget.parent as DebugUI.Foldout).columnLabels; + if (columnLabels != null && widget.pinnedElementIndex < columnLabels.Length) + { + displayName += $" ({columnLabels[widget.pinnedElementIndex]})"; + } + } + + Toggle(widget.values[widget.pinnedElementIndex], displayName); + m_ValueTupleWidgets.Add(widget); + } + else + { + widget.pinnedElementIndex = -1; + } + } + + internal bool IsEmpty() + { + return m_Items.Count == 0; + } + + internal void Clear() + { foreach (var item in m_Items) CoreUtils.Destroy(item.gameObject); diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerProgressBar.cs b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerProgressBar.cs new file mode 100644 index 00000000000..a21a1dcaf8a --- /dev/null +++ b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerProgressBar.cs @@ -0,0 +1,79 @@ +using System; +using UnityEngine.UI; + +namespace UnityEngine.Rendering.UI +{ + /// + /// DebugUIHandler for progress bar widget. + /// + public class DebugUIHandlerProgressBar : DebugUIHandlerWidget + { + /// Name of the progress bar. + public Text nameLabel; + /// Value of the progress bar. + public Text valueLabel; + /// Rectangle representing the progress bar. + public RectTransform progressBarRect; + + DebugUI.ProgressBarValue m_Value; + + float m_Timer; + + /// + /// OnEnable implementation. + /// + protected override void OnEnable() + { + m_Timer = 0f; + } + + internal override void SetWidget(DebugUI.Widget widget) + { + base.SetWidget(widget); + m_Value = CastWidget(); + nameLabel.text = m_Value.displayName; + UpdateValue(); + } + + /// + /// OnSelection implementation. + /// + /// True if the selection wrapped around. + /// Previous widget. + /// True if the selection is allowed. + public override bool OnSelection(bool fromNext, DebugUIHandlerWidget previous) + { + nameLabel.color = colorSelected; + return true; + } + + /// + /// OnDeselection implementation. + /// + public override void OnDeselection() + { + nameLabel.color = colorDefault; + } + + void Update() + { + if (m_Timer >= m_Value.refreshRate) + { + UpdateValue(); + m_Timer -= m_Value.refreshRate; + } + + m_Timer += Time.deltaTime; + } + + void UpdateValue() + { + float value = (float)m_Value.GetValue(); + valueLabel.text = m_Value.FormatString(value); + + Vector3 scale = progressBarRect.localScale; + scale.x = value; + progressBarRect.localScale = scale; + } + } +} diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerProgressBar.cs.meta b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerProgressBar.cs.meta new file mode 100644 index 00000000000..bebd9c8a465 --- /dev/null +++ b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerProgressBar.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1ad6bc96b5de44ad93f820fd338ee684 +timeCreated: 1625131386 \ No newline at end of file diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerToggleHistory.cs b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerToggleHistory.cs index b2b2e5a499f..0d80af7d8f3 100644 --- a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerToggleHistory.cs +++ b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerToggleHistory.cs @@ -9,17 +9,18 @@ namespace UnityEngine.Rendering.UI public class DebugUIHandlerToggleHistory : DebugUIHandlerToggle { Toggle[] historyToggles; - const float xDecal = 60f; + const float k_XOffset = 230f; internal override void SetWidget(DebugUI.Widget widget) { int historyDepth = (widget as DebugUI.HistoryBoolField)?.historyDepth ?? 0; historyToggles = new Toggle[historyDepth]; + float columnOffset = historyDepth > 0 ? k_XOffset / (float)historyDepth : 0f; for (int index = 0; index < historyDepth; ++index) { var historyToggle = Instantiate(valueToggle, transform); Vector3 pos = historyToggle.transform.position; - pos.x += (index + 1) * xDecal; + pos.x += (index + 1) * columnOffset; historyToggle.transform.position = pos; var background = historyToggle.transform.GetChild(0).GetComponent(); background.sprite = Sprite.Create(Texture2D.whiteTexture, new Rect(-1, -1, 2, 2), Vector2.zero); diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerValue.cs b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerValue.cs index a9d2c50cede..acf9e606699 100644 --- a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerValue.cs +++ b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerValue.cs @@ -11,9 +11,10 @@ public class DebugUIHandlerValue : DebugUIHandlerWidget public Text nameLabel; /// Value of the value field. public Text valueLabel; - DebugUI.Value m_Field; - float m_Timer; + DebugUI.Value m_Field; + protected internal float m_Timer; + static readonly Color k_ZeroColor = Color.gray; /// /// OnEnable implementation. @@ -56,7 +57,11 @@ void Update() { if (m_Timer >= m_Field.refreshRate) { - valueLabel.text = m_Field.GetValue().ToString(); + var value = m_Field.GetValue(); + valueLabel.text = m_Field.FormatString(value); + // De-emphasize zero values by switching to dark gray color + if (value is float) + valueLabel.color = (float)value == 0f ? k_ZeroColor : colorDefault; m_Timer -= m_Field.refreshRate; } diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerValueTuple.cs b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerValueTuple.cs new file mode 100644 index 00000000000..1481a00bc22 --- /dev/null +++ b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerValueTuple.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections; +using UnityEngine.Assertions; +using UnityEngine.UI; + +namespace UnityEngine.Rendering.UI +{ + /// + /// DebugUIHandler for value tuple widget. + /// + public class DebugUIHandlerValueTuple : DebugUIHandlerWidget + { + /// Name of the value field. + public Text nameLabel; + /// Value of the value field. + public Text valueLabel; + + protected internal DebugUI.ValueTuple m_Field; + protected internal Text[] valueElements; + + const float k_XOffset = 230f; + float m_Timer; + static readonly Color k_ZeroColor = Color.gray; + + /// + /// OnEnable implementation. + /// + protected override void OnEnable() + { + m_Timer = 0f; + } + + /// + /// OnSelection implementation. + /// + /// True if the selection wrapped around. + /// Previous widget. + /// True if the selection is allowed. + public override bool OnSelection(bool fromNext, DebugUIHandlerWidget previous) + { + nameLabel.color = colorSelected; + return true; + } + + /// + /// OnDeselection implementation. + /// + public override void OnDeselection() + { + nameLabel.color = colorDefault; + } + + internal override void SetWidget(DebugUI.Widget widget) + { + m_Widget = widget; + m_Field = CastWidget(); + nameLabel.text = m_Field.displayName; + + Debug.Assert(m_Field.numElements > 0); + int numElements = m_Field.numElements; + valueElements = new Text[numElements]; + valueElements[0] = valueLabel; + float columnOffset = k_XOffset / (float)numElements; + for (int index = 1; index < numElements; ++index) + { + var valueElement = Instantiate(valueLabel.gameObject, transform); + valueElement.AddComponent().ignoreLayout = true; + var rectTransform = valueElement.transform as RectTransform; + var originalTransform = nameLabel.transform as RectTransform; + rectTransform.anchorMax = rectTransform.anchorMin = new Vector2(0, 1); + rectTransform.sizeDelta = new Vector2(100, 26); + Vector3 pos = originalTransform.anchoredPosition; + pos.x += (index + 1) * columnOffset + 200f; + rectTransform.anchoredPosition = pos; + rectTransform.pivot = new Vector2(0, 1); + valueElements[index] = valueElement.GetComponent(); + } + } + + internal virtual void UpdateValueLabels() + { + for (int index = 0; index < m_Field.numElements; ++index) + { + if (index < valueElements.Length && valueElements[index] != null) + { + var value = m_Field.values[index].GetValue(); + valueElements[index].text = m_Field.values[index].FormatString(value); + // De-emphasize zero values by switching to dark gray color + if (value is float) + valueElements[index].color = (float)value == 0f ? k_ZeroColor : colorDefault; + } + } + } + + void Update() + { + if (m_Timer >= m_Field.refreshRate) + { + UpdateValueLabels(); + m_Timer -= m_Field.refreshRate; + } + + m_Timer += Time.deltaTime; + } + } +} diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerValueTuple.cs.meta b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerValueTuple.cs.meta new file mode 100644 index 00000000000..595c008304c --- /dev/null +++ b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Scripts/DebugUIHandlerValueTuple.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4df5db2b89304eb1a8d26c7a547149f9 +timeCreated: 1628504094 \ No newline at end of file diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Widgets/DebugUIProgressBar.prefab b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Widgets/DebugUIProgressBar.prefab new file mode 100644 index 00000000000..20254aba8bd --- /dev/null +++ b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Widgets/DebugUIProgressBar.prefab @@ -0,0 +1,334 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &1100371661045084 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 224720214277421396} + - component: {fileID: 2878355526880525161} + m_Layer: 5 + m_Name: DebugUIProgressBar + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &224720214277421396 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1100371661045084} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 224309343631572978} + - {fileID: 8099796348745405240} + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.5} + m_AnchorMax: {x: 1, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 26} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &2878355526880525161 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1100371661045084} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1ad6bc96b5de44ad93f820fd338ee684, type: 3} + m_Name: + m_EditorClassIdentifier: + colorDefault: {r: 0.8, g: 0.8, b: 0.8, a: 1} + colorSelected: {r: 0.25, g: 0.65, b: 0.8, a: 1} + nameLabel: {fileID: 114601347101323698} + valueLabel: {fileID: 114504040572925244} + progressBarRect: {fileID: 3673155785075797849} +--- !u!1 &1207032646716234 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 224309343631572978} + - component: {fileID: 222840031335149136} + - component: {fileID: 114601347101323698} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &224309343631572978 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1207032646716234} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 224720214277421396} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 300, y: 26} + m_Pivot: {x: 0, y: 1} +--- !u!222 &222840031335149136 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1207032646716234} + m_CullTransparentMesh: 0 +--- !u!114 &114601347101323698 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1207032646716234} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.8, g: 0.8, b: 0.8, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 12800000, guid: 74a5091d8707f334b9a5c31ef71a64ba, type: 3} + m_FontSize: 16 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 3 + m_AlignByGeometry: 0 + m_RichText: 0 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: New Text +--- !u!1 &1644687155343164 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 224518799942003328} + - component: {fileID: 222991141768779948} + - component: {fileID: 114504040572925244} + m_Layer: 5 + m_Name: Value + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &224518799942003328 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1644687155343164} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 8099796348745405240} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.5} + m_AnchorMax: {x: 1, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: -160, y: 26} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &222991141768779948 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1644687155343164} + m_CullTransparentMesh: 0 +--- !u!114 &114504040572925244 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1644687155343164} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.8, g: 0.8, b: 0.8, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 12800000, guid: 74a5091d8707f334b9a5c31ef71a64ba, type: 3} + m_FontSize: 16 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 1 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 0 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: '0 % + +' +--- !u!1 &6702026499903230195 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8099796348745405240} + m_Layer: 5 + m_Name: Panel + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &8099796348745405240 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6702026499903230195} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 3673155785075797849} + - {fileID: 224518799942003328} + m_Father: {fileID: 224720214277421396} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 6} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &7551593536009911401 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3673155785075797849} + - component: {fileID: 2553006676334283003} + - component: {fileID: 5850638271160688467} + m_Layer: 5 + m_Name: Progress + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &3673155785075797849 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7551593536009911401} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0.5, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 8099796348745405240} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.5} + m_AnchorMax: {x: 1, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 26} + m_Pivot: {x: 0, y: 0.5} +--- !u!222 &2553006676334283003 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7551593536009911401} + m_CullTransparentMesh: 0 +--- !u!114 &5850638271160688467 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7551593536009911401} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.24705882, g: 0.6509804, b: 0.8, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 127279d577f25ac4ea17dae3782e5074, type: 3} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Widgets/DebugUIProgressBar.prefab.meta b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Widgets/DebugUIProgressBar.prefab.meta new file mode 100644 index 00000000000..f05f3e7040e --- /dev/null +++ b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Widgets/DebugUIProgressBar.prefab.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d3770aaa3bbd8384aabab9ddd383e21e +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 100100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Widgets/DebugUIValuePersistent.prefab b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Widgets/DebugUIValuePersistent.prefab new file mode 100644 index 00000000000..031fdca005f --- /dev/null +++ b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Widgets/DebugUIValuePersistent.prefab @@ -0,0 +1,217 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &1100371661045084 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 224720214277421396} + - component: {fileID: 114728986975802896} + m_Layer: 5 + m_Name: DebugUIValuePersistent + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &224720214277421396 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1100371661045084} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 224309343631572978} + - {fileID: 224518799942003328} + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 26} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &114728986975802896 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1100371661045084} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bc78ce7c3bda3b845b1e94eade19277f, type: 3} + m_Name: + m_EditorClassIdentifier: + colorDefault: {r: 0.8, g: 0.8, b: 0.8, a: 1} + colorSelected: {r: 0.25, g: 0.65, b: 0.8, a: 1} + nameLabel: {fileID: 114601347101323698} + valueLabel: {fileID: 114504040572925244} +--- !u!1 &1207032646716234 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 224309343631572978} + - component: {fileID: 222840031335149136} + - component: {fileID: 114601347101323698} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &224309343631572978 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1207032646716234} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 224720214277421396} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0.8, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 1} +--- !u!222 &222840031335149136 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1207032646716234} + m_CullTransparentMesh: 1 +--- !u!114 &114601347101323698 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1207032646716234} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.8, g: 0.8, b: 0.8, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 12800000, guid: 74a5091d8707f334b9a5c31ef71a64ba, type: 3} + m_FontSize: 16 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 3 + m_AlignByGeometry: 0 + m_RichText: 0 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: New Text +--- !u!1 &1644687155343164 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 224518799942003328} + - component: {fileID: 222991141768779948} + - component: {fileID: 114504040572925244} + m_Layer: 5 + m_Name: Value + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &224518799942003328 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1644687155343164} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 224720214277421396} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.8, y: 0} + m_AnchorMax: {x: 0.98, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &222991141768779948 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1644687155343164} + m_CullTransparentMesh: 1 +--- !u!114 &114504040572925244 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1644687155343164} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.8, g: 0.8, b: 0.8, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 12800000, guid: 74a5091d8707f334b9a5c31ef71a64ba, type: 3} + m_FontSize: 16 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 1 + m_MaxSize: 40 + m_Alignment: 5 + m_AlignByGeometry: 0 + m_RichText: 0 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: '-' diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Widgets/DebugUIValuePersistent.prefab.meta b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Widgets/DebugUIValuePersistent.prefab.meta new file mode 100644 index 00000000000..e0f8dd4516f --- /dev/null +++ b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Widgets/DebugUIValuePersistent.prefab.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8778e6b26b51c6a4999b94d7cacd8b5d +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 100100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Widgets/DebugUIValueTuple.prefab b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Widgets/DebugUIValueTuple.prefab new file mode 100644 index 00000000000..35de732f24d --- /dev/null +++ b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Widgets/DebugUIValueTuple.prefab @@ -0,0 +1,217 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &1100371661045084 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 224720214277421396} + - component: {fileID: 661225178962715379} + m_Layer: 5 + m_Name: DebugUIValueTuple + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &224720214277421396 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1100371661045084} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 224309343631572978} + - {fileID: 224518799942003328} + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 26} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &661225178962715379 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1100371661045084} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4df5db2b89304eb1a8d26c7a547149f9, type: 3} + m_Name: + m_EditorClassIdentifier: + colorDefault: {r: 0.8, g: 0.8, b: 0.8, a: 1} + colorSelected: {r: 0.25, g: 0.65, b: 0.8, a: 1} + nameLabel: {fileID: 114601347101323698} + valueLabel: {fileID: 114504040572925244} +--- !u!1 &1207032646716234 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 224309343631572978} + - component: {fileID: 222840031335149136} + - component: {fileID: 114601347101323698} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &224309343631572978 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1207032646716234} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 224720214277421396} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0.5, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 1} +--- !u!222 &222840031335149136 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1207032646716234} + m_CullTransparentMesh: 1 +--- !u!114 &114601347101323698 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1207032646716234} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.8, g: 0.8, b: 0.8, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 12800000, guid: 74a5091d8707f334b9a5c31ef71a64ba, type: 3} + m_FontSize: 16 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 3 + m_AlignByGeometry: 0 + m_RichText: 0 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: New Text +--- !u!1 &1644687155343164 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 224518799942003328} + - component: {fileID: 222991141768779948} + - component: {fileID: 114504040572925244} + m_Layer: 5 + m_Name: Value + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &224518799942003328 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1644687155343164} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 224720214277421396} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &222991141768779948 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1644687155343164} + m_CullTransparentMesh: 1 +--- !u!114 &114504040572925244 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1644687155343164} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.8, g: 0.8, b: 0.8, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 12800000, guid: 74a5091d8707f334b9a5c31ef71a64ba, type: 3} + m_FontSize: 16 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 1 + m_MaxSize: 40 + m_Alignment: 3 + m_AlignByGeometry: 0 + m_RichText: 0 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: '-' diff --git a/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Widgets/DebugUIValueTuple.prefab.meta b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Widgets/DebugUIValueTuple.prefab.meta new file mode 100644 index 00000000000..0f07a9b2674 --- /dev/null +++ b/com.unity.render-pipelines.core/Runtime/Debugging/Prefabs/Widgets/DebugUIValueTuple.prefab.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a2148203dd960814ca5db0c293ceda35 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 100100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.render-pipelines.core/Tests/Runtime/RuntimeProfilerTests.cs b/com.unity.render-pipelines.core/Tests/Runtime/RuntimeProfilerTests.cs new file mode 100644 index 00000000000..441d37dc142 --- /dev/null +++ b/com.unity.render-pipelines.core/Tests/Runtime/RuntimeProfilerTests.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.TestTools; +using UnityEngine.Rendering; +using UnityEngine.SceneManagement; +using NUnit.Framework; + +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 UNITY_EDITOR + if (!UnityEditor.PlayerSettings.enableFrameTimingStats) + Assert.Ignore("Frame timing stats are disabled in Player Settings, skipping test."); +#endif + + // HACK #1 - really shouldn't have to do this here, but previous tests are leaking gameobjects + var objects = GameObject.FindObjectsOfType(); + foreach (var o in objects) + { + // HACK #2 - must not destroy DebugUpdater, which happens to have an EventSystem. + if (o.GetComponent() == 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 new WaitForEndOfFrame(); + + m_DebugFrameTiming.Reset(); + } + } + + // FIXME: Tests are disabled in player builds for now, since there's no API that tells whether frame timing is + // enabled or not. Re-enable if that changes. +#if UNITY_EDITOR + class RuntimeProfilerTests : RuntimeProfilerTestBase + { + [UnityTest] + public IEnumerator RuntimeProfilerGivesNonZeroOutput() + { + yield return Warmup(); + + m_ToCleanup = new GameObject(); + var camera = m_ToCleanup.AddComponent(); + for (int i = 0; i < k_NumFramesToRender; i++) + { + m_DebugFrameTiming.UpdateFrameTiming(); + camera.Render(); + yield return new WaitForEndOfFrame(); + } + + Assert.True( + m_DebugFrameTiming.m_BottleneckHistory.Histogram.Balanced > 0 || + m_DebugFrameTiming.m_BottleneckHistory.Histogram.CPU > 0 || + m_DebugFrameTiming.m_BottleneckHistory.Histogram.GPU > 0 || + m_DebugFrameTiming.m_BottleneckHistory.Histogram.PresentLimited > 0); + } + } +#endif +} diff --git a/com.unity.render-pipelines.core/Tests/Runtime/RuntimeProfilerTests.cs.meta b/com.unity.render-pipelines.core/Tests/Runtime/RuntimeProfilerTests.cs.meta new file mode 100644 index 00000000000..5ed745f2a43 --- /dev/null +++ b/com.unity.render-pipelines.core/Tests/Runtime/RuntimeProfilerTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 40a73c4a33c05924f813df35844d8c71 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.render-pipelines.high-definition/CHANGELOG.md b/com.unity.render-pipelines.high-definition/CHANGELOG.md index 2b4e67ab25f..b1f517939de 100644 --- a/com.unity.render-pipelines.high-definition/CHANGELOG.md +++ b/com.unity.render-pipelines.high-definition/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Added support for orthographic camera in path tracing. - Added public API to edit materials from script at runtime. - Added new functions that sample the custom buffer in custom passes (CustomPassSampleCustomColor and CustomPassLoadCustomColor) to handle the RTHandleScale automatically. +- Added new panels to Rendering Debugger Display Stats panel, displaying improved CPU/GPU frame timings and bottlenecks. ### Fixed - Fixed decal position when created from context menu. (case 1368987) @@ -41,6 +42,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - PrepareLightsForGPU CPU Light loop performance improvement (40% to 70% faster), utilizing burst and optimized. Utilizing better sorting, distributing work in jobs and improving cache access of light data. - In path tracing, camera ray misses now return a null value with Minimum Depth > 1. - HD's SpeedTree 8 upgrader now sets up CullModeForward as well. +- Restructured data under Display Stats panel to use column layout. ## [13.1.0] - 2021-09-24 diff --git a/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugDisplay.cs b/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugDisplay.cs index 093ed819c10..491884e71c6 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugDisplay.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugDisplay.cs @@ -179,7 +179,7 @@ public class DebugDisplaySettings : IDebugData static bool needsRefreshingCameraFreezeList = true; - List m_RecordedSamplers = new List(); + List m_RecordedSamplers = new List(); // Accumulate values to avg over one second. class AccumulatedTiming @@ -193,14 +193,14 @@ internal void UpdateLastAverage(int frameCount) accumulatedValue = 0.0f; } } - Dictionary m_AccumulatedGPUTiming = new Dictionary(); - Dictionary m_AccumulatedCPUTiming = new Dictionary(); - Dictionary m_AccumulatedInlineCPUTiming = new Dictionary(); + Dictionary m_AccumulatedGPUTiming = new Dictionary(); + Dictionary m_AccumulatedCPUTiming = new Dictionary(); + Dictionary m_AccumulatedInlineCPUTiming = new Dictionary(); float m_TimeSinceLastAvgValue = 0.0f; int m_AccumulatedFrames = 0; const float k_AccumulationTimeInSeconds = 1.0f; - List m_RecordedSamplersRT = new List(); + List m_RecordedSamplersRT = new List(); enum DebugProfilingType { CPU, @@ -208,6 +208,8 @@ enum DebugProfilingType InlineCPU } + internal DebugFrameTiming debugFrameTiming = new DebugFrameTiming(); + #if ENABLE_NVIDIA && ENABLE_NVIDIA_MODULE internal UnityEngine.NVIDIA.DebugView nvidiaDebugView { get; } = new UnityEngine.NVIDIA.DebugView(); #endif @@ -765,83 +767,73 @@ void EnableProfilingRecorders() { Debug.Assert(m_RecordedSamplers.Count == 0); - m_RecordedSamplers.Add(ProfilingSampler.Get(HDProfileId.HDRenderPipelineAllRenderRequest)); - m_RecordedSamplers.Add(ProfilingSampler.Get(HDProfileId.VolumeUpdate)); - m_RecordedSamplers.Add(ProfilingSampler.Get(HDProfileId.RenderShadowMaps)); - m_RecordedSamplers.Add(ProfilingSampler.Get(HDProfileId.GBuffer)); - m_RecordedSamplers.Add(ProfilingSampler.Get(HDProfileId.PrepareLightsForGPU)); - m_RecordedSamplers.Add(ProfilingSampler.Get(HDProfileId.VolumeVoxelization)); - m_RecordedSamplers.Add(ProfilingSampler.Get(HDProfileId.VolumetricLighting)); - m_RecordedSamplers.Add(ProfilingSampler.Get(HDProfileId.VolumetricClouds)); - m_RecordedSamplers.Add(ProfilingSampler.Get(HDProfileId.VolumetricCloudsTrace)); - m_RecordedSamplers.Add(ProfilingSampler.Get(HDProfileId.VolumetricCloudsReproject)); - m_RecordedSamplers.Add(ProfilingSampler.Get(HDProfileId.VolumetricCloudsUpscaleAndCombine)); - m_RecordedSamplers.Add(ProfilingSampler.Get(HDProfileId.RenderDeferredLightingCompute)); - m_RecordedSamplers.Add(ProfilingSampler.Get(HDProfileId.ForwardOpaque)); - m_RecordedSamplers.Add(ProfilingSampler.Get(HDProfileId.ForwardTransparent)); - m_RecordedSamplers.Add(ProfilingSampler.Get(HDProfileId.ForwardPreRefraction)); - m_RecordedSamplers.Add(ProfilingSampler.Get(HDProfileId.ColorPyramid)); - m_RecordedSamplers.Add(ProfilingSampler.Get(HDProfileId.DepthPyramid)); - m_RecordedSamplers.Add(ProfilingSampler.Get(HDProfileId.PostProcessing)); - } - - void DisableProfilingRecorders() - { - foreach (var sampler in m_RecordedSamplers) + m_RecordedSamplers.Add(HDProfileId.HDRenderPipelineAllRenderRequest); + m_RecordedSamplers.Add(HDProfileId.VolumeUpdate); + m_RecordedSamplers.Add(HDProfileId.RenderShadowMaps); + m_RecordedSamplers.Add(HDProfileId.GBuffer); + m_RecordedSamplers.Add(HDProfileId.PrepareLightsForGPU); + m_RecordedSamplers.Add(HDProfileId.VolumeVoxelization); + m_RecordedSamplers.Add(HDProfileId.VolumetricLighting); + m_RecordedSamplers.Add(HDProfileId.VolumetricClouds); + m_RecordedSamplers.Add(HDProfileId.VolumetricCloudsTrace); + m_RecordedSamplers.Add(HDProfileId.VolumetricCloudsReproject); + m_RecordedSamplers.Add(HDProfileId.VolumetricCloudsUpscaleAndCombine); + m_RecordedSamplers.Add(HDProfileId.RenderDeferredLightingCompute); + m_RecordedSamplers.Add(HDProfileId.ForwardOpaque); + m_RecordedSamplers.Add(HDProfileId.ForwardTransparent); + m_RecordedSamplers.Add(HDProfileId.ForwardPreRefraction); + m_RecordedSamplers.Add(HDProfileId.ColorPyramid); + m_RecordedSamplers.Add(HDProfileId.DepthPyramid); + m_RecordedSamplers.Add(HDProfileId.PostProcessing); + } + + void DisableProfilingRecorders(List samplers) + { + foreach (var sampler in samplers) { - sampler.enableRecording = false; + ProfilingSampler.Get(sampler).enableRecording = false; } - m_RecordedSamplers.Clear(); + samplers.Clear(); } void EnableProfilingRecordersRT() { Debug.Assert(m_RecordedSamplersRT.Count == 0); - m_RecordedSamplersRT.Add(ProfilingSampler.Get(HDProfileId.RaytracingBuildCluster)); - m_RecordedSamplersRT.Add(ProfilingSampler.Get(HDProfileId.RaytracingCullLights)); + m_RecordedSamplersRT.Add(HDProfileId.RaytracingBuildCluster); + m_RecordedSamplersRT.Add(HDProfileId.RaytracingCullLights); // Ray Traced Reflections - m_RecordedSamplersRT.Add(ProfilingSampler.Get(HDProfileId.RaytracingReflectionDirectionGeneration)); - m_RecordedSamplersRT.Add(ProfilingSampler.Get(HDProfileId.RaytracingReflectionEvaluation)); - m_RecordedSamplersRT.Add(ProfilingSampler.Get(HDProfileId.RaytracingReflectionAdjustWeight)); - m_RecordedSamplersRT.Add(ProfilingSampler.Get(HDProfileId.RaytracingReflectionUpscale)); - m_RecordedSamplersRT.Add(ProfilingSampler.Get(HDProfileId.RaytracingReflectionFilter)); + m_RecordedSamplersRT.Add(HDProfileId.RaytracingReflectionDirectionGeneration); + m_RecordedSamplersRT.Add(HDProfileId.RaytracingReflectionEvaluation); + m_RecordedSamplersRT.Add(HDProfileId.RaytracingReflectionAdjustWeight); + m_RecordedSamplersRT.Add(HDProfileId.RaytracingReflectionUpscale); + m_RecordedSamplersRT.Add(HDProfileId.RaytracingReflectionFilter); // Ray Traced Ambient Occlusion - m_RecordedSamplersRT.Add(ProfilingSampler.Get(HDProfileId.RaytracingAmbientOcclusion)); - m_RecordedSamplersRT.Add(ProfilingSampler.Get(HDProfileId.RaytracingFilterAmbientOcclusion)); + m_RecordedSamplersRT.Add(HDProfileId.RaytracingAmbientOcclusion); + m_RecordedSamplersRT.Add(HDProfileId.RaytracingFilterAmbientOcclusion); // Ray Traced Shadows - m_RecordedSamplersRT.Add(ProfilingSampler.Get(HDProfileId.RaytracingDirectionalLightShadow)); - m_RecordedSamplersRT.Add(ProfilingSampler.Get(HDProfileId.RaytracingLightShadow)); + m_RecordedSamplersRT.Add(HDProfileId.RaytracingDirectionalLightShadow); + m_RecordedSamplersRT.Add(HDProfileId.RaytracingLightShadow); // Ray Traced Indirect Diffuse - m_RecordedSamplersRT.Add(ProfilingSampler.Get(HDProfileId.RaytracingIndirectDiffuseDirectionGeneration)); - m_RecordedSamplersRT.Add(ProfilingSampler.Get(HDProfileId.RaytracingIndirectDiffuseEvaluation)); - m_RecordedSamplersRT.Add(ProfilingSampler.Get(HDProfileId.RaytracingIndirectDiffuseUpscale)); - m_RecordedSamplersRT.Add(ProfilingSampler.Get(HDProfileId.RaytracingFilterIndirectDiffuse)); - - m_RecordedSamplersRT.Add(ProfilingSampler.Get(HDProfileId.RaytracingDebugOverlay)); - m_RecordedSamplersRT.Add(ProfilingSampler.Get(HDProfileId.ForwardPreRefraction)); - m_RecordedSamplersRT.Add(ProfilingSampler.Get(HDProfileId.RayTracingRecursiveRendering)); - m_RecordedSamplersRT.Add(ProfilingSampler.Get(HDProfileId.RayTracingDepthPrepass)); - m_RecordedSamplersRT.Add(ProfilingSampler.Get(HDProfileId.RayTracingFlagMask)); - m_RecordedSamplersRT.Add(ProfilingSampler.Get(HDProfileId.RaytracingDeferredLighting)); - } - - void DisableProfilingRecordersRT() - { - foreach (var sampler in m_RecordedSamplersRT) - { - sampler.enableRecording = false; - } + m_RecordedSamplersRT.Add(HDProfileId.RaytracingIndirectDiffuseDirectionGeneration); + m_RecordedSamplersRT.Add(HDProfileId.RaytracingIndirectDiffuseEvaluation); + m_RecordedSamplersRT.Add(HDProfileId.RaytracingIndirectDiffuseUpscale); + m_RecordedSamplersRT.Add(HDProfileId.RaytracingFilterIndirectDiffuse); - m_RecordedSamplersRT.Clear(); + m_RecordedSamplersRT.Add(HDProfileId.RaytracingDebugOverlay); + m_RecordedSamplersRT.Add(HDProfileId.ForwardPreRefraction); + m_RecordedSamplersRT.Add(HDProfileId.RayTracingRecursiveRendering); + m_RecordedSamplersRT.Add(HDProfileId.RayTracingDepthPrepass); + m_RecordedSamplersRT.Add(HDProfileId.RayTracingFlagMask); + m_RecordedSamplersRT.Add(HDProfileId.RaytracingDeferredLighting); } - float GetSamplerTiming(DebugProfilingType type, ProfilingSampler sampler) + float GetSamplerTiming(HDProfileId samplerId, ProfilingSampler sampler, DebugProfilingType type) { if (data.averageProfilerTimingsOverASecond) { @@ -851,7 +843,7 @@ float GetSamplerTiming(DebugProfilingType type, ProfilingSampler sampler) m_AccumulatedGPUTiming; AccumulatedTiming accTiming = null; - if (accumulatedDictionary.TryGetValue(sampler.name, out accTiming)) + if (accumulatedDictionary.TryGetValue((int)samplerId, out accTiming)) return accTiming.lastAverage; } else @@ -862,79 +854,67 @@ float GetSamplerTiming(DebugProfilingType type, ProfilingSampler sampler) return 0.0f; } - ObservableList BuildProfilingSamplerList(DebugProfilingType type) + ObservableList BuildProfilingSamplerWidgetList(List samplerList) { var result = new ObservableList(); - // Find the right accumulated dictionary and add it there if not existing yet. - var accumulatedDictionary = type == DebugProfilingType.CPU ? m_AccumulatedCPUTiming : - type == DebugProfilingType.InlineCPU ? m_AccumulatedInlineCPUTiming : - m_AccumulatedGPUTiming; - - - foreach (var sampler in m_RecordedSamplers) + DebugUI.Value CreateWidgetForSampler(HDProfileId samplerId, ProfilingSampler sampler, DebugProfilingType type) { - sampler.enableRecording = true; - if (!accumulatedDictionary.ContainsKey(sampler.name)) + // Find the right accumulated dictionary and add it there if not existing yet. + var accumulatedDictionary = type == DebugProfilingType.CPU ? m_AccumulatedCPUTiming : + type == DebugProfilingType.InlineCPU ? m_AccumulatedInlineCPUTiming : + m_AccumulatedGPUTiming; + + if (!accumulatedDictionary.ContainsKey((int)samplerId)) { - accumulatedDictionary.Add(sampler.name, new AccumulatedTiming()); + accumulatedDictionary.Add((int)samplerId, new AccumulatedTiming()); } - - result.Add(new DebugUI.Value + return new() { - displayName = sampler.name, - getter = () => string.Format("{0:F2}", GetSamplerTiming(type, sampler)), - refreshRate = 1.0f / 5.0f - }); + formatString = "{0:F2}ms", + refreshRate = 1.0f / 5.0f, + getter = () => GetSamplerTiming(samplerId, sampler, type), + }; } - return result; - } - - ObservableList BuildProfilingSamplerListRT(DebugProfilingType type) - { - var result = new ObservableList(); - - // Find the right accumulated dictionary and add it there if not existing yet. - var accumulatedDictionary = type == DebugProfilingType.CPU ? m_AccumulatedCPUTiming : - type == DebugProfilingType.InlineCPU ? m_AccumulatedInlineCPUTiming : - m_AccumulatedGPUTiming; - - - foreach (var sampler in m_RecordedSamplersRT) + foreach (var samplerId in samplerList) { + var sampler = ProfilingSampler.Get(samplerId); + sampler.enableRecording = true; - if (!accumulatedDictionary.ContainsKey(sampler.name)) - { - accumulatedDictionary.Add(sampler.name, new AccumulatedTiming()); - } - result.Add(new DebugUI.Value + result.Add(new DebugUI.ValueTuple { displayName = sampler.name, - getter = () => string.Format("{0:F2}", GetSamplerTiming(type, sampler)), - refreshRate = 1.0f / 5.0f + values = new[] + { + CreateWidgetForSampler(samplerId, sampler, DebugProfilingType.CPU), + CreateWidgetForSampler(samplerId, sampler, DebugProfilingType.InlineCPU), + CreateWidgetForSampler(samplerId, sampler, DebugProfilingType.GPU), + } }); } return result; } - void UpdateListOfAveragedProfilerTimings(List samplers, bool needUpdatingAverages) + void UpdateListOfAveragedProfilerTimings(List samplers, bool needUpdatingAverages) { - foreach (var sampler in samplers) + foreach (var samplerId in samplers) { + var sampler = ProfilingSampler.Get(samplerId); + // Accumulate. AccumulatedTiming accCPUTiming = null; - if (m_AccumulatedCPUTiming.TryGetValue(sampler.name, out accCPUTiming)) + if (m_AccumulatedCPUTiming.TryGetValue((int)samplerId, out accCPUTiming)) accCPUTiming.accumulatedValue += sampler.cpuElapsedTime; AccumulatedTiming accInlineCPUTiming = null; - if (m_AccumulatedInlineCPUTiming.TryGetValue(sampler.name, out accInlineCPUTiming)) + if (m_AccumulatedInlineCPUTiming.TryGetValue((int)samplerId, out accInlineCPUTiming)) accInlineCPUTiming.accumulatedValue += sampler.inlineCpuElapsedTime; AccumulatedTiming accGPUTiming = null; - if (m_AccumulatedGPUTiming.TryGetValue(sampler.name, out accGPUTiming)) + if (m_AccumulatedGPUTiming.TryGetValue((int)samplerId, out accGPUTiming)) accGPUTiming.accumulatedValue += sampler.gpuElapsedTime; if (needUpdatingAverages) @@ -968,21 +948,18 @@ internal void UpdateAveragedProfilerTimings() void RegisterDisplayStatsDebug() { var list = new List(); - list.Add(new DebugUI.Value { displayName = "Frame Rate (fps)", getter = () => 1f / Time.smoothDeltaTime, refreshRate = 1f / 5f }); - list.Add(new DebugUI.Value { displayName = "Frame Time (ms)", getter = () => Time.smoothDeltaTime * 1000f, refreshRate = 1f / 5f }); + debugFrameTiming.RegisterDebugUI(list); EnableProfilingRecorders(); list.Add(new DebugUI.BoolField { displayName = "Update every second with average", getter = () => data.averageProfilerTimingsOverASecond, setter = value => data.averageProfilerTimingsOverASecond = value }); - list.Add(new DebugUI.Foldout("CPU timings (Command Buffers)", BuildProfilingSamplerList(DebugProfilingType.CPU))); - list.Add(new DebugUI.Foldout("GPU timings", BuildProfilingSamplerList(DebugProfilingType.GPU))); + list.Add(new DebugUI.Foldout("Detailed Stats", BuildProfilingSamplerWidgetList(m_RecordedSamplers), new[] { "CPU", "CPUInline", "GPU" })); + if (HDRenderPipeline.currentAsset?.currentPlatformRenderPipelineSettings.supportRayTracing ?? true) { EnableProfilingRecordersRT(); - list.Add(new DebugUI.Foldout("CPU timings RT (Command Buffers)", BuildProfilingSamplerListRT(DebugProfilingType.CPU))); - list.Add(new DebugUI.Foldout("GPU timings RT", BuildProfilingSamplerListRT(DebugProfilingType.GPU))); + list.Add(new DebugUI.Foldout("Ray Tracing Stats", BuildProfilingSamplerWidgetList(m_RecordedSamplersRT), new[] { "CPU", "CPUInline", "GPU" })); } - list.Add(new DebugUI.Foldout("Inline CPU timings", BuildProfilingSamplerList(DebugProfilingType.InlineCPU))); list.Add(new DebugUI.BoolField { displayName = "Count Rays (MRays/Frame)", getter = () => data.countRays, setter = value => data.countRays = value, onValueChanged = RefreshDisplayStatsDebug }); if (data.countRays) { @@ -1040,6 +1017,15 @@ DebugUI.Widget CreateMissingDebugShadersWarning() }; } + void UnregisterDisplayStatsDebug() + { + DisableProfilingRecorders(m_RecordedSamplers); + if (HDRenderPipeline.currentAsset?.currentPlatformRenderPipelineSettings.supportRayTracing ?? true) + DisableProfilingRecorders(m_RecordedSamplersRT); + + UnregisterDebugItems(k_PanelDisplayStats, m_DebugDisplayStatsItems); + } + static class MaterialStrings { public static readonly NameAndTooltip CommonMaterialProperties = new() { name = "Common Material Properties", tooltip = "Use the drop-down to select and debug a Material property to visualize on every GameObject on screen." }; @@ -1114,7 +1100,7 @@ void RegisterMaterialDebug() void RefreshDisplayStatsDebug(DebugUI.Field field, T value) { - UnregisterDebugItems(k_PanelDisplayStats, m_DebugDisplayStatsItems); + UnregisterDisplayStatsDebug(); RegisterDisplayStatsDebug(); } @@ -1795,31 +1781,38 @@ DebugUI.Widget makeWidget(string name, VolumeParameter param) var row = new DebugUI.Table.Row() { displayName = "Volume Info", - children = { new DebugUI.Value() { - displayName = "Interpolated Value", - getter = () => { - // This getter is called first at each render - // It is used to update the volumes - if (Time.time - timer < refreshRate) - return ""; - timer = Time.deltaTime; - if (data.volumeDebugSettings.selectedCameraIndex != 0) - { - var newVolumes = data.volumeDebugSettings.GetVolumes(); - if (!data.volumeDebugSettings.RefreshVolumes(newVolumes)) - { - for (int i = 0; i < newVolumes.Length; i++) - { - var visible = data.volumeDebugSettings.VolumeHasInfluence(newVolumes[i]); - table.SetColumnVisibility(i + 1, visible); - } - return ""; - } - } - RefreshVolumeDebug(null, false); - return ""; - } - } } + children = + { + new DebugUI.Value() + { + displayName = "Interpolated Value", + getter = () => + { + // This getter is called first at each render + // It is used to update the volumes + if (Time.time - timer < refreshRate) + return ""; + timer = Time.deltaTime; + if (data.volumeDebugSettings.selectedCameraIndex != 0) + { + var newVolumes = data.volumeDebugSettings.GetVolumes(); + if (!data.volumeDebugSettings.RefreshVolumes(newVolumes)) + { + for (int i = 0; i < newVolumes.Length; i++) + { + var visible = data.volumeDebugSettings.VolumeHasInfluence(newVolumes[i]); + table.SetColumnVisibility(i + 1, visible); + } + + return ""; + } + } + + RefreshVolumeDebug(null, false); + return ""; + } + } + } }; row.opened = true; @@ -2104,12 +2097,7 @@ internal void RegisterDebug() internal void UnregisterDebug() { UnregisterDebugItems(k_PanelDecals, m_DebugDecalsItems); - - DisableProfilingRecorders(); - if (HDRenderPipeline.currentAsset?.currentPlatformRenderPipelineSettings.supportRayTracing ?? true) - DisableProfilingRecordersRT(); - UnregisterDebugItems(k_PanelDisplayStats, m_DebugDisplayStatsItems); - + UnregisterDisplayStatsDebug(); UnregisterDebugItems(k_PanelMaterials, m_DebugMaterialItems); UnregisterDebugItems(k_PanelLighting, m_DebugLightingItems); UnregisterDebugItems(k_PanelVolume, m_DebugVolumeItems); diff --git a/com.unity.render-pipelines.high-definition/Runtime/Debug/RayCountManager.cs b/com.unity.render-pipelines.high-definition/Runtime/Debug/RayCountManager.cs index baa53727347..86ce2fad436 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Debug/RayCountManager.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Debug/RayCountManager.cs @@ -82,6 +82,11 @@ public int RayCountIsEnabled() return m_IsActive ? 1 : 0; } + internal void SetRayCountEnabled(bool value) + { + m_IsActive = value; + } + static public TextureHandle CreateRayCountTexture(RenderGraph renderGraph) { return renderGraph.CreateTexture(new TextureDesc(Vector2.one, true, true) diff --git a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.cs b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.cs index fb8f3eb7340..8a69fdc2438 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.cs @@ -1140,6 +1140,12 @@ protected override void Render(ScriptableRenderContext renderContext, Camera[] c #if ENABLE_NVIDIA && ENABLE_NVIDIA_MODULE m_DebugDisplaySettings.nvidiaDebugView.Update(); #endif + +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (DebugManager.instance.isAnyDebugUIActive) + m_DebugDisplaySettings.debugFrameTiming.UpdateFrameTiming(); +#endif + Terrain.GetActiveTerrains(m_ActiveTerrains); // This syntax is awful and hostile to debugging, please don't use it... @@ -2014,11 +2020,7 @@ AOVRequestData aovRequest ApplyDebugDisplaySettings(hdCamera, cmd, aovRequest.isValid); - if (DebugManager.instance.displayRuntimeUI -#if UNITY_EDITOR - || DebugManager.instance.displayEditorUI -#endif - ) + if (DebugManager.instance.isAnyDebugUIActive) m_CurrentDebugDisplaySettings.UpdateAveragedProfilerTimings(); SetupCameraProperties(hdCamera, renderContext, cmd); @@ -2047,6 +2049,7 @@ AOVRequestData aovRequest // Do the same for ray tracing if allowed if (m_RayTracingSupported) { + m_RayCountManager.SetRayCountEnabled(m_CurrentDebugDisplaySettings.data.countRays); BuildRayTracingLightData(cmd, hdCamera, m_CurrentDebugDisplaySettings); } diff --git a/com.unity.render-pipelines.universal/CHANGELOG.md b/com.unity.render-pipelines.universal/CHANGELOG.md index 8b4c4f04514..70b7aaf8fe3 100644 --- a/com.unity.render-pipelines.universal/CHANGELOG.md +++ b/com.unity.render-pipelines.universal/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Added Depth Priming support for Vulkan with MSAA. - Added Shadows and Additional Lights off variants stripping. - Exposed public API for DebugDisplaySettings. +- Added Display Stats panel to Rendering Debugger that displays CPU/GPU frame timings and bottlenecks. ### Changed - Main light shadow, additional light shadow and additional light keywords are now enabled based on urp setting instead of existence in scene. This allows better variant stripping. diff --git a/com.unity.render-pipelines.universal/Runtime/Debug/DebugDisplaySettingsMaterial.cs b/com.unity.render-pipelines.universal/Runtime/Debug/DebugDisplaySettingsMaterial.cs index 496a947dca9..0b94f8346e4 100644 --- a/com.unity.render-pipelines.universal/Runtime/Debug/DebugDisplaySettingsMaterial.cs +++ b/com.unity.render-pipelines.universal/Runtime/Debug/DebugDisplaySettingsMaterial.cs @@ -413,7 +413,7 @@ public SettingsPanel(DebugDisplaySettingsMaterial data) } } - #region IDebugDisplaySettingsData + #region IDebugDisplaySettingsQuery public bool AreAnySettingsActive => (materialDebugMode != DebugMaterialMode.None) || (vertexAttributeDebugMode != DebugVertexAttributeMode.None) || diff --git a/com.unity.render-pipelines.universal/Runtime/Debug/DebugDisplayStats.cs b/com.unity.render-pipelines.universal/Runtime/Debug/DebugDisplayStats.cs new file mode 100644 index 00000000000..c3e2be614c1 --- /dev/null +++ b/com.unity.render-pipelines.universal/Runtime/Debug/DebugDisplayStats.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace UnityEngine.Rendering.Universal +{ + class DebugDisplayStats : IDebugDisplaySettingsData + { + DebugFrameTiming m_DebugFrameTiming = new DebugFrameTiming(); + + private class StatsPanel : DebugDisplaySettingsPanel + { + public override string PanelName => "Display Stats"; + public override DebugUI.Flags Flags => DebugUI.Flags.RuntimeOnly; + + public StatsPanel(DebugFrameTiming frameTiming) + { + var list = new List(); + frameTiming.RegisterDebugUI(list); + + foreach (var w in list) + AddWidget(w); + } + } + + public void UpdateFrameTiming() + { + m_DebugFrameTiming.UpdateFrameTiming(); + } + + #region IDebugDisplaySettingsData + + public bool AreAnySettingsActive => false; + public bool IsPostProcessingAllowed => true; + public bool IsLightingActive => true; + public bool TryGetScreenClearColor(ref Color _) => false; + + public IDebugDisplaySettingsPanelDisposable CreatePanel() + { + return new StatsPanel(m_DebugFrameTiming); + } + + #endregion + } +} diff --git a/com.unity.render-pipelines.universal/Runtime/Debug/DebugDisplayStats.cs.meta b/com.unity.render-pipelines.universal/Runtime/Debug/DebugDisplayStats.cs.meta new file mode 100644 index 00000000000..aeb47ac4f13 --- /dev/null +++ b/com.unity.render-pipelines.universal/Runtime/Debug/DebugDisplayStats.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cd017e26c44959440b65ae797d9d4d90 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.render-pipelines.universal/Runtime/Debug/UniversalRenderPipelineDebugDisplaySettings.cs b/com.unity.render-pipelines.universal/Runtime/Debug/UniversalRenderPipelineDebugDisplaySettings.cs index f8013e8d2ea..1c6121f830c 100644 --- a/com.unity.render-pipelines.universal/Runtime/Debug/UniversalRenderPipelineDebugDisplaySettings.cs +++ b/com.unity.render-pipelines.universal/Runtime/Debug/UniversalRenderPipelineDebugDisplaySettings.cs @@ -21,6 +21,11 @@ public class UniversalRenderPipelineDebugDisplaySettings : DebugDisplaySettings< /// public DebugDisplaySettingsLighting lightingSettings { get; private set; } + /// + /// Display stats. + /// + internal DebugDisplayStats DisplayStats { get; private set; } + #region IDebugDisplaySettingsQuery /// @@ -71,10 +76,17 @@ public override void Reset() { m_Settings.Clear(); + DisplayStats = Add(new DebugDisplayStats()); CommonSettings = Add(new DebugDisplaySettingsCommon()); materialSettings = Add(new DebugDisplaySettingsMaterial()); lightingSettings = Add(new DebugDisplaySettingsLighting()); renderingSettings = Add(new DebugDisplaySettingsRendering()); } + + internal void UpdateFrameTiming() + { + if (DisplayStats != null) + DisplayStats.UpdateFrameTiming(); + } } } diff --git a/com.unity.render-pipelines.universal/Runtime/ScriptableRenderer.cs b/com.unity.render-pipelines.universal/Runtime/ScriptableRenderer.cs index f1e3b95d7ed..a95dcb4bd33 100644 --- a/com.unity.render-pipelines.universal/Runtime/ScriptableRenderer.cs +++ b/com.unity.render-pipelines.universal/Runtime/ScriptableRenderer.cs @@ -495,9 +495,9 @@ internal static void ConfigureActiveTarget(RenderTargetIdentifier colorAttachmen public ScriptableRenderer(ScriptableRendererData data) { - if (Debug.isDebugBuild) - DebugHandler = new DebugHandler(data); - +#if DEVELOPMENT_BUILD || UNITY_EDITOR + DebugHandler = new DebugHandler(data); +#endif profilingExecute = new ProfilingSampler($"{nameof(ScriptableRenderer)}.{nameof(ScriptableRenderer.Execute)}: {data.name}"); foreach (var feature in data.rendererFeatures) diff --git a/com.unity.render-pipelines.universal/Runtime/UniversalRenderPipeline.cs b/com.unity.render-pipelines.universal/Runtime/UniversalRenderPipeline.cs index dc0f79438b6..80e93fb0f5d 100644 --- a/com.unity.render-pipelines.universal/Runtime/UniversalRenderPipeline.cs +++ b/com.unity.render-pipelines.universal/Runtime/UniversalRenderPipeline.cs @@ -255,6 +255,11 @@ protected override void Render(ScriptableRenderContext renderContext, Camera[] c } #endif +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (DebugManager.instance.isAnyDebugUIActive) + UniversalRenderPipelineDebugDisplaySettings.Instance.UpdateFrameTiming(); +#endif + SortCameras(cameras); #if UNITY_2021_1_OR_NEWER for (int i = 0; i < cameras.Count; ++i)