2025-05-08 10:30:32 +02:00

475 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using SceneProfiler.Editor.GUI;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using Application = UnityEngine.Application;
using Object = UnityEngine.Object;
namespace SceneProfiler.Editor
{
public class SceneProfiler : EditorWindow
{
public enum InspectType
{
Textures, Materials, Meshes, AudioClips, Missing, Particles, Lights, Physics, Expensive
};
public bool includeDisabledObjects = true;
public bool IncludeSpriteAnimations = true;
public bool IncludeScriptReferences = true;
public bool IncludeGuiElements = true;
public bool IncludeLightmapTextures = true;
public bool IncludeSelectedFolder = false;
public bool thingsMissing = false;
public InspectType ActiveInspectType = InspectType.Textures;
public List<TextureDetails> ActiveTextures = new List<TextureDetails>();
public List<MaterialDetails> ActiveMaterials = new List<MaterialDetails>();
public List<MeshDetails> ActiveMeshDetails = new List<MeshDetails>();
public List<AudioClipDetails> ActiveClipDetails = new List<AudioClipDetails>();
public List<ParticleSystemDetails> ActiveParticleSystems = new List<ParticleSystemDetails>();
public List<Missing> MissingObjects = new List<Missing>();
public List<LightDetails> ActiveLights = new List<LightDetails>();
public List<PhysicsObjectDetails> ActivePhysicsObjects = new List<PhysicsObjectDetails>();
public List<SceneWarningDetails> Warnings = new List<SceneWarningDetails>();
public List<ExpensiveObjectDetails> ActiveExpensiveObjects = new List<ExpensiveObjectDetails>();
public float TotalTextureMemory = 0;
public int TotalMeshVertices = 0;
public bool ctrlPressed = false;
private static int _minWidth = 800;
public int currentObjectsInColumnCount = 100;
private SceneProfilerGUI _sceneProfilerGUI;
private CollectTextureData _collectTextureData;
private CollectMaterialsData _collectMaterialsData;
private CollectAudioClipData _collectAudioClipData;
private CollectMeshData _collectMeshData;
private CollectParticleSystemData _сollectParticleSystemData;
private CollectLightData _collectLightData;
private CollectPhysicsData _collectPhysicsData;
private CollectWarningsData _collectWarningsData;
private CollectExpensiveObject _collectExpensiveObject;
[MenuItem("Window/Analysis/Scene Profiler")]
public static void Init()
{
SceneProfiler window = (SceneProfiler)EditorWindow.GetWindow(typeof(SceneProfiler));
GUIContent titleContent = EditorGUIUtility.IconContent("UnityEditor.SceneView");
titleContent.text = "Scene Profiler";
window.titleContent = titleContent;
window.minSize = new Vector2(_minWidth, 400);
}
private void OnEnable()
{
_sceneProfilerGUI = new SceneProfilerGUI(this);
_collectTextureData = new CollectTextureData(this);
_collectMaterialsData = new CollectMaterialsData(this);
_collectAudioClipData = new CollectAudioClipData(this);
_collectMeshData = new CollectMeshData(this);
_сollectParticleSystemData = new CollectParticleSystemData(this);
_collectLightData = new CollectLightData(this);
_collectPhysicsData = new CollectPhysicsData(this);
_collectWarningsData = new CollectWarningsData(this);
_collectExpensiveObject = new CollectExpensiveObject(this);
EditorSceneManager.sceneOpened += OnSceneOpened;
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
SceneManager.sceneUnloaded += OnSceneUnloaded;
SceneManager.sceneLoaded += OnSceneLoaded;
ClearAndRepaint();
}
private void OnDisable()
{
EditorSceneManager.sceneOpened -= OnSceneOpened;
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
}
private void OnSceneOpened(Scene scene, OpenSceneMode mode)
{
ClearAndRepaint();
}
private void OnSceneUnloaded(Scene scene)
{
ClearAndRepaint();
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
ClearAndRepaint();
}
private void OnPlayModeStateChanged(PlayModeStateChange state)
{
if (state == PlayModeStateChange.EnteredPlayMode || state == PlayModeStateChange.ExitingPlayMode)
{
ClearAndRepaint();
}
}
public void ClearAndRepaint()
{
ActiveTextures.Clear();
ActiveMaterials.Clear();
ActiveMeshDetails.Clear();
ActiveClipDetails.Clear();
MissingObjects.Clear();
ActiveParticleSystems.Clear();
ActiveLights.Clear();
ActivePhysicsObjects.Clear();
ActiveExpensiveObjects.Clear();
Warnings.Clear();
TotalTextureMemory = 0;
TotalMeshVertices = 0;
thingsMissing = false;
Repaint();
}
void OnGUI()
{
_sceneProfilerGUI.DrawGUI();
}
public int UniqueShadersCount
{
get
{
return ActiveMaterials
.Where(mat => mat.material != null && mat.material.shader != null)
.Select(mat => mat.material.shader)
.Distinct()
.Count();
}
}
public void SelectObject(Object selectedObject, bool append)
{
if (append)
{
List<Object> currentSelection = new List<Object>(Selection.objects);
if (currentSelection.Contains(selectedObject)) currentSelection.Remove(selectedObject);
else currentSelection.Add(selectedObject);
Selection.objects = currentSelection.ToArray();
}
else Selection.activeObject = selectedObject;
}
public void SelectObjects(List<Object> selectedObjects, bool append)
{
if (append)
{
List<Object> currentSelection = new List<Object>(Selection.objects);
currentSelection.AddRange(selectedObjects);
Selection.objects = currentSelection.ToArray();
}
else Selection.objects = selectedObjects.ToArray();
}
private static int MaterialSorter(MaterialDetails first, MaterialDetails second)
{
var firstIsNull = first.material == null;
var secondIsNull = second.material == null;
if (firstIsNull && secondIsNull) return 0;
if (firstIsNull) return int.MaxValue;
if (secondIsNull) return int.MinValue;
return first.material.renderQueue - second.material.renderQueue;
}
public string FormatSizeString(float memSizeKB)
{
if (memSizeKB < 1024) return "" + memSizeKB + "k";
else
{
float memSizeMB = ((float)memSizeKB) / 1024.0f;
return memSizeMB.ToString("0.00") + "Mb";
}
}
// Main method
public void CollectData()
{
ActiveTextures.Clear();
ActiveMaterials.Clear();
ActiveMeshDetails.Clear();
MissingObjects.Clear();
ActiveClipDetails.Clear();
ActiveParticleSystems.Clear();
ActiveLights.Clear();
ActivePhysicsObjects.Clear();
ActiveExpensiveObjects.Clear();
thingsMissing = false;
_collectExpensiveObject.CollectData();
_сollectParticleSystemData.CheckParticleSystems();
_collectLightData.CheckLights();
_collectPhysicsData.CheckPhysicsObjects();
_collectMaterialsData.CheckRenderers();
_collectTextureData.CheckRenderers();
_collectTextureData.CheckLightmaps();
_collectMaterialsData.CheckGUIElements();
_collectTextureData.CheckGUIElements();
_collectMeshData.CheckMeshFilters();
_collectMeshData.CheckSkinnedMeshRenderers();
_collectMeshData.CheckLODGroups();
_collectTextureData.CheckSelectedFolder();
_collectAudioClipData.CheckSelectedFolder();
_collectTextureData.CheckSpriteAnimations();
CheckScriptReferences();
_collectTextureData.CheckMaterials(); //Collect all materials before
_collectAudioClipData.CheckAudioSources();
CheckForMissingScriptsInAllGameObjects();
CalculateTotals();
ActiveMaterials.Sort(MaterialSorter);
CheckWarnings();
}
public void AddMissingSprite(SpriteRenderer tSpriteRenderer)
{
Missing tMissing = new Missing
{
Object = tSpriteRenderer.transform,
type = "sprite",
name = tSpriteRenderer.transform.name
};
MissingObjects.Add(tMissing);
thingsMissing = true;
}
private void CheckScriptReferences()
{
if (!IncludeScriptReferences) return;
MonoBehaviour[] scripts = FindObjects<MonoBehaviour>();
foreach (MonoBehaviour script in scripts)
{
BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
FieldInfo[] fields = script.GetType().GetFields(flags);
foreach (FieldInfo field in fields)
{
_collectTextureData.CheckSpriteReferences(field, script);
_collectMeshData.CheckMeshReferences(field, script);
CheckMaterialReferences(field, script);
_collectAudioClipData.CheckAudioClipReferences(field, script);
_collectLightData.CheckLightReferences(field, script);
_сollectParticleSystemData.CheckParticleSystemReferences(field, script);
_collectPhysicsData.CheckRigidbodyReferences(field, script);
}
}
}
private void CheckMaterialReferences(FieldInfo field, MonoBehaviour script)
{
if (field.FieldType == typeof(Material))
{
Material tMaterial = field.GetValue(script) as Material;
if (tMaterial != null)
{
_collectMaterialsData.AddMaterialDetails(tMaterial);
_collectTextureData.CheckMaterialTextures(tMaterial);
_collectTextureData.CheckMaterialDependencies(tMaterial);
}
}
}
private void CheckForMissingScriptsInAllGameObjects()
{
GameObject[] allGameObjects = GetAllRootGameObjects();
foreach (GameObject go in allGameObjects)
{
CheckForMissingScripts(go);
}
}
void CheckForMissingScripts(GameObject go)
{
Component[] components = go.GetComponents<Component>();
foreach (Component component in components)
{
if (component == null)
{
Missing tMissing = new Missing();
tMissing.Object = go.transform;
tMissing.type = "missing script";
tMissing.name = go.name;
MissingObjects.Add(tMissing);
thingsMissing = true;
}
}
foreach (Transform child in go.transform)
{
CheckForMissingScripts(child.gameObject);
}
}
private void CalculateTotals()
{
TotalMeshVertices = 0;
foreach (MeshDetails tMeshDetails in ActiveMeshDetails) TotalMeshVertices += tMeshDetails.mesh.vertexCount;
ActiveTextures.Sort(delegate (TextureDetails details1, TextureDetails details2) { return (int)(details2.memSizeKB - details1.memSizeKB); });
ActiveTextures = ActiveTextures.Distinct().ToList();
TotalTextureMemory = 0;
foreach (TextureDetails tTextureDetails in ActiveTextures) TotalTextureMemory += tTextureDetails.memSizeKB;
ActiveMeshDetails.Sort(delegate (MeshDetails details1, MeshDetails details2) { return details2.mesh.vertexCount - details1.mesh.vertexCount; });
}
private void CheckWarnings()
{
Warnings.Clear();
if (thingsMissing)
{
Warnings.Add(new SceneWarningDetails("Some GameObjects are missing elements.", MessageType.Error));
}
float sceneFileSizeMB = _collectWarningsData.GetSceneFileSize();
Warnings.Add(new SceneWarningDetails($"Scene file size: {sceneFileSizeMB:F2} MB.", MessageType.Info));
int objectCount = _collectWarningsData.CountObjectsInScene();
Warnings.Add(new SceneWarningDetails($"There are {objectCount} objects in the scene.", MessageType.Info));
int canvasCount = _collectWarningsData.CountCanvasComponentsInScene();
if (canvasCount > 0)
{
Warnings.Add(new SceneWarningDetails($"There are {canvasCount} Canvas components in the scene.", MessageType.Info));
}
var platformsWithoutStaticBatching = _collectWarningsData.GetPlatformsWithoutStaticBatching();
if (platformsWithoutStaticBatching.Count > 0)
{
string message = "Static batching is not enabled: " + string.Join(", ", platformsWithoutStaticBatching);
Warnings.Add(new SceneWarningDetails(message, MessageType.Warning));
}
}
public GameObject[] GetAllRootGameObjects()
{
List<GameObject> allGo = new List<GameObject>();
for (int sceneIdx = 0; sceneIdx < UnityEngine.SceneManagement.SceneManager.sceneCount; ++sceneIdx)
{
Scene scene = UnityEngine.SceneManagement.SceneManager.GetSceneAt(sceneIdx);
if (scene.isLoaded)
{
allGo.AddRange(scene.GetRootGameObjects());
}
}
allGo.AddRange(GetDontDestroyOnLoadRoots());
return allGo.ToArray();
}
private static List<GameObject> GetDontDestroyOnLoadRoots()
{
List<GameObject> objs = new List<GameObject>();
if (Application.isPlaying)
{
GameObject temp = null;
try
{
temp = new GameObject();
DontDestroyOnLoad(temp);
UnityEngine.SceneManagement.Scene dontDestryScene = temp.scene;
DestroyImmediate(temp);
temp = null;
if(dontDestryScene.IsValid())
{
objs = dontDestryScene.GetRootGameObjects().ToList();
}
}
catch (System.Exception e)
{
Debug.LogException(e);
return null;
}
finally
{
if(temp != null)
DestroyImmediate(temp);
}
}
return objs;
}
public T[] FindObjects<T>() where T : Object
{
if (includeDisabledObjects) {
List<T> meshfilters = new List<T> ();
GameObject[] allGo = GetAllRootGameObjects();
foreach (GameObject go in allGo) {
Transform[] tgo = go.GetComponentsInChildren<Transform> (true).ToArray ();
foreach (Transform tr in tgo) {
if (tr.GetComponent<T> ())
meshfilters.Add (tr.GetComponent<T> ());
}
}
return (T[])meshfilters.ToArray ();
}
else
return (T[])FindObjectsOfType(typeof(T));
}
public List<GameObject> FindAllGameObjects()
{
List<GameObject> allObjects = new List<GameObject>();
for (int i = 0; i < SceneManager.sceneCount; i++)
{
Scene scene = SceneManager.GetSceneAt(i);
if (scene.isLoaded)
{
allObjects.AddRange(scene.GetRootGameObjects());
}
}
List<GameObject> dontDestroyOnLoadObjects = GetDontDestroyOnLoadRoots();
allObjects.AddRange(dontDestroyOnLoadObjects);
if (includeDisabledObjects)
{
List<GameObject> allObjectsWithChildren = new List<GameObject>();
foreach (GameObject go in allObjects)
{
allObjectsWithChildren.AddRange(go.GetComponentsInChildren<Transform>(true)
.Select(t => t.gameObject));
}
return allObjectsWithChildren;
}
else
{
List<GameObject> allActiveObjectsWithChildren = new List<GameObject>();
foreach (GameObject go in allObjects)
{
if (go.activeInHierarchy)
{
allActiveObjectsWithChildren.AddRange(go.GetComponentsInChildren<Transform>(false)
.Where(t => t.gameObject.activeInHierarchy)
.Select(t => t.gameObject));
}
}
return allActiveObjectsWithChildren;
}
}
}
}