暂无描述
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

LightBatchingDebugger.cs 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. using System;
  2. using System.Linq;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using UnityEngine.Accessibility;
  6. using UnityEngine.Rendering.Universal;
  7. using UnityEngine.SceneManagement;
  8. using UnityEngine.UIElements;
  9. namespace UnityEditor.Rendering.Universal
  10. {
  11. internal class LightBatchingDebugger : EditorWindow
  12. {
  13. private const string ResourcePath = "Packages/com.unity.render-pipelines.universal/Editor/2D/LightBatchingDebugger/";
  14. private class LayerBatch
  15. {
  16. public List<string> LayerNames = new List<string>();
  17. public List<UnityEngine.Object> Lights = new List<UnityEngine.Object>();
  18. public List<UnityEngine.Object> Shadows = new List<UnityEngine.Object>();
  19. public int batchId;
  20. }
  21. [MenuItem("Window/2D/Light Batching Debugger")]
  22. public static void ShowExample()
  23. {
  24. // Open Game View
  25. EditorApplication.ExecuteMenuItem("Window/General/Game");
  26. LightBatchingDebugger wnd = GetWindow<LightBatchingDebugger>();
  27. wnd.titleContent = new GUIContent("Light Batching Debugger");
  28. }
  29. VisualElement root => rootVisualElement;
  30. private static Color[] batchColors = new Color[10];
  31. private List<LayerBatch> batchList = new List<LayerBatch>();
  32. private List<int> selectedIndices = new List<int>();
  33. private ListView batchListView;
  34. private int lightCount = 0;
  35. private int shadowCount = 0;
  36. // Variables used for refresh view
  37. private bool doRefresh;
  38. private int cachedSceneHandle;
  39. private int totalLightCount;
  40. private int totalShadowCount;
  41. private Vector3 cachedCamPos;
  42. ILight2DCullResult lightCullResult
  43. {
  44. get
  45. {
  46. // Game view main camera
  47. var renderer = Camera.main?.GetUniversalAdditionalCameraData().scriptableRenderer as Renderer2D;
  48. var data = renderer?.GetRenderer2DData();
  49. if (data != null && data.lightCullResult.IsGameView())
  50. return data?.lightCullResult;
  51. return null;
  52. }
  53. }
  54. private bool PopulateData()
  55. {
  56. if (lightCullResult == null)
  57. return false;
  58. batchList.Clear();
  59. var layers = Light2DManager.GetCachedSortingLayer();
  60. var batches = LayerUtility.CalculateBatches(lightCullResult, out var batchCount);
  61. for (var i = 0; i < batchCount; i++)
  62. {
  63. var batchInfo = new LayerBatch
  64. {
  65. batchId = i
  66. };
  67. var batch = batches[i];
  68. // Get the lights
  69. foreach (var light in lightCullResult.visibleLights)
  70. {
  71. // If the lit layers are different, or if they are lit but this is a shadow casting light then don't batch.
  72. if (light.IsLitLayer(batch.startLayerID))
  73. {
  74. batchInfo.Lights.Add(light);
  75. }
  76. }
  77. // Get the shadows
  78. var visibleShadows = lightCullResult.visibleShadows.SelectMany(x => x.GetShadowCasters());
  79. foreach (var shadowCaster in visibleShadows)
  80. {
  81. if (shadowCaster.IsShadowedLayer(batch.startLayerID))
  82. batchInfo.Shadows.Add(shadowCaster);
  83. }
  84. for (var batchIndex = batch.startIndex; batchIndex <= batch.endIndex; batchIndex++)
  85. {
  86. batchInfo.LayerNames.Add(layers[batchIndex].name);
  87. }
  88. batchList.Add(batchInfo);
  89. }
  90. return true;
  91. }
  92. private VisualElement MakePill(UnityEngine.Object obj)
  93. {
  94. var bubble = new Button();
  95. bubble.AddToClassList("Pill");
  96. bubble.text = obj.name;
  97. bubble.clicked += () =>
  98. {
  99. Selection.activeObject = obj;
  100. };
  101. return bubble;
  102. }
  103. private VisualElement GetInfoView()
  104. {
  105. // Hide initial prompt
  106. DisplayInitialPrompt(false);
  107. return root.Query<VisualElement>("InfoView").First();
  108. }
  109. private void ViewBatch(int index)
  110. {
  111. if (index >= batchList.Count())
  112. return;
  113. var infoView = GetInfoView();
  114. var batch1 = batchList[index];
  115. var title = root.Query<Label>("InfoTitle").First();
  116. title.text = $"<b>Batch {batch1.batchId}</b>" + " selected. Select any two adjacent batches to compare.";
  117. var title2 = root.Query<Label>("InfoTitle2").First();
  118. title2.text = "";
  119. // Add Light Pill VisualElements
  120. var lightLabel1 = infoView.Query<Label>("LightLabel1").First();
  121. lightLabel1.text = $"Lights in <b>Batch {batch1.batchId}:</b>";
  122. if (batch1.Lights.Count() == 0)
  123. lightLabel1.text += "\n\nNo lights found.";
  124. var lightBubble1 = infoView.Query<VisualElement>("LightBubble1").First();
  125. lightBubble1.Clear();
  126. foreach (var obj in batch1.Lights)
  127. {
  128. if(obj != null)
  129. lightBubble1.Add(MakePill(obj));
  130. }
  131. var lightLabel2 = infoView.Query<Label>("LightLabel2").First();
  132. lightLabel2.text = "";
  133. var lightBubble2 = infoView.Query<VisualElement>("LightBubble2").First();
  134. lightBubble2.Clear();
  135. // Add Shadow Caster Pill VisualElements
  136. var shadowLabel1 = infoView.Query<Label>("ShadowLabel1").First();
  137. shadowLabel1.text = $"Shadow Casters in <b>Batch {batch1.batchId}:</b>";
  138. if (batch1.Shadows.Count() == 0)
  139. shadowLabel1.text += "\n\nNo shadow casters found.";
  140. var shadowBubble1 = infoView.Query<VisualElement>("ShadowBubble1").First();
  141. shadowBubble1.Clear();
  142. foreach (var obj in batch1.Shadows)
  143. {
  144. if (obj != null)
  145. shadowBubble1.Add(MakePill(obj));
  146. }
  147. var shadowLabel2 = infoView.Query<Label>("ShadowLabel2").First();
  148. shadowLabel2.text = "";
  149. var shadowBubble2 = infoView.Query<VisualElement>("ShadowBubble2").First();
  150. shadowBubble2.Clear();
  151. lightCount = batch1.Lights.Count;
  152. shadowCount = batch1.Shadows.Count;
  153. }
  154. private void CompareBatch(int index1, int index2)
  155. {
  156. // Each editor window contains a root VisualElement object
  157. var infoView = GetInfoView();
  158. LayerBatch batch1;
  159. LayerBatch batch2;
  160. if (batchList[index1].batchId < batchList[index2].batchId)
  161. {
  162. batch1 = batchList[index1];
  163. batch2 = batchList[index2];
  164. }
  165. else
  166. {
  167. batch1 = batchList[index2];
  168. batch2 = batchList[index1];
  169. }
  170. // Do batch comparisons
  171. var lightSet1 = batch1.Lights.Except(batch2.Lights);
  172. var lightSet2 = batch2.Lights.Except(batch1.Lights);
  173. var shadowSet1 = batch1.Shadows.Except(batch2.Shadows);
  174. var shadowSet2 = batch2.Shadows.Except(batch1.Shadows);
  175. // Change InfoTitle description when comparing batches
  176. var title = root.Query<Label>("InfoTitle").First();
  177. title.text = $"Comparing <b>Batch {batch1.batchId}</b> and <b>Batch {batch2.batchId}</b>.";
  178. var title2 = root.Query<Label>("InfoTitle2").First();
  179. title2.text = $"To batch <b>Batch {batch1.batchId}</b> and <b>Batch {batch2.batchId}</b>, ensure that the Sorting Layers in both batches share the same set of Lights and Shadow Casters.";
  180. // Light batch comparison
  181. var lightLabel1 = infoView.Query<Label>("LightLabel1").First();
  182. lightLabel1.text = $"Lights only in <b>Batch {batch1.batchId}:</b>";
  183. if (lightSet1.Count() == 0)
  184. lightLabel1.text += "\n\nNo lights found.";
  185. var lightBubble1 = infoView.Query<VisualElement>("LightBubble1").First();
  186. lightBubble1.Clear();
  187. foreach (var obj in lightSet1)
  188. {
  189. if(obj != null)
  190. lightBubble1.Add(MakePill(obj));
  191. }
  192. var lightLabel2 = infoView.Query<Label>("LightLabel2").First();
  193. lightLabel2.text = $"Lights only in <b>Batch {batch2.batchId}:</b>";
  194. if (lightSet2.Count() == 0)
  195. lightLabel2.text += "\n\nNo lights found.";
  196. var lightBubble2 = infoView.Query<VisualElement>("LightBubble2").First();
  197. lightBubble2.Clear();
  198. foreach (var obj in lightSet2)
  199. {
  200. if(obj != null)
  201. lightBubble2.Add(MakePill(obj));
  202. }
  203. // Shadow caster batch comparison
  204. var shadowLabel1 = infoView.Query<Label>("ShadowLabel1").First();
  205. shadowLabel1.text = $"Shadow Casters only in <b>Batch {batch1.batchId}:</b>";
  206. if (shadowSet1.Count() == 0)
  207. shadowLabel1.text += "\n\nNo shadow casters found.";
  208. var shadowBubble1 = infoView.Query<VisualElement>("ShadowBubble1").First();
  209. shadowBubble1.Clear();
  210. foreach (var obj in shadowSet1)
  211. {
  212. if (obj != null)
  213. shadowBubble1.Add(MakePill(obj));
  214. }
  215. var shadowLabel2 = infoView.Query<Label>("ShadowLabel2").First();
  216. shadowLabel2.text = $"Shadow Casters only in <b>Batch {batch2.batchId}:</b>";
  217. if (shadowSet2.Count() == 0)
  218. shadowLabel2.text += "\n\nNo shadow casters found.";
  219. var shadowBubble2 = infoView.Query<VisualElement>("ShadowBubble2").First();
  220. shadowBubble2.Clear();
  221. foreach (var obj in shadowSet2)
  222. {
  223. if (obj != null)
  224. shadowBubble2.Add(MakePill(obj));
  225. }
  226. lightCount = lightSet1.Count() + lightSet2.Count();
  227. shadowCount = shadowSet1.Count() + shadowSet2.Count();
  228. }
  229. // Create once, initialize
  230. private void CreateGUI()
  231. {
  232. // Generate color-blind friendly colors
  233. VisionUtility.GetColorBlindSafePalette(batchColors, 0.51f, 1.0f);
  234. var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(ResourcePath + "LightBatchingDebugger.uxml");
  235. var templateRoot = visualTree.Instantiate();
  236. templateRoot.style.flexGrow = 1;
  237. templateRoot.Q("ParentElement").StretchToParentSize();
  238. root.Add(templateRoot);
  239. var batchElement = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(ResourcePath + "LayerBatch.uxml");
  240. Func<VisualElement> makeItem = () => batchElement.Instantiate();
  241. Action<VisualElement, int> bindItem = (e, i) =>
  242. {
  243. if (i >= batchList.Count())
  244. return;
  245. // This is required to make the child of the ListView vary in heights
  246. e.style.height = StyleKeyword.Auto;
  247. var batch = batchList[i];
  248. var batchIndex = e.Query<Label>("BatchIndex").First();
  249. batchIndex.text = batch.batchId.ToString();
  250. var layers = e.Query<VisualElement>("LayerNames").First();
  251. layers.Clear();
  252. foreach (var layerName in batchList[i].LayerNames)
  253. {
  254. var label = new Label { text = layerName };
  255. label.AddToClassList("LayerNameLabel");
  256. layers.Add(label);
  257. }
  258. var color = e.Query<VisualElement>("BatchColor").First();
  259. color.style.backgroundColor = new StyleColor(batchColors[i % batchColors.Length]);
  260. };
  261. DisplayInitialPrompt(true);
  262. batchListView = root.Query<ListView>("BatchList").First();
  263. batchListView.itemsSource = batchList;
  264. batchListView.makeItem = makeItem;
  265. batchListView.bindItem = bindItem;
  266. batchListView.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight;
  267. batchListView.showAlternatingRowBackgrounds = AlternatingRowBackground.ContentOnly;
  268. batchListView.selectionType = SelectionType.Multiple;
  269. batchListView.selectionChanged += objects =>
  270. {
  271. OnSelectionChanged();
  272. };
  273. }
  274. private void OnEnable()
  275. {
  276. EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
  277. }
  278. private void OnDisable()
  279. {
  280. EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
  281. }
  282. void OnPlayModeStateChanged(PlayModeStateChange playModeState)
  283. {
  284. if (PlayModeStateChange.EnteredEditMode == playModeState)
  285. QueueRefresh();
  286. }
  287. void DisplayInitialPrompt(bool display)
  288. {
  289. var initialPrompt = root.Query<Label>("InitialPrompt").First();
  290. initialPrompt.style.display = display ? DisplayStyle.Flex : DisplayStyle.None;
  291. var infoView = root.Query<VisualElement>("InfoView").First();
  292. infoView.style.display = display ? DisplayStyle.None : DisplayStyle.Flex;
  293. }
  294. private void OnSelectionChanged()
  295. {
  296. if (batchListView == null)
  297. return;
  298. switch (batchListView.selectedIndices.Count())
  299. {
  300. case 1:
  301. selectedIndices.Clear();
  302. selectedIndices.Add(batchListView.selectedIndex);
  303. ViewBatch(batchListView.selectedIndex);
  304. break;
  305. case 2:
  306. selectedIndices.Clear();
  307. var firstIndex = batchListView.selectedIndices.First();
  308. var secondIndex = batchListView.selectedIndices.Last();
  309. if(secondIndex > firstIndex + 1 || secondIndex < firstIndex - 1)
  310. {
  311. // Clamp since we do adjacent batch comparisons
  312. secondIndex = Mathf.Clamp(secondIndex, firstIndex - 1, firstIndex + 1);
  313. selectedIndices.Add(firstIndex);
  314. selectedIndices.Add(secondIndex);
  315. batchListView.SetSelection(selectedIndices);
  316. }
  317. else
  318. {
  319. CompareBatch(firstIndex, secondIndex);
  320. selectedIndices.AddRange(batchListView.selectedIndices);
  321. }
  322. break;
  323. default:
  324. // Account for multiple select either with shift or ctrl keys
  325. if(batchListView.selectedIndices.Count() > 2)
  326. {
  327. if (selectedIndices.Count == 1)
  328. {
  329. firstIndex = secondIndex = selectedIndices.First();
  330. if (batchListView.selectedIndices.First() > firstIndex)
  331. secondIndex = firstIndex + 1;
  332. else if (batchListView.selectedIndices.First() < firstIndex)
  333. secondIndex = firstIndex - 1;
  334. selectedIndices.Add(secondIndex);
  335. batchListView.SetSelection(selectedIndices);
  336. }
  337. else if (selectedIndices.Count == 2)
  338. {
  339. batchListView.SetSelection(selectedIndices);
  340. }
  341. }
  342. break;
  343. }
  344. // Update counts
  345. Label lightHeader = root.Query<Label>("LightHeader");
  346. lightHeader.text = $"Lights ({lightCount})";
  347. Label shadowHeader = root.Query<Label>("ShadowHeader");
  348. shadowHeader.text = $"Shadow Casters ({shadowCount})";
  349. }
  350. private void RefreshView()
  351. {
  352. PopulateData();
  353. batchListView.RefreshItems();
  354. OnSelectionChanged();
  355. ResetDirty();
  356. }
  357. private void Update()
  358. {
  359. if (IsDirty())
  360. QueueRefresh();
  361. if (doRefresh)
  362. RefreshView();
  363. }
  364. private bool IsDirty()
  365. {
  366. bool isDirty = false;
  367. // Refresh if layers are added or removed
  368. isDirty |= Light2DManager.GetCachedSortingLayer().Count() != batchList.Sum(x => x.LayerNames.Count());
  369. isDirty |= cachedSceneHandle != SceneManager.GetActiveScene().handle;
  370. isDirty |= cachedCamPos != Camera.main?.transform.position;
  371. if (lightCullResult != null)
  372. {
  373. isDirty |= totalLightCount != lightCullResult.visibleLights.Count();
  374. isDirty |= totalShadowCount != lightCullResult.visibleShadows.Count();
  375. }
  376. return isDirty;
  377. }
  378. private void ResetDirty()
  379. {
  380. cachedSceneHandle = SceneManager.GetActiveScene().handle;
  381. if (Camera.main != null)
  382. cachedCamPos = Camera.main.transform.position;
  383. if (lightCullResult != null)
  384. {
  385. totalLightCount = lightCullResult.visibleLights.Count();
  386. totalShadowCount = lightCullResult.visibleShadows.Count();
  387. }
  388. doRefresh = false;
  389. }
  390. public void QueueRefresh()
  391. {
  392. doRefresh = true;
  393. }
  394. }
  395. }