using System; using System.Collections.Generic; using Unity.Collections; using static UnityEngine.Rendering.DebugUI; using static UnityEngine.Rendering.DebugUI.Widget; namespace UnityEngine.Rendering { /// /// GPU Resident Drawer Rendering Debugger settings. /// public class DebugDisplayGPUResidentDrawer : IDebugDisplaySettingsData { const string k_FormatString = "{0}"; const float k_RefreshRate = 1f / 5f; const int k_MaxViewCount = 32; const int k_MaxOcclusionPassCount = 32; const int k_MaxContextCount = 16; private bool displayBatcherStats { get { return GPUResidentDrawer.GetDebugStats()?.enabled ?? false; } set { DebugRendererBatcherStats debugStats = GPUResidentDrawer.GetDebugStats(); if (debugStats != null) debugStats.enabled = value; } } /// Returns the view instances id for the selected occluder debug view index, or 0 if not valid. internal bool GetOccluderViewInstanceID(out int viewInstanceID) { DebugRendererBatcherStats debugStats = GPUResidentDrawer.GetDebugStats(); if (debugStats != null) { if (occluderDebugViewIndex >= 0 && occluderDebugViewIndex < debugStats.occluderStats.Length) { viewInstanceID = debugStats.occluderStats[occluderDebugViewIndex].viewInstanceID; return true; } } viewInstanceID = 0; return false; } /// Returns if the occlusion test heatmap debug overlay is enabled. internal bool occlusionTestOverlayEnable { get { return GPUResidentDrawer.GetDebugStats()?.occlusionOverlayEnabled ?? false; } set { DebugRendererBatcherStats debugStats = GPUResidentDrawer.GetDebugStats(); if (debugStats != null) debugStats.occlusionOverlayEnabled = value; } } private bool occlusionTestOverlayCountVisible { get { return GPUResidentDrawer.GetDebugStats()?.occlusionOverlayCountVisible ?? false; } set { DebugRendererBatcherStats debugStats = GPUResidentDrawer.GetDebugStats(); if (debugStats != null) debugStats.occlusionOverlayCountVisible = value; } } private bool overrideOcclusionTestToAlwaysPass { get { return GPUResidentDrawer.GetDebugStats()?.overrideOcclusionTestToAlwaysPass ?? false; } set { DebugRendererBatcherStats debugStats = GPUResidentDrawer.GetDebugStats(); if (debugStats != null) debugStats.overrideOcclusionTestToAlwaysPass = value; } } /// Returns true if the occluder debug overlay is enabled. public bool occluderDebugViewEnable = false; internal bool occluderContextStats = false; internal Vector2 occluderDebugViewRange = new Vector2(0.0f, 1.0f); internal int occluderDebugViewIndex = 0; private static InstanceCullerViewStats GetInstanceCullerViewStats(int viewIndex) { DebugRendererBatcherStats debugStats = GPUResidentDrawer.GetDebugStats(); if (debugStats != null && viewIndex < debugStats.instanceCullerStats.Length) return debugStats.instanceCullerStats[viewIndex]; else return new InstanceCullerViewStats(); } private static InstanceOcclusionEventStats GetInstanceOcclusionEventStats(int passIndex) { DebugRendererBatcherStats debugStats = GPUResidentDrawer.GetDebugStats(); if (debugStats != null && passIndex < debugStats.instanceOcclusionEventStats.Length) return debugStats.instanceOcclusionEventStats[passIndex]; else return new InstanceOcclusionEventStats(); } static class Strings { public const string drawerSettingsContainerName = "GPU Resident Drawer Settings"; public static readonly NameAndTooltip displayBatcherStats = new() { name = "Display Culling Stats", tooltip = "Enable the checkbox to display stats for instance culling." }; public const string occlusionCullingTitle = "Occlusion Culling"; public static readonly NameAndTooltip occlusionTestOverlayEnable = new() { name = "Occlusion Test Overlay", tooltip = "Occlusion test visualisation." }; public static readonly NameAndTooltip occlusionTestOverlayCountVisible = new() { name = "Occlusion Test Overlay Count Visible", tooltip = "Occlusion test visualisation should count visible instances instead of occluded instances." }; public static readonly NameAndTooltip overrideOcclusionTestToAlwaysPass = new() { name = "Override Occlusion Test To Always Pass", tooltip = "Occlusion test always passes." }; public static readonly NameAndTooltip occluderContextStats = new() { name = "Occluder Context Stats", tooltip = "Show all the active occluder context textures." }; public static readonly NameAndTooltip occluderDebugViewEnable = new() { name = "Occluder Debug View", tooltip = "Debug view of occluder texture." }; public static readonly NameAndTooltip occluderDebugViewIndex = new() { name = "Occluder Debug View Index", tooltip = "Index of the view for which the occluder texture is displayed. Use the Occlusion Test Context Stats for a list of the views." }; public static readonly NameAndTooltip occluderDebugViewRangeMin = new() { name = "Occluder Debug View Range Min", tooltip = "Range in which the occluder debug texture are displayed." }; public static readonly NameAndTooltip occluderDebugViewRangeMax = new() { name = "Occluder Debug View Range Max", tooltip = "Range in which the occluder debug texture are displayed." }; } private static DebugOccluderStats GetOccluderStats(int occluderIndex) { DebugRendererBatcherStats debugStats = GPUResidentDrawer.GetDebugStats(); if (debugStats != null && occluderIndex < debugStats.occluderStats.Length) return debugStats.occluderStats[occluderIndex]; else return new DebugOccluderStats(); } private static int GetOcclusionContextsCounts() { return GPUResidentDrawer.GetDebugStats()?.occluderStats.Length ?? 0; } private static int GetInstanceCullerViewCount() { return GPUResidentDrawer.GetDebugStats()?.instanceCullerStats.Length ?? 0; } private static int GetInstanceOcclusionEventCount() { return GPUResidentDrawer.GetDebugStats()?.instanceOcclusionEventStats.Length ?? 0; } private static DebugUI.Table.Row AddInstanceCullerViewDataRow(int viewIndex) { return new DebugUI.Table.Row { displayName = "", opened = true, isHiddenCallback = () => { return viewIndex >= GetInstanceCullerViewCount(); }, children = { new DebugUI.Value { displayName = "View Type", refreshRate = k_RefreshRate, formatString = k_FormatString, getter = () => GetInstanceCullerViewStats(viewIndex).viewType }, new DebugUI.Value { displayName = "View Instance ID", refreshRate = k_RefreshRate, formatString = k_FormatString, getter = () => GetInstanceCullerViewStats(viewIndex).viewInstanceID }, new DebugUI.Value { displayName = "Split Index", refreshRate = k_RefreshRate, formatString = k_FormatString, getter = () => GetInstanceCullerViewStats(viewIndex).splitIndex }, new DebugUI.Value { displayName = "Visible Instances", refreshRate = k_RefreshRate, formatString = k_FormatString, getter = () => GetInstanceCullerViewStats(viewIndex).visibleInstances }, new DebugUI.Value { displayName = "Draw Commands", refreshRate = k_RefreshRate, formatString = k_FormatString, getter = () => GetInstanceCullerViewStats(viewIndex).drawCommands }, } }; } private static object OccluderVersionString(in InstanceOcclusionEventStats stats) { return (stats.eventType == InstanceOcclusionEventType.OccluderUpdate || stats.occlusionTest != OcclusionTest.None) ? stats.occluderVersion : "-"; } private static object OcclusionTestString(in InstanceOcclusionEventStats stats) { return (stats.eventType == InstanceOcclusionEventType.OcclusionTest) ? stats.occlusionTest : "-"; } private static object VisibleInstancesString(in InstanceOcclusionEventStats stats) { return (stats.eventType == InstanceOcclusionEventType.OcclusionTest) ? stats.visibleInstances : "-"; } private static object CulledInstancesString(in InstanceOcclusionEventStats stats) { return (stats.eventType == InstanceOcclusionEventType.OcclusionTest) ? stats.culledInstances : "-"; } private static DebugUI.Table.Row AddInstanceOcclusionPassDataRow(int eventIndex) { return new DebugUI.Table.Row { displayName = "", opened = true, isHiddenCallback = () => { return eventIndex >= GetInstanceOcclusionEventCount(); }, children = { new DebugUI.Value { displayName = "View Instance ID", refreshRate = k_RefreshRate, formatString = k_FormatString, getter = () => GetInstanceOcclusionEventStats(eventIndex).viewInstanceID }, new DebugUI.Value { displayName = "Event Type", refreshRate = k_RefreshRate, formatString = k_FormatString, getter = () => $"{GetInstanceOcclusionEventStats(eventIndex).eventType}" }, new DebugUI.Value { displayName = "Occluder Version", refreshRate = k_RefreshRate, formatString = k_FormatString, getter = () => OccluderVersionString(GetInstanceOcclusionEventStats(eventIndex)) }, new DebugUI.Value { displayName = "Subview Mask", refreshRate = k_RefreshRate, formatString = k_FormatString, getter = () => $"0x{GetInstanceOcclusionEventStats(eventIndex).subviewMask:X}" }, new DebugUI.Value { displayName = "Occlusion Test", refreshRate = k_RefreshRate, formatString = k_FormatString, getter = () => $"{OcclusionTestString(GetInstanceOcclusionEventStats(eventIndex))}" }, new DebugUI.Value { displayName = "Visible Instances", refreshRate = k_RefreshRate, formatString = k_FormatString, getter = () => VisibleInstancesString(GetInstanceOcclusionEventStats(eventIndex)) }, new DebugUI.Value { displayName = "Culled Instances", refreshRate = k_RefreshRate, formatString = k_FormatString, getter = () => CulledInstancesString(GetInstanceOcclusionEventStats(eventIndex)) }, } }; } private static DebugUI.Table.Row AddOcclusionContextDataRow(int index) { return new DebugUI.Table.Row { displayName = "", opened = true, isHiddenCallback = () => index >= GetOcclusionContextsCounts(), children = { new DebugUI.Value { displayName = "View Instance ID", refreshRate = k_RefreshRate, formatString = k_FormatString, getter = () => GetOccluderStats(index).viewInstanceID }, new DebugUI.Value { displayName = "Subview Count", refreshRate = k_RefreshRate, formatString = k_FormatString, getter = () => GetOccluderStats(index).subviewCount }, new DebugUI.Value { displayName = "Size Per Subview", refreshRate = k_RefreshRate, formatString = k_FormatString, getter = () => { Vector2Int size = GetOccluderStats(index).occluderMipLayoutSize; return $"{size.x}x{size.y}"; }}, } }; } [DisplayInfo(name = "GPU Resident Drawer", order = 5)] private class SettingsPanel : DebugDisplaySettingsPanel { public override string PanelName => "GPU Resident Drawer"; public override DebugUI.Flags Flags => DebugUI.Flags.EditorForceUpdate; public SettingsPanel(DebugDisplayGPUResidentDrawer data) { var helpBox = new DebugUI.MessageBox() { displayName = "Not Supported", style = MessageBox.Style.Warning, messageCallback = () => { var settings = GPUResidentDrawer.GetGlobalSettingsFromRPAsset(); return GPUResidentDrawer.IsGPUResidentDrawerSupportedBySRP(settings, out var msg, out var _) ? string.Empty : msg; }, isHiddenCallback = () => GPUResidentDrawer.IsEnabled() }; AddWidget(helpBox); AddWidget(new Container() { displayName = Strings.occlusionCullingTitle, isHiddenCallback = () => !GPUResidentDrawer.IsEnabled(), children = { new DebugUI.BoolField { nameAndTooltip = Strings.occlusionTestOverlayEnable, getter = () => data.occlusionTestOverlayEnable, setter = value => data.occlusionTestOverlayEnable = value}, new DebugUI.BoolField { nameAndTooltip = Strings.occlusionTestOverlayCountVisible, getter = () => data.occlusionTestOverlayCountVisible, setter = value => data.occlusionTestOverlayCountVisible = value}, new DebugUI.BoolField { nameAndTooltip = Strings.overrideOcclusionTestToAlwaysPass, getter = () => data.overrideOcclusionTestToAlwaysPass, setter = value => data.overrideOcclusionTestToAlwaysPass = value}, new DebugUI.BoolField { nameAndTooltip = Strings.occluderContextStats, getter = () => data.occluderContextStats, setter = value => data.occluderContextStats = value}, new DebugUI.BoolField { nameAndTooltip = Strings.occluderDebugViewEnable, getter = () => data.occluderDebugViewEnable, setter = value => data.occluderDebugViewEnable = value}, new DebugUI.IntField { nameAndTooltip = Strings.occluderDebugViewIndex, getter = () => data.occluderDebugViewIndex, setter = value => data.occluderDebugViewIndex = value, isHiddenCallback = () => !data.occluderDebugViewEnable, min = () => 0, max = () => Math.Max(GetOcclusionContextsCounts() - 1, 0) }, new DebugUI.FloatField {nameAndTooltip = Strings.occluderDebugViewRangeMin, getter = () => data.occluderDebugViewRange.x, setter = value => data.occluderDebugViewRange.x = value, isHiddenCallback = () => !data.occluderDebugViewEnable}, new DebugUI.FloatField {nameAndTooltip = Strings.occluderDebugViewRangeMax, getter = () => data.occluderDebugViewRange.y, setter = value => data.occluderDebugViewRange.y = value, isHiddenCallback = () => !data.occluderDebugViewEnable} } }); AddOcclusionContextStatsWidget(data); AddWidget(new DebugUI.Container() { displayName = Strings.drawerSettingsContainerName, isHiddenCallback = () => !GPUResidentDrawer.IsEnabled(), children = { new DebugUI.BoolField { nameAndTooltip = Strings.displayBatcherStats, getter = () => data.displayBatcherStats, setter = value => data.displayBatcherStats = value}, } }); AddInstanceCullingStatsWidget(data); } private void AddInstanceCullingStatsWidget(DebugDisplayGPUResidentDrawer data) { var instanceCullerStats = new DebugUI.Foldout { displayName = "Instance Culler Stats", isHeader = true, opened = true, isHiddenCallback = () => !data.displayBatcherStats }; instanceCullerStats.children.Add(new DebugUI.ValueTuple() { displayName = "View Count", values = new[] { new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_FormatString, getter = () => GetInstanceCullerViewCount() } } }); DebugUI.Table viewTable = new DebugUI.Table { displayName = "", isReadOnly = true }; // Always add all possible rows, they are dynamically hidden based on actual data for (int i = 0; i < k_MaxViewCount; i++) viewTable.children.Add(AddInstanceCullerViewDataRow(i)); var perViewStats = new DebugUI.Foldout { displayName = "Per View Stats", isHeader = true, opened = false, isHiddenCallback = () => !data.displayBatcherStats }; perViewStats.children.Add(viewTable); instanceCullerStats.children.Add(perViewStats); DebugUI.Table eventTable = new DebugUI.Table { displayName = "", // First column is empty because its content needs to change dynamically isReadOnly = true }; // Always add all possible rows, they are dynamically hidden based on actual data for (int i = 0; i < k_MaxOcclusionPassCount; i++) eventTable.children.Add(AddInstanceOcclusionPassDataRow(i)); var perEventStats = new DebugUI.Foldout { displayName = "Occlusion Culling Events", isHeader = true, opened = false, isHiddenCallback = () => !data.displayBatcherStats }; perEventStats.children.Add(eventTable); instanceCullerStats.children.Add(perEventStats); AddWidget(instanceCullerStats); } private void AddOcclusionContextStatsWidget(DebugDisplayGPUResidentDrawer data) { var visibilityStats = new DebugUI.Foldout { displayName = "Occlusion Context Stats", isHeader = true, opened = true, isHiddenCallback = () => !data.occluderContextStats }; visibilityStats.children.Add(new DebugUI.ValueTuple() { displayName = "Active Occlusion Contexts", values = new[] { new DebugUI.Value { refreshRate = k_RefreshRate, formatString = k_FormatString, getter = () => GetOcclusionContextsCounts() } } }); DebugUI.Table viewTable = new DebugUI.Table { displayName = "", // First column is empty because its content needs to change dynamically isReadOnly = true }; // Always add all possible rows, they are dynamically hidden based on actual data for (int i = 0; i < k_MaxContextCount; i++) viewTable.children.Add(AddOcclusionContextDataRow(i)); visibilityStats.children.Add(viewTable); AddWidget(visibilityStats); } } #region IDebugDisplaySettingsQuery /// public bool AreAnySettingsActive => displayBatcherStats; /// public bool IsPostProcessingAllowed => true; /// public bool IsLightingActive => true; /// public bool TryGetScreenClearColor(ref Color color) { return false; } /// IDebugDisplaySettingsPanelDisposable IDebugDisplaySettingsData.CreatePanel() { return new SettingsPanel(this); } #endregion } }