using System; using System.Collections.Generic; using System.Reflection; using System.Linq; namespace UnityEngine.Rendering.HighDefinition { /// /// Type of entity on which frame settings are applied. /// public enum FrameSettingsRenderType { /// Frame settings are applied to a camera. Camera, /// Frame settings are applied to a baked or custom reflection probe. CustomOrBakedReflection, /// Frame settings are applied to a realtime reflection. RealtimeReflection } internal interface IFrameSettingsHistoryContainer : IDebugData { FrameSettingsHistory frameSettingsHistory { get; set; } FrameSettingsOverrideMask frameSettingsMask { get; } FrameSettings frameSettings { get; } bool hasCustomFrameSettings { get; } } struct FrameSettingsHistory { internal static readonly string[] foldoutNames = { "Rendering", "Lighting", "Async Compute", "Light Loop" }; static readonly string[] columnNames = { "Debug", "Sanitized", "Overridden", "Default" }; static readonly string[] columnTooltips = { "Displays Frame Setting values you can modify for the selected Camera.", "Displays the Frame Setting values that the selected Camera uses after Unity checks to see if your HDRP Asset supports them.", "Displays the Frame Setting values that the selected Camera overrides.", "Displays the default Frame Setting values in your current HDRP Asset." }; static readonly Dictionary attributes; static Dictionary>> attributesGroup = new Dictionary>>(); // due to strange management of Scene view cameras, all camera of type Scene will share same FrameSettingsHistory #if UNITY_EDITOR //internal static Camera sceneViewCamera; class MinimalHistoryContainer : IFrameSettingsHistoryContainer { FrameSettingsHistory m_FrameSettingsHistory = new FrameSettingsHistory(); FrameSettingsHistory IFrameSettingsHistoryContainer.frameSettingsHistory { get => m_FrameSettingsHistory; set => m_FrameSettingsHistory = value; } // never used as hasCustomFrameSettings forced to false FrameSettingsOverrideMask IFrameSettingsHistoryContainer.frameSettingsMask => throw new NotImplementedException(); // never used as hasCustomFrameSettings forced to false FrameSettings IFrameSettingsHistoryContainer.frameSettings => throw new NotImplementedException(); // forced to false as there is no control on this object bool IFrameSettingsHistoryContainer.hasCustomFrameSettings => false; public MinimalHistoryContainer() { m_FrameSettingsHistory.debug = GraphicsSettings.TryGetRenderPipelineSettings(out var renderingPathFrameSettings) ? renderingPathFrameSettings.GetDefaultFrameSettings(FrameSettingsRenderType.Camera) : FrameSettingsDefaults.Get(FrameSettingsRenderType.Camera); } Action IDebugData.GetReset() //caution: we actually need to retrieve the //m_FrameSettingsHistory as it is a struct so no direct // => m_FrameSettingsHistory.TriggerReset => () => m_FrameSettingsHistory.TriggerReset(); } internal static IFrameSettingsHistoryContainer sceneViewFrameSettingsContainer = new MinimalHistoryContainer(); #endif internal static HashSet containers = new HashSet(); public FrameSettingsRenderType defaultType; public FrameSettings overridden; public FrameSettingsOverrideMask customMask; public FrameSettings sanitazed; public FrameSettings debug; bool hasDebug; static bool s_PossiblyInUse; public static bool enabled { get { // The feature is enabled when either DebugWindow or DebugRuntimeUI // are displayed. When none are displayed, the feature remain in use // as long as there is one renderer that have debug modification. // We use s_PossiblyInUse to perform the check on the FrameSettingsHistory // collection the less possible (only when we exited all the windows // as long as there is modification). if (!s_PossiblyInUse) return s_PossiblyInUse = DebugManager.instance.displayEditorUI || DebugManager.instance.displayRuntimeUI; else return DebugManager.instance.displayEditorUI || DebugManager.instance.displayRuntimeUI // a && (a = something) different than a &= something as if a is false something is not evaluated in second version || (s_PossiblyInUse && (s_PossiblyInUse = containers.Any(history => history.frameSettingsHistory.hasDebug))); } } /// Initialize data for FrameSettings panel of DebugMenu construction. static FrameSettingsHistory() { attributes = new Dictionary(); attributesGroup = new Dictionary>>(); Dictionary frameSettingsEnumNameMap = FrameSettingsFieldAttribute.GetEnumNameMap(); Type type = typeof(FrameSettingsField); foreach (FrameSettingsField enumVal in frameSettingsEnumNameMap.Keys) { attributes[enumVal] = type.GetField(frameSettingsEnumNameMap[enumVal]).GetCustomAttribute(); } } /// Same than FrameSettings.AggregateFrameSettings but keep history of agregation in a collection for DebugMenu. /// Aggregation is default with override of the renderer then sanitazed depending on supported features of hdrpasset. Then the DebugMenu override occurs. /// The aggregated FrameSettings result. /// The camera rendering. /// Additional data of the camera rendering. /// HDRenderPipelineAsset contening supported features. public static void AggregateFrameSettings(RenderingPathFrameSettings defaultRenderingPathFrameSettings, ref FrameSettings aggregatedFrameSettings, Camera camera, HDAdditionalCameraData additionalData, HDRenderPipelineAsset hdrpAsset, HDRenderPipelineAsset defaultHdrpAsset) { if (hdrpAsset == null && defaultHdrpAsset == null) return; var type = additionalData != null ? additionalData.defaultFrameSettings : FrameSettingsRenderType.Camera; AggregateFrameSettings( ref aggregatedFrameSettings, camera, #if UNITY_EDITOR camera.cameraType == CameraType.SceneView ? sceneViewFrameSettingsContainer : #endif additionalData, ref defaultRenderingPathFrameSettings.GetDefaultFrameSettings(type), //fallback on Camera for SceneCamera and PreviewCamera hdrpAsset != null ? hdrpAsset.currentPlatformRenderPipelineSettings : defaultHdrpAsset.currentPlatformRenderPipelineSettings ); } // Note: this version is the one tested as there is issue getting HDRenderPipelineAsset in batchmode in unit test framework currently. /// Same than FrameSettings.AggregateFrameSettings but keep history of agregation in a collection for DebugMenu. /// Aggregation is default with override of the renderer then sanitazed depending on supported features of hdrpasset. Then the DebugMenu override occurs. /// The aggregated FrameSettings result. /// The camera rendering. /// Additional data of the camera rendering. /// Base framesettings to copy prior any override. /// Currently supported feature for the sanitazation pass. public static void AggregateFrameSettings(ref FrameSettings aggregatedFrameSettings, Camera camera, IFrameSettingsHistoryContainer historyContainer, ref FrameSettings defaultFrameSettings, RenderPipelineSettings supportedFeatures) { FrameSettingsHistory history = historyContainer.frameSettingsHistory; aggregatedFrameSettings = defaultFrameSettings; bool updatedComponent = false; if (historyContainer.hasCustomFrameSettings) { FrameSettings.Override(ref aggregatedFrameSettings, historyContainer.frameSettings, historyContainer.frameSettingsMask); updatedComponent = history.customMask.mask != historyContainer.frameSettingsMask.mask; history.customMask = historyContainer.frameSettingsMask; } history.overridden = aggregatedFrameSettings; FrameSettings.Sanitize(ref aggregatedFrameSettings, camera, supportedFeatures); history.hasDebug = history.debug != aggregatedFrameSettings; updatedComponent |= history.sanitazed != aggregatedFrameSettings; bool dirtyDebugData = !history.hasDebug || updatedComponent; history.sanitazed = aggregatedFrameSettings; if (dirtyDebugData) { // Reset debug data history.debug = history.sanitazed; } else { // Keep user modified debug data // Ensure user is not trying to activate unsupported settings in DebugMenu FrameSettings.Sanitize(ref history.debug, camera, supportedFeatures); } aggregatedFrameSettings = history.debug; historyContainer.frameSettingsHistory = history; } static DebugUI.HistoryBoolField GenerateHistoryBoolField(RenderingPathFrameSettings defaulRenderingPathFrameSettings, IFrameSettingsHistoryContainer frameSettingsContainer, FrameSettingsField field, FrameSettingsFieldAttribute attribute) { string displayIndent = ""; for (int indent = 0; indent < attribute.indentLevel; ++indent) displayIndent += " "; return new DebugUI.HistoryBoolField { displayName = displayIndent + attribute.displayedName, tooltip = attribute.tooltip, getter = () => frameSettingsContainer.frameSettingsHistory.debug.IsEnabled(field), setter = value => { var tmp = frameSettingsContainer.frameSettingsHistory; tmp.debug.SetEnabled(field, value); frameSettingsContainer.frameSettingsHistory = tmp; }, historyGetter = new Func[] { () => frameSettingsContainer.frameSettingsHistory.sanitazed.IsEnabled(field), () => frameSettingsContainer.frameSettingsHistory.overridden.IsEnabled(field), () => defaulRenderingPathFrameSettings?.GetDefaultFrameSettings(frameSettingsContainer.frameSettingsHistory.defaultType).IsEnabled(field) ?? false } }; } static DebugUI.HistoryEnumField GenerateHistoryEnumField(RenderingPathFrameSettings defaulRenderingPathFrameSettings, IFrameSettingsHistoryContainer frameSettingsContainer, FrameSettingsField field, FrameSettingsFieldAttribute attribute, Type autoEnum) { string displayIndent = ""; for (int indent = 0; indent < attribute.indentLevel; ++indent) displayIndent += " "; return new DebugUI.HistoryEnumField { displayName = displayIndent + attribute.displayedName, tooltip = attribute.tooltip, getter = () => frameSettingsContainer.frameSettingsHistory.debug.IsEnabled(field) ? 1 : 0, setter = value => { var tmp = frameSettingsContainer.frameSettingsHistory; //indexer with struct will create a copy tmp.debug.SetEnabled(field, value == 1); frameSettingsContainer.frameSettingsHistory = tmp; }, autoEnum = autoEnum, // Contrarily to other enum of DebugMenu, we do not need to stock index as // it can be computed again with data in the dedicated debug section of history getIndex = () => frameSettingsContainer.frameSettingsHistory.debug.IsEnabled(field) ? 1 : 0, setIndex = (int a) => { }, historyIndexGetter = new Func[] { () => frameSettingsContainer.frameSettingsHistory.sanitazed.IsEnabled(field) ? 1 : 0, () => frameSettingsContainer.frameSettingsHistory.overridden.IsEnabled(field) ? 1 : 0, () => defaulRenderingPathFrameSettings .GetDefaultFrameSettings(frameSettingsContainer.frameSettingsHistory.defaultType).IsEnabled(field) ? 1 : 0 } }; } static ObservableList GenerateHistoryArea(IFrameSettingsHistoryContainer frameSettingsContainer, int groupIndex) { if (!attributesGroup.ContainsKey(groupIndex) || attributesGroup[groupIndex] == null) attributesGroup[groupIndex] = attributes?.Where(pair => pair.Value?.group == groupIndex)?.OrderBy(pair => pair.Value.orderInGroup); if (!attributesGroup.ContainsKey(groupIndex)) throw new ArgumentException("Unknown groupIndex"); var area = new ObservableList(); var defaulRenderingPathFrameSettings = GraphicsSettings.GetRenderPipelineSettings(); foreach (var field in attributesGroup[groupIndex]) { switch (field.Value.type) { case FrameSettingsFieldAttribute.DisplayType.BoolAsCheckbox: area.Add(GenerateHistoryBoolField(defaulRenderingPathFrameSettings, frameSettingsContainer, field.Key, field.Value)); break; case FrameSettingsFieldAttribute.DisplayType.BoolAsEnumPopup: area.Add(GenerateHistoryEnumField(defaulRenderingPathFrameSettings, frameSettingsContainer, field.Key, field.Value, RetrieveEnumTypeByField(field.Key) )); break; case FrameSettingsFieldAttribute.DisplayType.Others: // for now, skip other display settings. Add them if needed break; } } return area; } internal static DebugUI.Widget[] GenerateFrameSettingsPanelContent(IFrameSettingsHistoryContainer frameSettingsContainer) { #if UNITY_EDITOR frameSettingsContainer ??= sceneViewFrameSettingsContainer; #endif if (frameSettingsContainer == null) return Array.Empty(); var panelContent = new DebugUI.Widget[foldoutNames.Length]; for (int index = 0; index < foldoutNames.Length; ++index) { panelContent[index] = new DebugUI.Foldout(foldoutNames[index], GenerateHistoryArea(frameSettingsContainer, index), columnNames, columnTooltips); } return panelContent; } static Type RetrieveEnumTypeByField(FrameSettingsField field) { switch (field) { case FrameSettingsField.LitShaderMode: return typeof(LitShaderMode); default: throw new ArgumentException("Unknown enum type for this field"); } } /// Register FrameSettingsHistory for DebugMenu public static IDebugData RegisterDebug(IFrameSettingsHistoryContainer frameSettingsContainer, bool sceneViewCamera = false) { #if UNITY_EDITOR if (sceneViewCamera) frameSettingsContainer = sceneViewFrameSettingsContainer; #endif containers.Add(frameSettingsContainer); return frameSettingsContainer; } public static void Clear() { #if UNITY_EDITOR sceneViewFrameSettingsContainer = new MinimalHistoryContainer(); #endif containers.Clear(); } /// Unregister FrameSettingsHistory for DebugMenu public static void UnRegisterDebug(IFrameSettingsHistoryContainer container) { if (container != null) containers.Remove(container); } /// Check if a camera is registered. public static bool IsRegistered(IFrameSettingsHistoryContainer container, bool sceneViewCamera = false) { if (sceneViewCamera) return true; return containers.Contains(container); } internal void TriggerReset() { // Fix UUM-36594 "Exiting PlayMode after RenderingDebugger reset makes scene overexposed" // Overwriting of debug settings is conditioned on hasDebug because it's possible that AggregateFrameSettings // has never been called for that frameSettingsContainer, in which case the sanitized settings was never // initialized and contains all zeros (can happen with a layout where SceneView is not visible in Play Mode). if (hasDebug) debug = sanitazed; hasDebug = false; } } }