using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEditor; namespace UnityEngine.Rendering { /// /// Debug Dispaly Settings Volume /// public class DebugDisplaySettingsVolume : IDebugDisplaySettingsData { /// Current volume debug settings. public IVolumeDebugSettings2 volumeDebugSettings { get; } /// /// Constructor with the settings /// /// public DebugDisplaySettingsVolume(IVolumeDebugSettings2 volumeDebugSettings) { this.volumeDebugSettings = volumeDebugSettings; } internal int volumeComponentEnumIndex; static class Styles { public static readonly GUIContent none = new GUIContent("None"); public static readonly GUIContent editorCamera = new GUIContent("Editor Camera"); } static class Strings { public static readonly string none = "None"; public static readonly string camera = "Camera"; public static readonly string parameter = "Parameter"; public static readonly string component = "Component"; public static readonly string debugViewNotSupported = "Debug view not supported"; public static readonly string volumeInfo = "Volume Info"; public static readonly string interpolatedValue = "Interpolated Value"; public static readonly string defaultValue = "Default Value"; public static readonly string global = "Global"; public static readonly string local = "Local"; } internal static class WidgetFactory { public static DebugUI.EnumField CreateComponentSelector(SettingsPanel panel, Action, int> refresh) { int componentIndex = 0; var componentNames = new List() { Styles.none }; var componentValues = new List() { componentIndex++ }; foreach (var type in panel.data.volumeDebugSettings.volumeComponentsPathAndType) { componentNames.Add(new GUIContent() { text = type.Item1 }); componentValues.Add(componentIndex++); } return new DebugUI.EnumField { displayName = Strings.component, getter = () => panel.data.volumeDebugSettings.selectedComponent, setter = value => panel.data.volumeDebugSettings.selectedComponent = value, enumNames = componentNames.ToArray(), enumValues = componentValues.ToArray(), getIndex = () => panel.data.volumeComponentEnumIndex, setIndex = value => { panel.data.volumeComponentEnumIndex = value; }, onValueChanged = refresh }; } public static DebugUI.ObjectPopupField CreateCameraSelector(SettingsPanel panel, Action, Object> refresh) { return new DebugUI.ObjectPopupField { displayName = Strings.camera, getter = () => panel.data.volumeDebugSettings.selectedCamera, setter = value => { var c = panel.data.volumeDebugSettings.cameras.ToArray(); panel.data.volumeDebugSettings.selectedCameraIndex = Array.IndexOf(c, value as Camera); }, getObjects = () => panel.data.volumeDebugSettings.cameras, onValueChanged = refresh }; } static DebugUI.Widget CreateVolumeParameterWidget(string name, VolumeParameter param, Func isHiddenCallback = null) { if (param == null) return new DebugUI.Value() { displayName = name, getter = () => "-" }; var parameterType = param.GetType(); // Special overrides if (parameterType == typeof(ColorParameter)) { var p = (ColorParameter)param; return new DebugUI.ColorField() { displayName = name, hdr = p.hdr, showAlpha = p.showAlpha, getter = () => p.value, setter = value => p.value = value, isHiddenCallback = isHiddenCallback }; } else if (parameterType == typeof(BoolParameter)) { var p = (BoolParameter)param; return new DebugUI.BoolField() { displayName = name, getter = () => p.value, setter = value => p.value = value, isHiddenCallback = isHiddenCallback }; } else { var typeInfo = parameterType.GetTypeInfo(); var genericArguments = typeInfo.BaseType.GenericTypeArguments; if (genericArguments.Length > 0 && genericArguments[0].IsArray) { return new DebugUI.ObjectListField() { displayName = name, getter = () => (Object[])parameterType.GetProperty("value").GetValue(param, null), type = parameterType }; } } // For parameters that do not override `ToString` var property = param.GetType().GetProperty("value"); var toString = property.PropertyType.GetMethod("ToString", Type.EmptyTypes); if ((toString == null) || (toString.DeclaringType == typeof(object)) || (toString.DeclaringType == typeof(UnityEngine.Object))) { // Check if the parameter has a name var nameProp = property.PropertyType.GetProperty("name"); if (nameProp == null) return new DebugUI.Value() { displayName = name, getter = () => Strings.debugViewNotSupported }; // Return the parameter name return new DebugUI.Value() { displayName = name, getter = () => { var value = property.GetValue(param); if (value == null || value.Equals(null)) return Strings.none; var valueString = nameProp.GetValue(value); return valueString ?? Strings.none; }, isHiddenCallback = isHiddenCallback }; } // Call the ToString method return new DebugUI.Value() { displayName = name, getter = () => { var value = property.GetValue(param); return value == null ? Strings.none : value.ToString(); }, isHiddenCallback = isHiddenCallback }; } public static DebugUI.Table CreateVolumeTable(DebugDisplaySettingsVolume data) { var table = new DebugUI.Table() { displayName = Strings.parameter, isReadOnly = true }; Type selectedType = data.volumeDebugSettings.selectedComponentType; if (selectedType == null) return table; var stack = data.volumeDebugSettings.selectedCameraVolumeStack ?? VolumeManager.instance.stack; var stackComponent = stack.GetComponent(selectedType); if (stackComponent == null) return table; var volumes = data.volumeDebugSettings.GetVolumes(); var inst = (VolumeComponent)ScriptableObject.CreateInstance(selectedType); // First row for volume info var row = new DebugUI.Table.Row() { displayName = Strings.volumeInfo, opened = true, // Open by default for the in-game view children = { new DebugUI.Value() { displayName = Strings.interpolatedValue, getter = () => string.Empty } } }; // Second row, links to volume gameobjects var row2 = new DebugUI.Table.Row() { displayName = "GameObject", children = { new DebugUI.Value() { getter = () => string.Empty } } }; foreach (var volume in volumes) { var profile = volume.HasInstantiatedProfile() ? volume.profile : volume.sharedProfile; row.children.Add(new DebugUI.Value() { displayName = profile.name, getter = () => { var scope = volume.isGlobal ? Strings.global : Strings.local; var weight = data.volumeDebugSettings.GetVolumeWeight(volume); return scope + " (" + (weight * 100f) + "%)"; } }); row2.children.Add(new DebugUI.ObjectField() { displayName = profile.name, getter = () => volume, }); } row.children.Add(new DebugUI.Value() { displayName = Strings.defaultValue, getter = () => string.Empty }); table.children.Add(row); row2.children.Add(new DebugUI.Value() { getter = () => string.Empty }); table.children.Add(row2); // Build rows - recursively handles nested parameters var rows = new List(); int AddParameterRows(Type type, string baseName = null, int skip = 0) { void AddRow(FieldInfo f, string prefix, int skip) { var fieldName = prefix + f.Name; var attr = (DisplayInfoAttribute[])f.GetCustomAttributes(typeof(DisplayInfoAttribute), true); if (attr.Length != 0) fieldName = prefix + attr[0].name; #if UNITY_EDITOR // Would be nice to have the equivalent for the runtime debug. else fieldName = UnityEditor.ObjectNames.NicifyVariableName(fieldName); #endif int currentParam = rows.Count + skip; row = new DebugUI.Table.Row() { displayName = fieldName, children = { CreateVolumeParameterWidget(Strings.interpolatedValue, stackComponent.parameterList[currentParam]) }, }; foreach (var volume in volumes) { VolumeParameter param = null; var profile = volume.HasInstantiatedProfile() ? volume.profile : volume.sharedProfile; if (profile.TryGet(selectedType, out VolumeComponent component)) param = component.parameterList[currentParam]; row.children.Add(CreateVolumeParameterWidget(volume.name + " (" + profile.name + ")", param, () => !component.parameterList[currentParam].overrideState)); } row.children.Add(CreateVolumeParameterWidget(Strings.defaultValue, inst.parameterList[currentParam])); rows.Add(row); } var fields = type .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .OrderBy(t => t.MetadataToken); foreach (var field in fields) { if (field.GetCustomAttributes(typeof(ObsoleteAttribute), false).Length != 0) { skip++; continue; } var fieldType = field.FieldType; if (fieldType.IsSubclassOf(typeof(VolumeParameter))) AddRow(field, baseName ?? string.Empty, skip); else if (!fieldType.IsArray && fieldType.IsClass) skip += AddParameterRows(fieldType, baseName ?? (field.Name + " "), skip); } return skip; } AddParameterRows(selectedType); foreach (var r in rows.OrderBy(t => t.displayName)) table.children.Add(r); data.volumeDebugSettings.RefreshVolumes(volumes); for (int i = 0; i < volumes.Length; i++) table.SetColumnVisibility(i + 1, data.volumeDebugSettings.VolumeHasInfluence(volumes[i])); float timer = 0.0f, refreshRate = 0.2f; table.isHiddenCallback = () => { timer += Time.deltaTime; if (timer >= refreshRate) { if (data.volumeDebugSettings.selectedCamera != null) { 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); } } if (!volumes.SequenceEqual(newVolumes)) { volumes = newVolumes; DebugManager.instance.ReDrawOnScreenDebug(); } } timer = 0.0f; } return false; }; return table; } } [DisplayInfo(name = "Volume", order = int.MaxValue)] internal class SettingsPanel : DebugDisplaySettingsPanel { public SettingsPanel(DebugDisplaySettingsVolume data) : base(data) { AddWidget(WidgetFactory.CreateComponentSelector(this, (_, __) => Refresh())); AddWidget(WidgetFactory.CreateCameraSelector(this, (_, __) => Refresh())); } DebugUI.Table m_VolumeTable = null; void Refresh() { var panel = DebugManager.instance.GetPanel(PanelName); if (panel == null) return; bool needsRefresh = false; if (m_VolumeTable != null) { needsRefresh = true; panel.children.Remove(m_VolumeTable); } if (m_Data.volumeDebugSettings.selectedComponent > 0 && m_Data.volumeDebugSettings.selectedCamera != null) { needsRefresh = true; m_VolumeTable = WidgetFactory.CreateVolumeTable(m_Data); AddWidget(m_VolumeTable); panel.children.Add(m_VolumeTable); } if (needsRefresh) DebugManager.instance.ReDrawOnScreenDebug(); } } #region IDebugDisplaySettingsData /// /// Checks whether ANY of the debug settings are currently active. /// public bool AreAnySettingsActive => false; // Volume Debug Panel doesn't need to modify the renderer data, therefore this property returns false /// /// Checks whether the current state of these settings allows post-processing. /// public bool IsPostProcessingAllowed => true; /// /// Checks whether lighting is active for these settings. /// public bool IsLightingActive => true; /// /// Attempts to get the color used to clear the screen for this debug setting. /// /// A reference to the screen clear color to use. /// "true" if we updated the color, "false" if we didn't change anything. public bool TryGetScreenClearColor(ref Color color) { return false; } /// /// Creates the panel /// /// The panel public IDebugDisplaySettingsPanelDisposable CreatePanel() { return new SettingsPanel(this); } #endregion } }