Created
February 21, 2016 21:39
-
-
Save kornman00/6ef834f9254d4426594f to your computer and use it in GitHub Desktop.
Preferences editor for Unity3D's profiler window
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Use at your own risk. | |
// Should work with any Unity Editor since at least 4.6 | |
// These macros make it so changes aren't actually committed to the EditorPrefs. Instead, changes are Debug.Log'd | |
//#define VIEW_PREFS_READ_ONLY | |
#define CHART_PREFS_READ_ONLY | |
// You'll need this defined, unless you have my BitVector32 from https://bitbucket.org/KornnerStudios/ksoft | |
#define USE_SYSTEM_BITVECTOR | |
using System; | |
using System.Collections.Generic; | |
using UnityEngine; | |
using UnityEditor; | |
using UnityEditorInternal; | |
#if USE_SYSTEM_BITVECTOR | |
using BitVector32 = System.Collections.Specialized.BitVector32; | |
#else | |
using BitVector32 = KSoft.Collections.BitVector32; | |
#endif | |
namespace UnityEditorInternal | |
{ | |
static class SystemBitVectorUtils | |
{ | |
public static int ToBitVectorIndexer(this int bit) | |
{ | |
#if USE_SYSTEM_BITVECTOR | |
return 1 << bit; | |
#else | |
return bit; | |
#endif | |
} | |
}; | |
public static class ProfilerHierarchyInterop | |
{ | |
private sealed class ProfilerHierarchyColumns | |
{ | |
public string ColumnSettings; | |
public ProfilerColumn[] Columns; | |
private string[] mColumnNames; | |
private int mColumnSettingsMask; | |
private BitVector32 mRemovedForMinimal; | |
public ProfilerHierarchyColumns Setup() | |
{ | |
mColumnNames = new string[Columns.Length]; | |
for (int x = 0; x < Columns.Length; x++) | |
mColumnNames[x] = Columns[x].ToString(); | |
mColumnSettingsMask = (1 << Columns.Length) - 1; | |
return this; | |
} | |
public ProfilerHierarchyColumns RemoveForMinimalView(params ProfilerColumn[] remove) | |
{ | |
#if !USE_SYSTEM_BITVECTOR | |
mRemovedForMinimal.Clear(); | |
foreach (var col in remove) | |
{ | |
int index = Array.IndexOf(Columns, col); | |
mRemovedForMinimal[index] = true; | |
} | |
#endif | |
return this; | |
} | |
private void DoRemoveForMinimalGUI() | |
{ | |
#if !USE_SYSTEM_BITVECTOR | |
if (mRemovedForMinimal.IsAllClear) | |
return; | |
var oldSettings = GetProfilerColumnSettings(ColumnSettings, Columns); | |
var newSettings = oldSettings.AndNot(mRemovedForMinimal); | |
if (newSettings == oldSettings) | |
return; | |
if (GUILayout.Button("Use minimal view")) | |
{ | |
if (mOriginalVisibleSettings == null) | |
{ | |
mOriginalVisibleSettings = oldSettings.Data; | |
} | |
SetProfilerColumnSettings(ColumnSettings, Columns, newSettings); | |
} | |
#endif | |
} | |
private bool mExpanded = true; | |
private int? mOriginalVisibleSettings; | |
public void DoGUI(string label) | |
{ | |
mExpanded = EditorGUILayout.Foldout(mExpanded, label); | |
if (!mExpanded) | |
return; | |
GUILayout.BeginVertical("box"); | |
DoRemoveForMinimalGUI(); | |
var oldSettings = GetProfilerColumnSettings(ColumnSettings, Columns); | |
int newSettings = EditorGUILayout.MaskField("Visible", oldSettings.Data, mColumnNames); | |
// Ugh, MaskField's "Everything" sets bits that are outside the option set! | |
// Mask out non-options to ignore false positive changes | |
newSettings &= mColumnSettingsMask; | |
if (newSettings != oldSettings.Data) | |
{ | |
#if VIEW_PREFS_READ_ONLY | |
Debug.LogFormat("{0} changed: {1} -> {2}", | |
ColumnSettings, oldSettings.Data, newSettings); | |
#else | |
if (mOriginalVisibleSettings == null) | |
{ | |
mOriginalVisibleSettings = oldSettings.Data; | |
} | |
SetProfilerColumnSettings(ColumnSettings, Columns, new BitVector32(newSettings)); | |
#endif | |
} | |
#if !VIEW_PREFS_READ_ONLY | |
if (mOriginalVisibleSettings.HasValue && GUILayout.Button("Revert")) | |
{ | |
SetProfilerColumnSettings(ColumnSettings, Columns, new BitVector32(mOriginalVisibleSettings.Value)); | |
mOriginalVisibleSettings = null; | |
} | |
#endif | |
GUILayout.EndVertical(); | |
} | |
}; | |
// No ProfilerWindow code uses this setting | |
private const string kProfilerVisibleGraphsSettings = "VisibleProfilerGraphs"; | |
private static ProfilerHierarchyColumns kProfilerCpuColumns; | |
private static ProfilerHierarchyColumns kProfilerCpuDetailColumns; | |
private static ProfilerHierarchyColumns kProfilerGpuColumns; | |
private static ProfilerHierarchyColumns kProfilerGpuDetailColumns; | |
static ProfilerHierarchyInterop() | |
{ | |
#region kProfilerCpuColumns | |
kProfilerCpuColumns = new ProfilerHierarchyColumns | |
{ | |
ColumnSettings = "VisibleProfilerColumnsV2", | |
Columns = new[] { | |
ProfilerColumn.FunctionName, ProfilerColumn.TotalPercent, ProfilerColumn.SelfPercent, | |
ProfilerColumn.Calls, ProfilerColumn.GCMemory, ProfilerColumn.TotalTime, | |
ProfilerColumn.SelfTime, ProfilerColumn.WarningCount | |
}, | |
}; | |
kProfilerCpuColumns.Setup() | |
.RemoveForMinimalView(ProfilerColumn.GCMemory, ProfilerColumn.TotalTime, ProfilerColumn.SelfTime, ProfilerColumn.WarningCount); | |
#endregion | |
#region kProfilerCpuDetailColumns | |
kProfilerCpuDetailColumns = new ProfilerHierarchyColumns | |
{ | |
ColumnSettings = "VisibleProfilerDetailColumns", | |
Columns = new[] { | |
ProfilerColumn.ObjectName, ProfilerColumn.TotalPercent, ProfilerColumn.SelfPercent, | |
ProfilerColumn.Calls, ProfilerColumn.GCMemory, ProfilerColumn.TotalTime, | |
ProfilerColumn.SelfTime | |
}, | |
}; | |
kProfilerCpuDetailColumns.Setup() | |
.RemoveForMinimalView(ProfilerColumn.GCMemory, ProfilerColumn.TotalTime, ProfilerColumn.SelfTime); | |
#endregion | |
#region kProfilerGpuColumns | |
kProfilerGpuColumns = new ProfilerHierarchyColumns | |
{ | |
ColumnSettings = "VisibleProfilerGPUColumns", | |
Columns = new[] { | |
ProfilerColumn.FunctionName, ProfilerColumn.TotalGPUPercent, ProfilerColumn.DrawCalls, ProfilerColumn.TotalGPUTime | |
}, | |
}; | |
kProfilerGpuColumns.Setup(); | |
#endregion | |
#region kProfilerGpuDetailColumns | |
kProfilerGpuDetailColumns = new ProfilerHierarchyColumns | |
{ | |
ColumnSettings = "VisibleProfilerGPUDetailColumns", | |
Columns = new[] { | |
ProfilerColumn.ObjectName, ProfilerColumn.TotalGPUPercent, ProfilerColumn.DrawCalls, ProfilerColumn.TotalGPUTime | |
}, | |
}; | |
kProfilerGpuDetailColumns.Setup(); | |
#endregion | |
} | |
private static BitVector32 GetProfilerColumnSettings(string columnSettingsName, ProfilerColumn[] columns) | |
{ | |
string str = EditorPrefs.GetString(columnSettingsName); | |
var settings = new BitVector32(); | |
if (string.IsNullOrEmpty(str)) | |
{ | |
// internal GUI code actually uses the settings string for determining columns to hide. | |
// So if the editor prefs doesn't have the string, no columns will get hidden | |
// See end of ProfilerHierarchyGUI.SetupSplitter | |
for (int x = 0; x < columns.Length; x++) | |
settings[x.ToBitVectorIndexer()] = true; | |
return settings; | |
} | |
if (str.Length < columns.Length) | |
{ | |
Debug.LogWarningFormat("Settings string {0} is shorter than expected columns: {1} < {2}. {3}", | |
columnSettingsName, str.Length, columns.Length, str); | |
} | |
else if (str.Length > columns.Length) | |
{ | |
Debug.LogWarningFormat("Settings string {0} is longer than expected columns: {1} > {2}. {3}", | |
columnSettingsName, str.Length, columns.Length, str); | |
} | |
for (int x = 0; x < str.Length; x++) | |
{ | |
var c = str[x]; | |
if (c != '0') | |
settings[x.ToBitVectorIndexer()] = true; | |
} | |
return settings; | |
} | |
private static void SetProfilerColumnSettings(string columnSettingsName, ProfilerColumn[] columns, BitVector32 settings) | |
{ | |
var str = new System.Text.StringBuilder(); | |
str.Append('1', columns.Length); | |
for (int x = 0; x < columns.Length; x++) | |
{ | |
var bit = settings[x.ToBitVectorIndexer()]; | |
if (!bit) | |
str[x] = '0'; | |
} | |
EditorPrefs.SetString(columnSettingsName, str.ToString()); | |
} | |
private static Vector2 sScrollPosition; | |
[PreferenceItem("Profiler View")] | |
private static void OnPreferencesGUI() | |
{ | |
sScrollPosition = EditorGUILayout.BeginScrollView(sScrollPosition); | |
kProfilerCpuColumns.DoGUI("CPU Columns"); | |
kProfilerCpuDetailColumns.DoGUI("CPU Detail Columns"); | |
kProfilerGpuColumns.DoGUI("GPU Columns"); | |
kProfilerGpuDetailColumns.DoGUI("GPU Detail Columns"); | |
EditorGUILayout.EndScrollView(); | |
} | |
}; | |
public static class ProfilerChartInterop | |
{ | |
// #TODO eventually support this | |
private const string kPrefCharts = "ProfilerChart"; | |
private sealed class ProfilerChartSettings | |
{ | |
public ProfilerArea Area; | |
private string mLabel; | |
private string mSettingsName; | |
private List<string> mStats; | |
private string[] mStatsArray; | |
private string mActiveSettingName; | |
private string mStatsVisibleSettingName; | |
private int mStatsVisibleMask; | |
private string mOrderingSettingName; | |
private List<int> mOrdering; | |
public ProfilerChartSettings(ProfilerArea area) | |
{ | |
Area = area; | |
mLabel = Area.ToString(); | |
mSettingsName = kPrefCharts + Area; | |
mStats = new List<string>(); | |
mActiveSettingName = mSettingsName; | |
mStatsVisibleSettingName = mSettingsName + "Visible"; | |
mOrderingSettingName = mSettingsName + "Order"; | |
} | |
public ProfilerChartSettings AddStat(string name) | |
{ | |
mStats.Add(name); | |
return this; | |
} | |
public ProfilerChartSettings AddChart(string name) | |
{ | |
AddStat(name); | |
AddStat("Selected" + name); | |
return this; | |
} | |
public ProfilerChartSettings Setup() | |
{ | |
mStatsVisibleMask = (1 << mStats.Count) - 1; | |
mStatsArray = mStats.ToArray(); | |
mOrdering = new List<int>(mStats.Count); | |
for (int i = 0; i < mStats.Count; i++) | |
mOrdering.Add(mStats.Count - 1 - i); | |
return this; | |
} | |
private bool Active | |
{ | |
get { return EditorPrefs.GetBool(mActiveSettingName, true); } | |
set { EditorPrefs.SetBool(mActiveSettingName, value); } | |
} | |
private bool mExpanded; | |
private bool mOrderingExpanded; | |
private int? mOriginalVisibleSettings; | |
public void DoGUI() | |
{ | |
mExpanded = EditorGUILayout.Foldout(mExpanded, mLabel); | |
if (!mExpanded) | |
return; | |
GUILayout.BeginVertical("box"); | |
#region Active | |
bool oldActive = Active; | |
bool newActive = EditorGUILayout.Toggle("Active", oldActive); | |
if (newActive != oldActive) | |
{ | |
Active = newActive; | |
#if CHART_PREFS_READ_ONLY | |
Debug.LogFormat("{0} changed: {1} -> {2}", | |
mActiveSettingName, oldActive, newActive); | |
#else | |
Active = newActive; | |
#endif | |
} | |
#endregion | |
#region Visible | |
var oldSettings = GetChartAreaVisibleSettings(mStatsVisibleSettingName, mStats); | |
int newSettings = EditorGUILayout.MaskField("Visible", oldSettings.Data, mStatsArray); | |
// Ugh, MaskField's "Everything" sets bits that are outside the option set! | |
// Mask out non-options to ignore false positive changes | |
newSettings &= mStatsVisibleMask; | |
if (newSettings != oldSettings.Data) | |
{ | |
#if CHART_PREFS_READ_ONLY | |
Debug.LogFormat("{0} changed: {1} -> {2}", | |
mStatsVisibleSettingName, oldSettings.Data, newSettings); | |
#else | |
if (mOriginalVisibleSettings == null) | |
{ | |
mOriginalVisibleSettings = oldSettings.Data; | |
} | |
SetChartAreaVisibleSettings(mStatsVisibleSettingName, mStats, new BitVector32(newSettings)); | |
#endif | |
#if !CHART_PREFS_READ_ONLY | |
if (mOriginalVisibleSettings.HasValue && GUILayout.Button("Revert")) | |
{ | |
SetChartAreaVisibleSettings(mStatsVisibleSettingName, mStats, new BitVector32(mOriginalVisibleSettings.Value)); | |
mOriginalVisibleSettings = null; | |
} | |
#endif | |
} | |
#endregion | |
#region Ordering TODO | |
mOrderingExpanded = EditorGUILayout.Foldout(mOrderingExpanded, "Ordering"); | |
if (mOrderingExpanded) | |
{ | |
for (int x = 0; x < mStats.Count; x++) | |
{ | |
#if CHART_PREFS_READ_ONLY | |
EditorGUILayout.LabelField(mStats[x], mOrdering[x].ToString()); | |
#endif | |
} | |
} | |
#endregion | |
GUILayout.EndVertical(); | |
} | |
}; | |
private static BitVector32 GetChartAreaVisibleSettings(string settingsName, List<string> chartNames) | |
{ | |
string str = EditorPrefs.GetString(settingsName); | |
var settings = new BitVector32(); | |
if (string.IsNullOrEmpty(str)) | |
{ | |
// internal GUI code actually uses the settings string for determining columns to hide. | |
// So if the editor prefs doesn't have the string, no columns will get hidden | |
// See Chart.LoadChartsSettings | |
for (int x = 0; x < chartNames.Count; x++) | |
settings[x.ToBitVectorIndexer()] = true; | |
return settings; | |
} | |
if (str.Length < chartNames.Count) | |
{ | |
Debug.LogWarningFormat("Settings string {0} is shorter than expected: {1} < {2}. {3}", | |
settingsName, str.Length, chartNames.Count, str); | |
} | |
else if (str.Length > chartNames.Count) | |
{ | |
Debug.LogWarningFormat("Settings string {0} is longer than expected: {1} > {2}. {3}", | |
settingsName, str.Length, chartNames.Count, str); | |
} | |
for (int x = 0; x < str.Length; x++) | |
{ | |
var c = str[x]; | |
if (c != '0') | |
settings[x.ToBitVectorIndexer()] = true; | |
} | |
return settings; | |
} | |
private static void SetChartAreaVisibleSettings(string settingsName, List<string> chartNames, BitVector32 settings) | |
{ | |
var str = new System.Text.StringBuilder(); | |
str.Append('1', chartNames.Count); | |
for (int x = 0; x < chartNames.Count; x++) | |
{ | |
var bit = settings[x.ToBitVectorIndexer()]; | |
if (!bit) | |
str[x] = '0'; | |
} | |
EditorPrefs.SetString(settingsName, str.ToString()); | |
} | |
#region kProfilerAreaCpu | |
private static ProfilerChartSettings kProfilerAreaCpu = new ProfilerChartSettings(ProfilerArea.CPU) | |
.AddChart("Rendering") | |
.AddChart("Scripts") | |
.AddChart("Physics") | |
.AddChart("GarbageCollector") | |
.AddChart("VSync") | |
.AddChart("Others") | |
.Setup(); | |
#endregion | |
#region kProfilerAreaGpu | |
private static ProfilerChartSettings kProfilerAreaGpu = new ProfilerChartSettings(ProfilerArea.GPU) | |
.AddChart("Opaque") | |
.AddChart("Transparent") | |
.AddChart("Shadows/Depth") | |
.AddChart("Deferred PrePass") | |
.AddChart("Deferred Lighting") | |
.AddChart("PostProcess") | |
.AddChart("Other") | |
.Setup(); | |
#endregion | |
#region kProfilerAreaRendering | |
private static ProfilerChartSettings kProfilerAreaRendering = new ProfilerChartSettings(ProfilerArea.Rendering) | |
.AddStat("Draw Calls") | |
.AddStat("Triangles") | |
.AddStat("Vertices") | |
.Setup(); | |
#endregion | |
#region kProfilerAreaMemory | |
private static ProfilerChartSettings kProfilerAreaMemory = new ProfilerChartSettings(ProfilerArea.Memory) | |
.AddStat("Total Allocated") | |
.AddStat("Texture Memory") | |
.AddStat("Texture Count") | |
.AddStat("Mesh Count") | |
.AddStat("Material Count") | |
.AddStat("Object Count") | |
.Setup(); | |
#endregion | |
#region kProfilerAreaAudio | |
private static ProfilerChartSettings kProfilerAreaAudio = new ProfilerChartSettings(ProfilerArea.Audio) | |
.AddStat("Total Sources") | |
.AddStat("Playing Sources") | |
.AddStat("Paused Sources") | |
.AddStat("Audio Clip Count") | |
.AddStat("Playing Voices") | |
.AddStat("Total Audio CPU") | |
.AddStat("DSP CPU") | |
.AddStat("Streaming CPU") | |
.AddStat("Other CPU") | |
.AddStat("Total Audio Memory") | |
.AddStat("Streaming Sound Memory") | |
.AddStat("Sample Sound Memory") | |
.AddStat("Voice Memory") | |
.AddStat("DSP Memory") | |
.AddStat("Extra DSP Buffer Memory") | |
.AddStat("Codec Memory") | |
.AddStat("Recording Memory") | |
.AddStat("Reverb Memory") | |
.AddStat("Other Audio Buffers") | |
.AddStat("Other Memory") | |
.Setup(); | |
#endregion | |
#region kProfilerAreaPhysics | |
private static ProfilerChartSettings kProfilerAreaPhysics = new ProfilerChartSettings(ProfilerArea.Physics) | |
.AddStat("Active Rigidbodies") | |
.AddStat("Sleeping Rigidbodies") | |
.AddStat("Number of Contacts") | |
.AddStat("Static Colliders") | |
.AddStat("Dynamic Colliders") | |
.Setup(); | |
#endregion | |
#region kProfilerAreaPhysics2D | |
private static ProfilerChartSettings kProfilerAreaPhysics2D = new ProfilerChartSettings(ProfilerArea.Physics2D) | |
.AddStat("Total Bodies") | |
.AddStat("Active Bodies") | |
.AddStat("Sleeping Bodies") | |
.AddStat("Dynamic Bodies") | |
.AddStat("Kinematic Bodies") | |
.AddStat("Discrete Bodies") | |
.AddStat("Continuous Bodies") | |
.AddStat("Joints") | |
.AddStat("Contacts") | |
.AddStat("Static Collider Shapes") | |
.AddStat("Active Collider Shapes") | |
.AddStat("Sleeping Collider Shapes") | |
.AddStat("Step Time") | |
.AddStat("Contact Time") | |
.AddStat("Solve Time") | |
.AddStat("Solve Initialization Time") | |
.AddStat("Solve Velocity Time") | |
.AddStat("Solve Position Time") | |
.AddStat("Solve Broadphase Time") | |
.AddStat("Solve TOI Time") | |
.Setup(); | |
#endregion | |
private static Vector2 sScrollPosition; | |
[PreferenceItem("Profiler Charts")] | |
private static void OnPreferencesGUI() | |
{ | |
sScrollPosition = EditorGUILayout.BeginScrollView(sScrollPosition); | |
kProfilerAreaCpu.DoGUI(); | |
kProfilerAreaGpu.DoGUI(); | |
kProfilerAreaRendering.DoGUI(); | |
kProfilerAreaMemory.DoGUI(); | |
kProfilerAreaAudio.DoGUI(); | |
kProfilerAreaPhysics.DoGUI(); | |
kProfilerAreaPhysics2D.DoGUI(); | |
EditorGUILayout.EndScrollView(); | |
} | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment