UnityGame/Library/PackageCache/com.unity.render-pipelines.universal/Editor/2D/LightBatchingDebugger/LightBatchingDebugger.cs
2024-10-27 10:53:47 +03:00

494 lines
18 KiB
C#

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;
}
}
}