123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493 |
- using System;
- using System.Linq;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.Accessibility;
- using UnityEngine.Rendering.Universal;
- using UnityEngine.SceneManagement;
- using UnityEngine.UIElements;
-
- namespace UnityEditor.Rendering.Universal
- {
- internal class LightBatchingDebugger : EditorWindow
- {
- private const string ResourcePath = "Packages/com.unity.render-pipelines.universal/Editor/2D/LightBatchingDebugger/";
-
- private class LayerBatch
- {
- public List<string> LayerNames = new List<string>();
- public List<UnityEngine.Object> Lights = new List<UnityEngine.Object>();
- public List<UnityEngine.Object> Shadows = new List<UnityEngine.Object>();
- public int batchId;
- }
-
- [MenuItem("Window/2D/Light Batching Debugger")]
- public static void ShowExample()
- {
- // Open Game View
- EditorApplication.ExecuteMenuItem("Window/General/Game");
-
- LightBatchingDebugger wnd = GetWindow<LightBatchingDebugger>();
- wnd.titleContent = new GUIContent("Light Batching Debugger");
- }
-
- VisualElement root => rootVisualElement;
-
- private static Color[] batchColors = new Color[10];
- private List<LayerBatch> batchList = new List<LayerBatch>();
- private List<int> selectedIndices = new List<int>();
- private ListView batchListView;
- private int lightCount = 0;
- private int shadowCount = 0;
-
- // Variables used for refresh view
- private bool doRefresh;
- private int cachedSceneHandle;
- private int totalLightCount;
- private int totalShadowCount;
- private Vector3 cachedCamPos;
-
- ILight2DCullResult lightCullResult
- {
- get
- {
- // Game view main camera
- var renderer = Camera.main?.GetUniversalAdditionalCameraData().scriptableRenderer as Renderer2D;
- var data = renderer?.GetRenderer2DData();
- if (data != null && data.lightCullResult.IsGameView())
- return data?.lightCullResult;
-
- return null;
- }
- }
-
- private bool PopulateData()
- {
- if (lightCullResult == null)
- return false;
-
- batchList.Clear();
-
- var layers = Light2DManager.GetCachedSortingLayer();
- var batches = LayerUtility.CalculateBatches(lightCullResult, out var batchCount);
-
- for (var i = 0; i < batchCount; i++)
- {
- var batchInfo = new LayerBatch
- {
- batchId = i
- };
-
- var batch = batches[i];
-
- // Get the lights
- foreach (var light in lightCullResult.visibleLights)
- {
- // If the lit layers are different, or if they are lit but this is a shadow casting light then don't batch.
- if (light.IsLitLayer(batch.startLayerID))
- {
- batchInfo.Lights.Add(light);
- }
- }
-
- // Get the shadows
- var visibleShadows = lightCullResult.visibleShadows.SelectMany(x => x.GetShadowCasters());
- foreach (var shadowCaster in visibleShadows)
- {
- if (shadowCaster.IsShadowedLayer(batch.startLayerID))
- batchInfo.Shadows.Add(shadowCaster);
- }
-
- for (var batchIndex = batch.startIndex; batchIndex <= batch.endIndex; batchIndex++)
- {
- batchInfo.LayerNames.Add(layers[batchIndex].name);
- }
-
- batchList.Add(batchInfo);
- }
-
- return true;
- }
-
- private VisualElement MakePill(UnityEngine.Object obj)
- {
- var bubble = new Button();
- bubble.AddToClassList("Pill");
- bubble.text = obj.name;
-
- bubble.clicked += () =>
- {
- Selection.activeObject = obj;
- };
-
- return bubble;
- }
-
- private VisualElement GetInfoView()
- {
- // Hide initial prompt
- DisplayInitialPrompt(false);
-
- return root.Query<VisualElement>("InfoView").First();
- }
-
- private void ViewBatch(int index)
- {
- if (index >= batchList.Count())
- return;
-
- var infoView = GetInfoView();
-
- var batch1 = batchList[index];
-
- var title = root.Query<Label>("InfoTitle").First();
- title.text = $"<b>Batch {batch1.batchId}</b>" + " selected. Select any two adjacent batches to compare.";
-
- var title2 = root.Query<Label>("InfoTitle2").First();
- title2.text = "";
-
- // Add Light Pill VisualElements
- var lightLabel1 = infoView.Query<Label>("LightLabel1").First();
- lightLabel1.text = $"Lights in <b>Batch {batch1.batchId}:</b>";
-
- if (batch1.Lights.Count() == 0)
- lightLabel1.text += "\n\nNo lights found.";
-
- var lightBubble1 = infoView.Query<VisualElement>("LightBubble1").First();
- lightBubble1.Clear();
-
- foreach (var obj in batch1.Lights)
- {
- if(obj != null)
- lightBubble1.Add(MakePill(obj));
- }
-
- var lightLabel2 = infoView.Query<Label>("LightLabel2").First();
- lightLabel2.text = "";
-
- var lightBubble2 = infoView.Query<VisualElement>("LightBubble2").First();
- lightBubble2.Clear();
-
- // Add Shadow Caster Pill VisualElements
- var shadowLabel1 = infoView.Query<Label>("ShadowLabel1").First();
- shadowLabel1.text = $"Shadow Casters in <b>Batch {batch1.batchId}:</b>";
-
- if (batch1.Shadows.Count() == 0)
- shadowLabel1.text += "\n\nNo shadow casters found.";
-
- var shadowBubble1 = infoView.Query<VisualElement>("ShadowBubble1").First();
- shadowBubble1.Clear();
-
- foreach (var obj in batch1.Shadows)
- {
- if (obj != null)
- shadowBubble1.Add(MakePill(obj));
- }
-
- var shadowLabel2 = infoView.Query<Label>("ShadowLabel2").First();
- shadowLabel2.text = "";
-
- var shadowBubble2 = infoView.Query<VisualElement>("ShadowBubble2").First();
- shadowBubble2.Clear();
-
- lightCount = batch1.Lights.Count;
- shadowCount = batch1.Shadows.Count;
- }
-
- private void CompareBatch(int index1, int index2)
- {
- // Each editor window contains a root VisualElement object
- var infoView = GetInfoView();
-
- LayerBatch batch1;
- LayerBatch batch2;
-
- if (batchList[index1].batchId < batchList[index2].batchId)
- {
- batch1 = batchList[index1];
- batch2 = batchList[index2];
- }
- else
- {
- batch1 = batchList[index2];
- batch2 = batchList[index1];
- }
-
- // Do batch comparisons
- var lightSet1 = batch1.Lights.Except(batch2.Lights);
- var lightSet2 = batch2.Lights.Except(batch1.Lights);
- var shadowSet1 = batch1.Shadows.Except(batch2.Shadows);
- var shadowSet2 = batch2.Shadows.Except(batch1.Shadows);
-
- // Change InfoTitle description when comparing batches
- var title = root.Query<Label>("InfoTitle").First();
- title.text = $"Comparing <b>Batch {batch1.batchId}</b> and <b>Batch {batch2.batchId}</b>.";
-
- var title2 = root.Query<Label>("InfoTitle2").First();
- 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.";
-
- // Light batch comparison
- var lightLabel1 = infoView.Query<Label>("LightLabel1").First();
- lightLabel1.text = $"Lights only in <b>Batch {batch1.batchId}:</b>";
-
- if (lightSet1.Count() == 0)
- lightLabel1.text += "\n\nNo lights found.";
-
- var lightBubble1 = infoView.Query<VisualElement>("LightBubble1").First();
- lightBubble1.Clear();
- foreach (var obj in lightSet1)
- {
- if(obj != null)
- lightBubble1.Add(MakePill(obj));
- }
-
- var lightLabel2 = infoView.Query<Label>("LightLabel2").First();
- lightLabel2.text = $"Lights only in <b>Batch {batch2.batchId}:</b>";
-
- if (lightSet2.Count() == 0)
- lightLabel2.text += "\n\nNo lights found.";
-
- var lightBubble2 = infoView.Query<VisualElement>("LightBubble2").First();
- lightBubble2.Clear();
- foreach (var obj in lightSet2)
- {
- if(obj != null)
- lightBubble2.Add(MakePill(obj));
- }
-
- // Shadow caster batch comparison
- var shadowLabel1 = infoView.Query<Label>("ShadowLabel1").First();
- shadowLabel1.text = $"Shadow Casters only in <b>Batch {batch1.batchId}:</b>";
-
- if (shadowSet1.Count() == 0)
- shadowLabel1.text += "\n\nNo shadow casters found.";
-
- var shadowBubble1 = infoView.Query<VisualElement>("ShadowBubble1").First();
- shadowBubble1.Clear();
- foreach (var obj in shadowSet1)
- {
- if (obj != null)
- shadowBubble1.Add(MakePill(obj));
- }
-
- var shadowLabel2 = infoView.Query<Label>("ShadowLabel2").First();
- shadowLabel2.text = $"Shadow Casters only in <b>Batch {batch2.batchId}:</b>";
-
- if (shadowSet2.Count() == 0)
- shadowLabel2.text += "\n\nNo shadow casters found.";
-
- var shadowBubble2 = infoView.Query<VisualElement>("ShadowBubble2").First();
- shadowBubble2.Clear();
- foreach (var obj in shadowSet2)
- {
- if (obj != null)
- shadowBubble2.Add(MakePill(obj));
- }
-
- lightCount = lightSet1.Count() + lightSet2.Count();
- shadowCount = shadowSet1.Count() + shadowSet2.Count();
- }
-
- // Create once, initialize
- private void CreateGUI()
- {
- // Generate color-blind friendly colors
- VisionUtility.GetColorBlindSafePalette(batchColors, 0.51f, 1.0f);
-
- var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(ResourcePath + "LightBatchingDebugger.uxml");
- var templateRoot = visualTree.Instantiate();
- templateRoot.style.flexGrow = 1;
- templateRoot.Q("ParentElement").StretchToParentSize();
- root.Add(templateRoot);
-
- var batchElement = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(ResourcePath + "LayerBatch.uxml");
- Func<VisualElement> makeItem = () => batchElement.Instantiate();
-
- Action<VisualElement, int> bindItem = (e, i) =>
- {
- if (i >= batchList.Count())
- return;
-
- // This is required to make the child of the ListView vary in heights
- e.style.height = StyleKeyword.Auto;
-
- var batch = batchList[i];
- var batchIndex = e.Query<Label>("BatchIndex").First();
- batchIndex.text = batch.batchId.ToString();
-
- var layers = e.Query<VisualElement>("LayerNames").First();
- layers.Clear();
- foreach (var layerName in batchList[i].LayerNames)
- {
- var label = new Label { text = layerName };
- label.AddToClassList("LayerNameLabel");
- layers.Add(label);
- }
-
- var color = e.Query<VisualElement>("BatchColor").First();
- color.style.backgroundColor = new StyleColor(batchColors[i % batchColors.Length]);
- };
-
- DisplayInitialPrompt(true);
-
- batchListView = root.Query<ListView>("BatchList").First();
- batchListView.itemsSource = batchList;
- batchListView.makeItem = makeItem;
- batchListView.bindItem = bindItem;
- batchListView.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight;
- batchListView.showAlternatingRowBackgrounds = AlternatingRowBackground.ContentOnly;
- batchListView.selectionType = SelectionType.Multiple;
-
- batchListView.selectionChanged += objects =>
- {
- OnSelectionChanged();
- };
- }
-
- private void OnEnable()
- {
- EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
- }
-
- private void OnDisable()
- {
- EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
- }
-
- void OnPlayModeStateChanged(PlayModeStateChange playModeState)
- {
- if (PlayModeStateChange.EnteredEditMode == playModeState)
- QueueRefresh();
- }
-
- void DisplayInitialPrompt(bool display)
- {
- var initialPrompt = root.Query<Label>("InitialPrompt").First();
- initialPrompt.style.display = display ? DisplayStyle.Flex : DisplayStyle.None;
-
- var infoView = root.Query<VisualElement>("InfoView").First();
- infoView.style.display = display ? DisplayStyle.None : DisplayStyle.Flex;
- }
-
- private void OnSelectionChanged()
- {
- if (batchListView == null)
- return;
-
- switch (batchListView.selectedIndices.Count())
- {
- case 1:
- selectedIndices.Clear();
- selectedIndices.Add(batchListView.selectedIndex);
- ViewBatch(batchListView.selectedIndex);
- break;
-
- case 2:
- selectedIndices.Clear();
- var firstIndex = batchListView.selectedIndices.First();
- var secondIndex = batchListView.selectedIndices.Last();
-
- if(secondIndex > firstIndex + 1 || secondIndex < firstIndex - 1)
- {
- // Clamp since we do adjacent batch comparisons
- secondIndex = Mathf.Clamp(secondIndex, firstIndex - 1, firstIndex + 1);
- selectedIndices.Add(firstIndex);
- selectedIndices.Add(secondIndex);
- batchListView.SetSelection(selectedIndices);
- }
- else
- {
- CompareBatch(firstIndex, secondIndex);
- selectedIndices.AddRange(batchListView.selectedIndices);
- }
- break;
-
- default:
- // Account for multiple select either with shift or ctrl keys
- if(batchListView.selectedIndices.Count() > 2)
- {
- if (selectedIndices.Count == 1)
- {
- firstIndex = secondIndex = selectedIndices.First();
-
- if (batchListView.selectedIndices.First() > firstIndex)
- secondIndex = firstIndex + 1;
- else if (batchListView.selectedIndices.First() < firstIndex)
- secondIndex = firstIndex - 1;
-
- selectedIndices.Add(secondIndex);
- batchListView.SetSelection(selectedIndices);
- }
- else if (selectedIndices.Count == 2)
- {
- batchListView.SetSelection(selectedIndices);
- }
- }
- break;
- }
-
- // Update counts
- Label lightHeader = root.Query<Label>("LightHeader");
- lightHeader.text = $"Lights ({lightCount})";
- Label shadowHeader = root.Query<Label>("ShadowHeader");
- shadowHeader.text = $"Shadow Casters ({shadowCount})";
- }
-
- private void RefreshView()
- {
- PopulateData();
- batchListView.RefreshItems();
- OnSelectionChanged();
-
- ResetDirty();
- }
-
- private void Update()
- {
- if (IsDirty())
- QueueRefresh();
-
- if (doRefresh)
- RefreshView();
- }
-
- private bool IsDirty()
- {
- bool isDirty = false;
-
- // Refresh if layers are added or removed
- isDirty |= Light2DManager.GetCachedSortingLayer().Count() != batchList.Sum(x => x.LayerNames.Count());
- isDirty |= cachedSceneHandle != SceneManager.GetActiveScene().handle;
- isDirty |= cachedCamPos != Camera.main?.transform.position;
-
- if (lightCullResult != null)
- {
- isDirty |= totalLightCount != lightCullResult.visibleLights.Count();
- isDirty |= totalShadowCount != lightCullResult.visibleShadows.Count();
- }
-
- return isDirty;
- }
-
- private void ResetDirty()
- {
- cachedSceneHandle = SceneManager.GetActiveScene().handle;
-
- if (Camera.main != null)
- cachedCamPos = Camera.main.transform.position;
-
- if (lightCullResult != null)
- {
- totalLightCount = lightCullResult.visibleLights.Count();
- totalShadowCount = lightCullResult.visibleShadows.Count();
- }
-
- doRefresh = false;
- }
-
- public void QueueRefresh()
- {
- doRefresh = true;
- }
- }
- }
|