DebugWindow.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. #if ENABLE_INPUT_SYSTEM && ENABLE_INPUT_SYSTEM_PACKAGE
  2. #define USE_INPUT_SYSTEM
  3. #endif
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using UnityEditor.Callbacks;
  8. using UnityEngine;
  9. using UnityEngine.Rendering;
  10. namespace UnityEditor.Rendering
  11. {
  12. #pragma warning disable 414
  13. [Serializable]
  14. sealed class WidgetStateDictionary : SerializedDictionary<string, DebugState> {}
  15. sealed class DebugWindowSettings : ScriptableObject
  16. {
  17. // Keep these settings in a separate scriptable object so we can handle undo/redo on them
  18. // without the rest of the debug window interfering
  19. public int currentStateHash;
  20. public int selectedPanel;
  21. void OnEnable()
  22. {
  23. hideFlags = HideFlags.HideAndDontSave;
  24. }
  25. }
  26. sealed class DebugWindow : EditorWindow
  27. {
  28. static readonly GUIContent k_ResetButtonContent = new GUIContent("Reset");
  29. //static readonly GUIContent k_SaveButtonContent = new GUIContent("Save");
  30. //static readonly GUIContent k_LoadButtonContent = new GUIContent("Load");
  31. //static bool isMultiview = false;
  32. static Styles s_Styles;
  33. static GUIStyle s_SplitterLeft;
  34. static float splitterPos = 150f;
  35. const float minSideBarWidth = 100;
  36. const float minContentWidth = 100;
  37. bool dragging = false;
  38. [SerializeField]
  39. WidgetStateDictionary m_WidgetStates;
  40. [SerializeField]
  41. DebugWindowSettings m_Settings;
  42. [SerializeField]
  43. int m_DebugTreeState;
  44. bool m_IsDirty;
  45. Vector2 m_PanelScroll;
  46. Vector2 m_ContentScroll;
  47. static bool s_TypeMapDirty;
  48. static Dictionary<Type, Type> s_WidgetStateMap; // DebugUI.Widget type -> DebugState type
  49. static Dictionary<Type, DebugUIDrawer> s_WidgetDrawerMap; // DebugUI.Widget type -> DebugUIDrawer
  50. static bool s_Open;
  51. public static bool open
  52. {
  53. get => s_Open;
  54. private set
  55. {
  56. if (s_Open ^ value)
  57. OnDebugWindowToggled?.Invoke(value);
  58. s_Open = value;
  59. }
  60. }
  61. static event Action<bool> OnDebugWindowToggled;
  62. [DidReloadScripts]
  63. static void OnEditorReload()
  64. {
  65. s_TypeMapDirty = true;
  66. //find if it where open, relink static event end propagate the info
  67. open = (Resources.FindObjectsOfTypeAll<DebugWindow>()?.Length ?? 0) > 0;
  68. if (OnDebugWindowToggled == null)
  69. OnDebugWindowToggled += DebugManager.instance.ToggleEditorUI;
  70. DebugManager.instance.ToggleEditorUI(open);
  71. }
  72. static void RebuildTypeMaps()
  73. {
  74. // Map states to widget (a single state can map to several widget types if the value to
  75. // serialize is the same)
  76. var attrType = typeof(DebugStateAttribute);
  77. var stateTypes = CoreUtils.GetAllTypesDerivedFrom<DebugState>()
  78. .Where(
  79. t => t.IsDefined(attrType, false)
  80. && !t.IsAbstract
  81. );
  82. s_WidgetStateMap = new Dictionary<Type, Type>();
  83. foreach (var stateType in stateTypes)
  84. {
  85. var attr = (DebugStateAttribute)stateType.GetCustomAttributes(attrType, false)[0];
  86. foreach (var t in attr.types)
  87. s_WidgetStateMap.Add(t, stateType);
  88. }
  89. // Drawers
  90. attrType = typeof(DebugUIDrawerAttribute);
  91. var types = CoreUtils.GetAllTypesDerivedFrom<DebugUIDrawer>()
  92. .Where(
  93. t => t.IsDefined(attrType, false)
  94. && !t.IsAbstract
  95. );
  96. s_WidgetDrawerMap = new Dictionary<Type, DebugUIDrawer>();
  97. foreach (var t in types)
  98. {
  99. var attr = (DebugUIDrawerAttribute)t.GetCustomAttributes(attrType, false)[0];
  100. var inst = (DebugUIDrawer)Activator.CreateInstance(t);
  101. s_WidgetDrawerMap.Add(attr.type, inst);
  102. }
  103. // Done
  104. s_TypeMapDirty = false;
  105. }
  106. [MenuItem("Window/Render Pipeline/Render Pipeline Debug", priority = 10005)] // 112 is hardcoded number given by the UxTeam to fit correctly in the Windows menu
  107. static void Init()
  108. {
  109. var window = GetWindow<DebugWindow>();
  110. window.titleContent = new GUIContent("Debug");
  111. if(OnDebugWindowToggled == null)
  112. OnDebugWindowToggled += DebugManager.instance.ToggleEditorUI;
  113. open = true;
  114. }
  115. void OnEnable()
  116. {
  117. DebugManager.instance.refreshEditorRequested = false;
  118. hideFlags = HideFlags.HideAndDontSave;
  119. autoRepaintOnSceneChange = true;
  120. if (m_Settings == null)
  121. m_Settings = CreateInstance<DebugWindowSettings>();
  122. // States are ScriptableObjects (necessary for Undo/Redo) but are not saved on disk so when the editor is closed then reopened, any existing debug window will have its states set to null
  123. // Since we don't care about persistance in this case, we just re-init everything.
  124. if (m_WidgetStates == null || !AreWidgetStatesValid())
  125. m_WidgetStates = new WidgetStateDictionary();
  126. if (s_WidgetStateMap == null || s_WidgetDrawerMap == null || s_TypeMapDirty)
  127. RebuildTypeMaps();
  128. Undo.undoRedoPerformed += OnUndoRedoPerformed;
  129. DebugManager.instance.onSetDirty += MarkDirty;
  130. // First init
  131. m_DebugTreeState = DebugManager.instance.GetState();
  132. UpdateWidgetStates();
  133. EditorApplication.update -= Repaint;
  134. var panels = DebugManager.instance.panels;
  135. var selectedPanelIndex = m_Settings.selectedPanel;
  136. if (selectedPanelIndex >= 0
  137. && selectedPanelIndex < panels.Count
  138. && panels[selectedPanelIndex].editorForceUpdate)
  139. EditorApplication.update += Repaint;
  140. }
  141. // Note: this won't get called if the window is opened when the editor itself is closed
  142. void OnDestroy()
  143. {
  144. open = false;
  145. DebugManager.instance.onSetDirty -= MarkDirty;
  146. Undo.ClearUndo(m_Settings);
  147. DestroyWidgetStates();
  148. }
  149. public void DestroyWidgetStates()
  150. {
  151. if (m_WidgetStates != null)
  152. {
  153. // Clear all the states from memory
  154. foreach (var state in m_WidgetStates)
  155. {
  156. var s = state.Value;
  157. Undo.ClearUndo(s); // Don't leave dangling states in the global undo/redo stack
  158. DestroyImmediate(s);
  159. }
  160. m_WidgetStates.Clear();
  161. }
  162. }
  163. bool AreWidgetStatesValid()
  164. {
  165. foreach (var state in m_WidgetStates)
  166. {
  167. if (state.Value == null)
  168. {
  169. return false;
  170. }
  171. }
  172. return true;
  173. }
  174. void MarkDirty()
  175. {
  176. m_IsDirty = true;
  177. }
  178. // We use item states to keep a cached value of each serializable debug items in order to
  179. // handle domain reloads, play mode entering/exiting and undo/redo
  180. // Note: no removal of orphan states
  181. void UpdateWidgetStates()
  182. {
  183. foreach (var panel in DebugManager.instance.panels)
  184. UpdateWidgetStates(panel);
  185. }
  186. void UpdateWidgetStates(DebugUI.IContainer container)
  187. {
  188. // Skip runtime only containers, we won't draw them so no need to serialize them either
  189. var actualWidget = container as DebugUI.Widget;
  190. if (actualWidget != null && actualWidget.isInactiveInEditor)
  191. return;
  192. // Recursively update widget states
  193. foreach (var widget in container.children)
  194. {
  195. // Skip non-serializable widgets but still traverse them in case one of their
  196. // children needs serialization support
  197. var valueField = widget as DebugUI.IValueField;
  198. if (valueField != null)
  199. {
  200. // Skip runtime & readonly only items
  201. if (widget.isInactiveInEditor)
  202. return;
  203. var widgetType = widget.GetType();
  204. string guid = widget.queryPath;
  205. Type stateType;
  206. s_WidgetStateMap.TryGetValue(widgetType, out stateType);
  207. // Create missing states & recreate the ones that are null
  208. if (stateType != null)
  209. {
  210. if (!m_WidgetStates.ContainsKey(guid) || m_WidgetStates[guid] == null)
  211. {
  212. var inst = (DebugState)CreateInstance(stateType);
  213. inst.queryPath = guid;
  214. inst.SetValue(valueField.GetValue(), valueField);
  215. m_WidgetStates[guid] = inst;
  216. }
  217. }
  218. }
  219. // Recurse if the widget is a container
  220. var containerField = widget as DebugUI.IContainer;
  221. if (containerField != null)
  222. UpdateWidgetStates(containerField);
  223. }
  224. }
  225. public void ApplyStates(bool forceApplyAll = false)
  226. {
  227. if (!forceApplyAll && DebugState.m_CurrentDirtyState != null)
  228. {
  229. ApplyState(DebugState.m_CurrentDirtyState.queryPath, DebugState.m_CurrentDirtyState);
  230. DebugState.m_CurrentDirtyState = null;
  231. return;
  232. }
  233. foreach (var state in m_WidgetStates)
  234. ApplyState(state.Key, state.Value);
  235. DebugState.m_CurrentDirtyState = null;
  236. }
  237. void ApplyState(string queryPath, DebugState state)
  238. {
  239. var widget = DebugManager.instance.GetItem(queryPath) as DebugUI.IValueField;
  240. if (widget == null)
  241. return;
  242. widget.SetValue(state.GetValue());
  243. }
  244. void OnUndoRedoPerformed()
  245. {
  246. int stateHash = ComputeStateHash();
  247. // Something has been undone / redone, re-apply states to the debug tree
  248. if (stateHash != m_Settings.currentStateHash)
  249. {
  250. ApplyStates(true);
  251. m_Settings.currentStateHash = stateHash;
  252. }
  253. Repaint();
  254. }
  255. int ComputeStateHash()
  256. {
  257. unchecked
  258. {
  259. int hash = 13;
  260. foreach (var state in m_WidgetStates)
  261. hash = hash * 23 + state.Value.GetHashCode();
  262. return hash;
  263. }
  264. }
  265. void Update()
  266. {
  267. // If the render pipeline asset has been reloaded we force-refresh widget states in case
  268. // some debug values need to be refresh/recreated as well (e.g. frame settings on HD)
  269. if (DebugManager.instance.refreshEditorRequested)
  270. {
  271. DestroyWidgetStates();
  272. DebugManager.instance.refreshEditorRequested = false;
  273. }
  274. int treeState = DebugManager.instance.GetState();
  275. if (m_DebugTreeState != treeState || m_IsDirty)
  276. {
  277. UpdateWidgetStates();
  278. ApplyStates();
  279. m_DebugTreeState = treeState;
  280. m_IsDirty = false;
  281. }
  282. }
  283. void OnGUI()
  284. {
  285. if (s_Styles == null)
  286. {
  287. s_Styles = new Styles();
  288. s_SplitterLeft = new GUIStyle();
  289. }
  290. var panels = DebugManager.instance.panels;
  291. int itemCount = panels.Count(x => !x.isInactiveInEditor && x.children.Count(w => !w.isInactiveInEditor) > 0);
  292. if (itemCount == 0)
  293. {
  294. EditorGUILayout.HelpBox("No debug item found.", MessageType.Info);
  295. return;
  296. }
  297. // Background color
  298. var wrect = position;
  299. wrect.x = 0;
  300. wrect.y = 0;
  301. var oldColor = GUI.color;
  302. GUI.color = s_Styles.skinBackgroundColor;
  303. GUI.DrawTexture(wrect, EditorGUIUtility.whiteTexture);
  304. GUI.color = oldColor;
  305. GUILayout.BeginHorizontal(EditorStyles.toolbar);
  306. //isMultiview = GUILayout.Toggle(isMultiview, "multiview", EditorStyles.toolbarButton);
  307. //if (isMultiview)
  308. // EditorGUILayout.Popup(0, new[] { new GUIContent("SceneView 1"), new GUIContent("SceneView 2") }, EditorStyles.toolbarDropDown, GUILayout.Width(100f));
  309. GUILayout.FlexibleSpace();
  310. //GUILayout.Button(k_LoadButtonContent, EditorStyles.toolbarButton);
  311. //GUILayout.Button(k_SaveButtonContent, EditorStyles.toolbarButton);
  312. if (GUILayout.Button(k_ResetButtonContent, EditorStyles.toolbarButton))
  313. DebugManager.instance.Reset();
  314. GUILayout.EndHorizontal();
  315. // We check if the legacy input manager is not here because we can have both the new and old input system at the same time
  316. // and in this case the debug menu works correctly.
  317. #if !ENABLE_LEGACY_INPUT_MANAGER
  318. EditorGUILayout.HelpBox("The debug menu does not support the new Unity Input package yet. inputs will be disabled in play mode and build.", MessageType.Error);
  319. #endif
  320. using (new EditorGUILayout.HorizontalScope())
  321. {
  322. // Side bar
  323. using (var scrollScope = new EditorGUILayout.ScrollViewScope(m_PanelScroll, s_Styles.sectionScrollView, GUILayout.Width(splitterPos)))
  324. {
  325. GUILayout.Space(40f);
  326. if (m_Settings.selectedPanel >= panels.Count)
  327. m_Settings.selectedPanel = 0;
  328. // Validate container id
  329. while (panels[m_Settings.selectedPanel].isInactiveInEditor || panels[m_Settings.selectedPanel].children.Count(x => !x.isInactiveInEditor) == 0)
  330. {
  331. m_Settings.selectedPanel++;
  332. if (m_Settings.selectedPanel >= panels.Count)
  333. m_Settings.selectedPanel = 0;
  334. }
  335. // Root children are containers
  336. for (int i = 0; i < panels.Count; i++)
  337. {
  338. var panel = panels[i];
  339. if (panel.isInactiveInEditor)
  340. continue;
  341. if (panel.children.Count(x => !x.isInactiveInEditor) == 0)
  342. continue;
  343. var elementRect = GUILayoutUtility.GetRect(EditorGUIUtility.TrTextContent(panel.displayName), s_Styles.sectionElement, GUILayout.ExpandWidth(true));
  344. if (m_Settings.selectedPanel == i && Event.current.type == EventType.Repaint)
  345. s_Styles.selected.Draw(elementRect, false, false, false, false);
  346. EditorGUI.BeginChangeCheck();
  347. GUI.Toggle(elementRect, m_Settings.selectedPanel == i, panel.displayName, s_Styles.sectionElement);
  348. if (EditorGUI.EndChangeCheck())
  349. {
  350. Undo.RegisterCompleteObjectUndo(m_Settings, "Debug Panel Selection");
  351. var previousPanel = m_Settings.selectedPanel >= 0 && m_Settings.selectedPanel < panels.Count
  352. ? panels[m_Settings.selectedPanel]
  353. : null;
  354. if (previousPanel != null && previousPanel.editorForceUpdate && !panel.editorForceUpdate)
  355. EditorApplication.update -= Repaint;
  356. else if ((previousPanel == null || !previousPanel.editorForceUpdate) && panel.editorForceUpdate)
  357. EditorApplication.update += Repaint;
  358. m_Settings.selectedPanel = i;
  359. }
  360. }
  361. m_PanelScroll = scrollScope.scrollPosition;
  362. }
  363. Rect splitterRect = new Rect(splitterPos - 3, 0, 6, Screen.height);
  364. GUI.Box(splitterRect, "", s_SplitterLeft);
  365. GUILayout.Space(10f);
  366. // Main section - traverse current container
  367. using (var changedScope = new EditorGUI.ChangeCheckScope())
  368. {
  369. using (new EditorGUILayout.VerticalScope())
  370. {
  371. var selectedPanel = panels[m_Settings.selectedPanel];
  372. GUILayout.Label(selectedPanel.displayName, s_Styles.sectionHeader);
  373. GUILayout.Space(10f);
  374. using (var scrollScope = new EditorGUILayout.ScrollViewScope(m_ContentScroll))
  375. {
  376. TraverseContainerGUI(selectedPanel);
  377. m_ContentScroll = scrollScope.scrollPosition;
  378. }
  379. }
  380. if (changedScope.changed)
  381. {
  382. m_Settings.currentStateHash = ComputeStateHash();
  383. DebugManager.instance.ReDrawOnScreenDebug();
  384. }
  385. }
  386. // Splitter events
  387. if (Event.current != null)
  388. {
  389. switch (Event.current.rawType)
  390. {
  391. case EventType.MouseDown:
  392. if (splitterRect.Contains(Event.current.mousePosition))
  393. {
  394. dragging = true;
  395. }
  396. break;
  397. case EventType.MouseDrag:
  398. if (dragging)
  399. {
  400. splitterPos += Event.current.delta.x;
  401. splitterPos = Mathf.Clamp(splitterPos, minSideBarWidth, Screen.width - minContentWidth);
  402. Repaint();
  403. }
  404. break;
  405. case EventType.MouseUp:
  406. if (dragging)
  407. {
  408. dragging = false;
  409. }
  410. break;
  411. }
  412. }
  413. EditorGUIUtility.AddCursorRect(splitterRect, MouseCursor.ResizeHorizontal);
  414. }
  415. }
  416. void OnWidgetGUI(DebugUI.Widget widget)
  417. {
  418. if (widget.isInactiveInEditor)
  419. return;
  420. DebugState state; // State will be null for stateless widget
  421. m_WidgetStates.TryGetValue(widget.queryPath, out state);
  422. DebugUIDrawer drawer;
  423. if (!s_WidgetDrawerMap.TryGetValue(widget.GetType(), out drawer))
  424. {
  425. EditorGUILayout.LabelField("Drawer not found (" + widget.GetType() + ").");
  426. }
  427. else
  428. {
  429. drawer.Begin(widget, state);
  430. if (drawer.OnGUI(widget, state))
  431. {
  432. var container = widget as DebugUI.IContainer;
  433. if (container != null)
  434. TraverseContainerGUI(container);
  435. }
  436. drawer.End(widget, state);
  437. }
  438. }
  439. void TraverseContainerGUI(DebugUI.IContainer container)
  440. {
  441. // /!\ SHAAAAAAAME ALERT /!\
  442. // A container can change at runtime because of the way IMGUI works and how we handle
  443. // onValueChanged on widget so we have to take this into account while iterating
  444. try
  445. {
  446. foreach (var widget in container.children)
  447. OnWidgetGUI(widget);
  448. }
  449. catch (InvalidOperationException)
  450. {
  451. Repaint();
  452. }
  453. }
  454. public class Styles
  455. {
  456. public static float s_DefaultLabelWidth = 0.5f;
  457. public readonly GUIStyle sectionScrollView = "PreferencesSectionBox";
  458. public readonly GUIStyle sectionElement = new GUIStyle("PreferencesSection");
  459. public readonly GUIStyle selected = "OL SelectedRow";
  460. public readonly GUIStyle sectionHeader = new GUIStyle(EditorStyles.largeLabel);
  461. public readonly Color skinBackgroundColor;
  462. public Styles()
  463. {
  464. sectionScrollView = new GUIStyle(sectionScrollView);
  465. sectionScrollView.overflow.bottom += 1;
  466. sectionElement.alignment = TextAnchor.MiddleLeft;
  467. sectionHeader.fontStyle = FontStyle.Bold;
  468. sectionHeader.fontSize = 18;
  469. sectionHeader.margin.top = 10;
  470. sectionHeader.margin.left += 1;
  471. sectionHeader.normal.textColor = !EditorGUIUtility.isProSkin
  472. ? new Color(0.4f, 0.4f, 0.4f, 1.0f)
  473. : new Color(0.7f, 0.7f, 0.7f, 1.0f);
  474. if (EditorGUIUtility.isProSkin)
  475. {
  476. sectionHeader.normal.textColor = new Color(0.7f, 0.7f, 0.7f, 1.0f);
  477. skinBackgroundColor = Color.gray * new Color(0.3f, 0.3f, 0.3f, 0.5f);
  478. }
  479. else
  480. {
  481. sectionHeader.normal.textColor = new Color(0.4f, 0.4f, 0.4f, 1.0f);
  482. skinBackgroundColor = Color.gray * new Color(1f, 1f, 1f, 0.32f);
  483. }
  484. }
  485. }
  486. }
  487. #pragma warning restore 414
  488. }