暫無描述
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DebugDisplaySettingsVolumes.cs 22KB

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