A game about forced loneliness, made by TACStudios
at master 482 lines 23 kB view raw
1using System; 2using System.Collections.Generic; 3using System.Linq; 4using System.Reflection; 5using UnityEditor; 6 7namespace UnityEngine.Rendering 8{ 9 /// <summary> 10 /// Debug Display Settings Volume 11 /// </summary> 12 public class DebugDisplaySettingsVolume : IDebugDisplaySettingsData 13 { 14 /// <summary>Current volume debug settings.</summary> 15 public IVolumeDebugSettings volumeDebugSettings { get; } 16 17 /// <summary> 18 /// Constructor with the settings 19 /// </summary> 20 /// <param name="volumeDebugSettings">The volume debug settings object used for configuration.</param> 21 public DebugDisplaySettingsVolume(IVolumeDebugSettings volumeDebugSettings) 22 { 23 this.volumeDebugSettings = volumeDebugSettings; 24 } 25 26 internal int volumeComponentEnumIndex; 27 28 internal Dictionary<string, VolumeComponent> debugState = new Dictionary<string, VolumeComponent>(); 29 30 static class Styles 31 { 32 public static readonly GUIContent none = new GUIContent("None"); 33 public static readonly GUIContent editorCamera = new GUIContent("Editor Camera"); 34 } 35 36 static class Strings 37 { 38 public static readonly string none = "None"; 39 public static readonly string camera = "Camera"; 40 public static readonly string parameter = "Parameter"; 41 public static readonly string component = "Component"; 42 public static readonly string debugViewNotSupported = "Debug view not supported"; 43 public static readonly string volumeInfo = "Volume Info"; 44 public static readonly string resultValue = "Result"; 45 public static readonly string resultValueTooltip = "The interpolated result value of the parameter. This value is used to render the camera."; 46 public static readonly string globalDefaultValue = "Default"; 47 public static readonly string globalDefaultValueTooltip = "Default value for this parameter, defined by the Default Volume Profile in Global Settings."; 48 public static readonly string qualityLevelValue = "SRP Asset"; 49 public static readonly string qualityLevelValueTooltip = "Override value for this parameter, defined by the Volume Profile in the current SRP Asset."; 50 public static readonly string global = "Global"; 51 public static readonly string local = "Local"; 52 } 53 54 const string k_PanelTitle = "Volume"; 55 56#if UNITY_EDITOR 57 internal static void OpenInRenderingDebugger() 58 { 59 EditorApplication.ExecuteMenuItem("Window/Analysis/Rendering Debugger"); 60 var idx = DebugManager.instance.FindPanelIndex(k_PanelTitle); 61 if (idx != -1) 62 DebugManager.instance.RequestEditorWindowPanelIndex(idx); 63 } 64#endif 65 66 internal static class WidgetFactory 67 { 68 public static DebugUI.EnumField CreateComponentSelector(SettingsPanel panel, Action<DebugUI.Field<int>, int> refresh) 69 { 70 int componentIndex = 0; 71 var componentNames = new List<GUIContent>() { Styles.none }; 72 var componentValues = new List<int>() { componentIndex++ }; 73 74 var volumesAndTypes = VolumeManager.instance.GetVolumeComponentsForDisplay(GraphicsSettings.currentRenderPipelineAssetType); 75 foreach (var type in volumesAndTypes) 76 { 77 componentNames.Add(new GUIContent() { text = type.Item1 }); 78 componentValues.Add(componentIndex++); 79 } 80 81 return new DebugUI.EnumField 82 { 83 displayName = Strings.component, 84 getter = () => panel.data.volumeDebugSettings.selectedComponent, 85 setter = value => panel.data.volumeDebugSettings.selectedComponent = value, 86 enumNames = componentNames.ToArray(), 87 enumValues = componentValues.ToArray(), 88 getIndex = () => panel.data.volumeComponentEnumIndex, 89 setIndex = value => { panel.data.volumeComponentEnumIndex = value; }, 90 onValueChanged = refresh 91 }; 92 } 93 94 public static DebugUI.ObjectPopupField CreateCameraSelector(SettingsPanel panel, Action<DebugUI.Field<Object>, Object> refresh) 95 { 96 return new DebugUI.ObjectPopupField 97 { 98 displayName = Strings.camera, 99 getter = () => panel.data.volumeDebugSettings.selectedCamera, 100 setter = value => 101 { 102 var c = panel.data.volumeDebugSettings.cameras.ToArray(); 103 panel.data.volumeDebugSettings.selectedCameraIndex = Array.IndexOf(c, value as Camera); 104 }, 105 getObjects = () => panel.data.volumeDebugSettings.cameras, 106 onValueChanged = refresh 107 }; 108 } 109 110 static DebugUI.Widget CreateVolumeParameterWidget(string name, VolumeParameter param, Func<bool> isHiddenCallback = null) 111 { 112 if (param == null) 113 return new DebugUI.Value() { displayName = name, getter = () => "-" }; 114 115 var parameterType = param.GetType(); 116 117 // Special overrides 118 if (parameterType == typeof(ColorParameter)) 119 { 120 var p = (ColorParameter)param; 121 return new DebugUI.ColorField() 122 { 123 displayName = name, 124 hdr = p.hdr, 125 showAlpha = p.showAlpha, 126 getter = () => p.value, 127 setter = value => p.value = value, 128 isHiddenCallback = isHiddenCallback 129 }; 130 } 131 else if (parameterType == typeof(BoolParameter)) 132 { 133 var p = (BoolParameter)param; 134 return new DebugUI.BoolField() 135 { 136 displayName = name, 137 getter = () => p.value, 138 setter = value => p.value = value, 139 isHiddenCallback = isHiddenCallback 140 }; 141 } 142 else 143 { 144 var typeInfo = parameterType.GetTypeInfo(); 145 var genericArguments = typeInfo.BaseType.GenericTypeArguments; 146 if (genericArguments.Length > 0 && genericArguments[0].IsArray) 147 { 148 return new DebugUI.ObjectListField() 149 { 150 displayName = name, 151 getter = () => (Object[])parameterType.GetProperty("value").GetValue(param, null), 152 type = parameterType 153 }; 154 } 155 } 156 157 // For parameters that do not override `ToString` 158 var property = param.GetType().GetProperty("value"); 159 var toString = property.PropertyType.GetMethod("ToString", Type.EmptyTypes); 160 if ((toString == null) || (toString.DeclaringType == typeof(object)) || (toString.DeclaringType == typeof(UnityEngine.Object))) 161 { 162 // Check if the parameter has a name 163 var nameProp = property.PropertyType.GetProperty("name"); 164 if (nameProp == null) 165 return new DebugUI.Value() { displayName = name, getter = () => Strings.debugViewNotSupported }; 166 167 // Return the parameter name 168 return new DebugUI.Value() 169 { 170 displayName = name, 171 getter = () => 172 { 173 var value = property.GetValue(param); 174 if (value == null || value.Equals(null)) 175 return Strings.none; 176 var valueString = nameProp.GetValue(value); 177 return valueString ?? Strings.none; 178 }, 179 isHiddenCallback = isHiddenCallback 180 }; 181 } 182 183 // Call the ToString method 184 return new DebugUI.Value() 185 { 186 displayName = name, 187 getter = () => 188 { 189 var value = property.GetValue(param); 190 return value == null ? Strings.none : value.ToString(); 191 }, 192 isHiddenCallback = isHiddenCallback 193 }; 194 } 195 196 static DebugUI.Value s_EmptyDebugUIValue = new DebugUI.Value { getter = () => string.Empty }; 197 198 public static DebugUI.Table CreateVolumeTable(DebugDisplaySettingsVolume data) 199 { 200 var table = new DebugUI.Table() 201 { 202 displayName = Strings.parameter, 203 isReadOnly = true, 204 isHiddenCallback = () => data.volumeDebugSettings.selectedComponent == 0 205 }; 206 207 Type selectedType = data.volumeDebugSettings.selectedComponentType; 208 if (selectedType == null) 209 return table; 210 211 var volumeManager = VolumeManager.instance; 212 var stack = data.volumeDebugSettings.selectedCameraVolumeStack ?? volumeManager.stack; 213 var stackComponent = stack.GetComponent(selectedType); 214 if (stackComponent == null) 215 return table; 216 217 var volumes = data.volumeDebugSettings.GetVolumes(); 218 219 // First row for volume info 220 var row1 = new DebugUI.Table.Row() 221 { 222 displayName = Strings.volumeInfo, 223 opened = true, // Open by default for the in-game view 224 children = 225 { 226 new DebugUI.Value() 227 { 228 displayName = Strings.resultValue, 229 tooltip = Strings.resultValueTooltip, 230 getter = () => string.Empty 231 } 232 } 233 }; 234 235 // Second row, links to volume gameobjects 236 var row2 = new DebugUI.Table.Row() 237 { 238 displayName = "GameObject", 239 children = { s_EmptyDebugUIValue } 240 }; 241 242 // Third row, links to volume profile assets 243 var row3 = new DebugUI.Table.Row() 244 { 245 displayName = "Volume Profile", 246 children = { s_EmptyDebugUIValue } 247 }; 248 249 // Fourth row, empty (to separate from actual data) 250 var row4 = new DebugUI.Table.Row() 251 { 252 displayName = string.Empty , 253 children = { s_EmptyDebugUIValue } 254 }; 255 256 foreach (var volume in volumes) 257 { 258 var profile = volume.HasInstantiatedProfile() ? volume.profile : volume.sharedProfile; 259 row1.children.Add(new DebugUI.Value() 260 { 261 displayName = profile.name, 262 tooltip = $"Override value for this parameter, defined by {profile.name}", 263 getter = () => 264 { 265 var scope = volume.isGlobal ? Strings.global : Strings.local; 266 var weight = data.volumeDebugSettings.GetVolumeWeight(volume); 267 return scope + " (" + (weight * 100f) + "%)"; 268 } 269 }); 270 row2.children.Add(new DebugUI.ObjectField() { displayName = string.Empty, getter = () => volume }); 271 row3.children.Add(new DebugUI.ObjectField() { displayName = string.Empty, getter = () => profile }); 272 row4.children.Add(s_EmptyDebugUIValue); 273 } 274 275 // Default value profiles 276 var globalDefaultComponent = GetSelectedVolumeComponent(volumeManager.globalDefaultProfile); 277 var qualityDefaultComponent = GetSelectedVolumeComponent(volumeManager.qualityDefaultProfile); 278 List<(VolumeProfile, VolumeComponent)> customDefaultComponents = new(); 279 if (volumeManager.customDefaultProfiles != null) 280 { 281 foreach (var customProfile in volumeManager.customDefaultProfiles) 282 { 283 var customDefaultComponent = GetSelectedVolumeComponent(customProfile); 284 if (customDefaultComponent != null) 285 customDefaultComponents.Add((customProfile, customDefaultComponent)); 286 } 287 } 288 289 foreach (var (customProfile, _) in customDefaultComponents) 290 { 291 row1.children.Add(new DebugUI.Value() { displayName = customProfile.name, getter = () => string.Empty }); 292 row2.children.Add(s_EmptyDebugUIValue); 293 row3.children.Add(new DebugUI.ObjectField() { displayName = string.Empty, getter = () => customProfile }); 294 row4.children.Add(s_EmptyDebugUIValue); 295 } 296 297 row1.children.Add(new DebugUI.Value() { displayName = Strings.qualityLevelValue, tooltip = Strings.qualityLevelValueTooltip, getter = () => string.Empty }); 298 row2.children.Add(s_EmptyDebugUIValue); 299 row3.children.Add(new DebugUI.ObjectField() { displayName = string.Empty, getter = () => volumeManager.qualityDefaultProfile }); 300 row4.children.Add(s_EmptyDebugUIValue); 301 302 row1.children.Add(new DebugUI.Value() { displayName = Strings.globalDefaultValue, tooltip = Strings.globalDefaultValueTooltip, getter = () => string.Empty }); 303 row2.children.Add(s_EmptyDebugUIValue); 304 row3.children.Add(new DebugUI.ObjectField() { displayName = string.Empty, getter = () => volumeManager.globalDefaultProfile }); 305 row4.children.Add(s_EmptyDebugUIValue); 306 307 table.children.Add(row1); 308 table.children.Add(row2); 309 table.children.Add(row3); 310 table.children.Add(row4); 311 312 VolumeComponent GetSelectedVolumeComponent(VolumeProfile profile) 313 { 314 if (profile != null) 315 { 316 foreach (var component in profile.components) 317 if (component.GetType() == selectedType) 318 return component; 319 } 320 return null; 321 } 322 323 // Build rows - recursively handles nested parameters 324 var rows = new List<DebugUI.Table.Row>(); 325 int AddParameterRows(Type type, string baseName = null, int skip = 0) 326 { 327 void AddRow(FieldInfo f, string prefix, int skip) 328 { 329 var fieldName = prefix + f.Name; 330 var attr = (DisplayInfoAttribute[])f.GetCustomAttributes(typeof(DisplayInfoAttribute), true); 331 if (attr.Length != 0) 332 fieldName = prefix + attr[0].name; 333#if UNITY_EDITOR 334 // Would be nice to have the equivalent for the runtime debug. 335 else 336 fieldName = UnityEditor.ObjectNames.NicifyVariableName(fieldName); 337#endif 338 339 int currentParam = rows.Count + skip; 340 DebugUI.Table.Row row = new DebugUI.Table.Row() 341 { 342 displayName = fieldName, 343 children = { CreateVolumeParameterWidget(Strings.resultValue, stackComponent.parameterList[currentParam]) }, 344 }; 345 346 foreach (var volume in volumes) 347 { 348 VolumeParameter param = null; 349 var profile = volume.HasInstantiatedProfile() ? volume.profile : volume.sharedProfile; 350 if (profile.TryGet(selectedType, out VolumeComponent component)) 351 param = component.parameterList[currentParam]; 352 row.children.Add(CreateVolumeParameterWidget(volume.name + " (" + profile.name + ")", param, () => !component.parameterList[currentParam].overrideState)); 353 } 354 355 foreach (var (customProfile, customComponent) in customDefaultComponents) 356 row.children.Add(CreateVolumeParameterWidget(customProfile.name, 357 customComponent != null ? customComponent.parameterList[currentParam] : null)); 358 359 row.children.Add(CreateVolumeParameterWidget(Strings.qualityLevelValue, 360 qualityDefaultComponent != null ? qualityDefaultComponent.parameterList[currentParam] : null)); 361 362 row.children.Add(CreateVolumeParameterWidget(Strings.globalDefaultValue, 363 globalDefaultComponent != null ? globalDefaultComponent.parameterList[currentParam] : null)); 364 365 rows.Add(row); 366 } 367 368 var fields = type 369 .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) 370 .OrderBy(t => t.MetadataToken); 371 foreach (var field in fields) 372 { 373 if (field.GetCustomAttributes(typeof(ObsoleteAttribute), false).Length != 0) 374 { 375 skip++; 376 continue; 377 } 378 var fieldType = field.FieldType; 379 if (fieldType.IsSubclassOf(typeof(VolumeParameter))) 380 AddRow(field, baseName ?? string.Empty, skip); 381 else if (!fieldType.IsArray && fieldType.IsClass) 382 skip += AddParameterRows(fieldType, baseName ?? (field.Name + " "), skip); 383 } 384 return skip; 385 } 386 387 AddParameterRows(selectedType); 388 foreach (var r in rows.OrderBy(t => t.displayName)) 389 table.children.Add(r); 390 391 data.volumeDebugSettings.RefreshVolumes(volumes); 392 for (int i = 0; i < volumes.Length; i++) 393 table.SetColumnVisibility(i + 1, data.volumeDebugSettings.VolumeHasInfluence(volumes[i])); 394 395 float timer = 0.0f, refreshRate = 0.2f; 396 table.isHiddenCallback = () => 397 { 398 timer += Time.deltaTime; 399 if (timer >= refreshRate) 400 { 401 if (data.volumeDebugSettings.selectedCamera != null) 402 { 403 var newVolumes = data.volumeDebugSettings.GetVolumes(); 404 if (!data.volumeDebugSettings.RefreshVolumes(newVolumes)) 405 { 406 for (int i = 0; i < newVolumes.Length; i++) 407 { 408 var visible = data.volumeDebugSettings.VolumeHasInfluence(newVolumes[i]); 409 table.SetColumnVisibility(i + 1, visible); 410 } 411 } 412 413 if (!volumes.SequenceEqual(newVolumes)) 414 { 415 volumes = newVolumes; 416 DebugManager.instance.ReDrawOnScreenDebug(); 417 } 418 } 419 420 timer = 0.0f; 421 } 422 return false; 423 }; 424 425 return table; 426 } 427 } 428 429 [DisplayInfo(name = k_PanelTitle, order = int.MaxValue)] 430 internal class SettingsPanel : DebugDisplaySettingsPanel<DebugDisplaySettingsVolume> 431 { 432 public SettingsPanel(DebugDisplaySettingsVolume data) 433 : base(data) 434 { 435 AddWidget(WidgetFactory.CreateCameraSelector(this, (_, __) => Refresh())); 436 AddWidget(WidgetFactory.CreateComponentSelector(this, (_, __) => Refresh())); 437 m_VolumeTable = WidgetFactory.CreateVolumeTable(m_Data); 438 AddWidget(m_VolumeTable); 439 } 440 441 DebugUI.Table m_VolumeTable = null; 442 void Refresh() 443 { 444 var panel = DebugManager.instance.GetPanel(PanelName); 445 if (panel == null) 446 return; 447 448 bool needsRefresh = false; 449 if (m_VolumeTable != null) 450 { 451 needsRefresh = true; 452 panel.children.Remove(m_VolumeTable); 453 } 454 455 if (m_Data.volumeDebugSettings.selectedComponent > 0 && m_Data.volumeDebugSettings.selectedCamera != null) 456 { 457 needsRefresh = true; 458 m_VolumeTable = WidgetFactory.CreateVolumeTable(m_Data); 459 AddWidget(m_VolumeTable); 460 panel.children.Add(m_VolumeTable); 461 } 462 463 if (needsRefresh) 464 DebugManager.instance.ReDrawOnScreenDebug(); 465 } 466 } 467 468 #region IDebugDisplaySettingsData 469 /// <summary> 470 /// Checks whether ANY of the debug settings are currently active. 471 /// </summary> 472 public bool AreAnySettingsActive => false; // Volume Debug Panel doesn't need to modify the renderer data, therefore this property returns false 473 474 /// <inheritdoc/> 475 public IDebugDisplaySettingsPanelDisposable CreatePanel() 476 { 477 return new SettingsPanel(this); 478 } 479 480 #endregion 481 } 482}