initial commit

This commit is contained in:
Jo 2025-01-07 02:06:59 +01:00
parent 6715289efe
commit 788c3389af
37645 changed files with 2526849 additions and 80 deletions

View file

@ -0,0 +1,391 @@
using System;
using System.Collections.Generic;
using UnityEditor.Analytics;
using UnityEngine;
using UnityEngine.Analytics;
namespace UnityEditor.Performance.ProfileAnalyzer
{
class ProfileAnalyzerAnalytics
{
const int k_MaxEventsPerHour = 100;
const int k_MaxEventItems = 1000;
const string k_VendorKey = "unity.profileanalyzer";
const string k_EventTopicName = "usability";
static bool s_EnableAnalytics = false;
public static void EnableAnalytics()
{
#if UNITY_2018_1_OR_NEWER
AnalyticsResult result = EditorAnalytics.RegisterEventWithLimit(k_EventTopicName, k_MaxEventsPerHour, k_MaxEventItems, k_VendorKey);
if (result == AnalyticsResult.Ok)
s_EnableAnalytics = true;
#endif
}
public enum UIButton
{
Pull,
OpenProfiler,
CloseProfiler,
JumpToFrame,
ExportSingleFrames,
ExportComparisonFrames,
};
public enum UIUsageMode
{
Single,
Comparison,
};
public enum UIVisibility
{
FrameTimeContextMenu,
Filters,
TopTen,
Frames,
Threads,
Markers,
};
public enum UIResizeView
{
Single,
Comparison,
};
[Serializable]
struct ProfileAnalyzerUIButtonEventParameters
{
public string name;
public ProfileAnalyzerUIButtonEventParameters(string name)
{
this.name = name;
}
}
// camelCase since these events get serialized to Json and naming convention in analytics is camelCase
[Serializable]
struct ProfileAnalyzerUIButtonEvent
{
public ProfileAnalyzerUIButtonEvent(string name, float durationInTicks)
{
subtype = "profileAnalyzerUIButton";
// ts is auto added so no need to include it here
//ts = (int)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
this.duration = durationInTicks;
parameters = new ProfileAnalyzerUIButtonEventParameters(name);
}
public string subtype;
//public int ts;
public float duration; // Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
public ProfileAnalyzerUIButtonEventParameters parameters;
}
[Serializable]
struct ProfileAnalyzerUIUsageEventParameters
{
public string name;
public ProfileAnalyzerUIUsageEventParameters(string name)
{
this.name = name;
}
}
[Serializable]
struct ProfileAnalyzerUIUsageEvent
{
public ProfileAnalyzerUIUsageEvent(string name, float durationInTicks)
{
subtype = "profileAnalyzerModeUsage";
this.duration = durationInTicks;
parameters = new ProfileAnalyzerUIUsageEventParameters(name);
}
public string subtype;
public float duration; // Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
public ProfileAnalyzerUIUsageEventParameters parameters;
}
[Serializable]
struct ProfileAnalyzerUIVisibilityEventParameters
{
public string name;
public bool show;
public ProfileAnalyzerUIVisibilityEventParameters(string name, bool show)
{
this.name = name;
this.show = show;
}
}
[Serializable]
struct ProfileAnalyzerUIVisibilityEvent
{
public ProfileAnalyzerUIVisibilityEvent(string name, float durationInTicks, bool show)
{
subtype = "profileAnalyzerUIVisibility";
this.duration = durationInTicks;
parameters = new ProfileAnalyzerUIVisibilityEventParameters(name, show);
}
public string subtype;
public float duration; // Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
public ProfileAnalyzerUIVisibilityEventParameters parameters;
}
[Serializable]
struct ProfileAnalyzerUIResizeEventParameters
{
public string name;
public float width;
public float height;
public float screenWidth;
public float screenHeight;
public bool docked;
public ProfileAnalyzerUIResizeEventParameters(string name, float width, float height, float screenWidth, float screenHeight, bool isDocked)
{
this.name = name;
this.width = width;
this.height = height;
this.screenWidth = screenWidth;
this.screenHeight = screenHeight;
docked = isDocked;
}
}
[Serializable]
struct ProfileAnalyzerUIResizeEvent
{
public ProfileAnalyzerUIResizeEvent(string name, float durationInTicks, float width, float height, float screenWidth, float screenHeight, bool isDocked)
{
subtype = "profileAnalyzerUIResize";
this.duration = durationInTicks;
parameters = new ProfileAnalyzerUIResizeEventParameters(name, width, height, screenWidth, screenHeight, isDocked);
}
public string subtype;
public float duration; // Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
public ProfileAnalyzerUIResizeEventParameters parameters;
}
static float SecondsToTicks(float durationInSeconds)
{
return durationInSeconds * 10000;
}
public static bool SendUIButtonEvent(UIButton uiButton, float durationInSeconds)
{
if (!s_EnableAnalytics)
return false;
#if UNITY_2018_1_OR_NEWER
// Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
float durationInTicks = SecondsToTicks(durationInSeconds);
ProfileAnalyzerUIButtonEvent uiButtonEvent;
switch (uiButton)
{
case UIButton.Pull:
uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerGrab", durationInTicks);
break;
case UIButton.OpenProfiler:
uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerOpenProfiler", durationInTicks);
break;
case UIButton.CloseProfiler:
uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerCloseProfiler", durationInTicks);
break;
case UIButton.JumpToFrame:
uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerJumpToFrame", durationInTicks);
break;
case UIButton.ExportSingleFrames:
uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerExportSingleFrames", durationInTicks);
break;
case UIButton.ExportComparisonFrames:
uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerExportComparisonFrames", durationInTicks);
break;
default:
Debug.LogFormat("SendUIButtonEvent: Unsupported button type : {0}", uiButton);
return false;
}
AnalyticsResult result = EditorAnalytics.SendEventWithLimit(k_EventTopicName, uiButtonEvent);
if (result != AnalyticsResult.Ok)
return false;
return true;
#else
return false;
#endif
}
public static bool SendUIUsageModeEvent(UIUsageMode uiUsageMode, float durationInSeconds)
{
if (!s_EnableAnalytics)
return false;
#if UNITY_2018_1_OR_NEWER
// Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
float durationInTicks = SecondsToTicks(durationInSeconds);
ProfileAnalyzerUIUsageEvent uiUsageEvent;
switch (uiUsageMode)
{
case UIUsageMode.Single:
uiUsageEvent = new ProfileAnalyzerUIUsageEvent("profileAnalyzerSingle", durationInTicks);
break;
case UIUsageMode.Comparison:
uiUsageEvent = new ProfileAnalyzerUIUsageEvent("profileAnalyzerCompare", durationInTicks);
break;
default:
Debug.LogFormat("SendUsageEvent: Unsupported usage mode : {0}", uiUsageMode);
return false;
}
AnalyticsResult result = EditorAnalytics.SendEventWithLimit(k_EventTopicName, uiUsageEvent);
if (result != AnalyticsResult.Ok)
return false;
return true;
#else
return false;
#endif
}
public static bool SendUIVisibilityEvent(UIVisibility uiVisibility, float durationInSeconds, bool show)
{
if (!s_EnableAnalytics)
return false;
#if UNITY_2018_1_OR_NEWER
// Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
float durationInTicks = SecondsToTicks(durationInSeconds);
ProfileAnalyzerUIVisibilityEvent uiUsageEvent;
switch (uiVisibility)
{
case UIVisibility.FrameTimeContextMenu:
uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerFrameTimeContextMenu", durationInTicks, show);
break;
case UIVisibility.Filters:
uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerFilters", durationInTicks, show);
break;
case UIVisibility.TopTen:
uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerTopTen", durationInTicks, show);
break;
case UIVisibility.Frames:
uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerFrames", durationInTicks, show);
break;
case UIVisibility.Threads:
uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerThreads", durationInTicks, show);
break;
case UIVisibility.Markers:
uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerMarkers", durationInTicks, show);
break;
default:
Debug.LogFormat("SendUIVisibilityEvent: Unsupported visibililty item : {0}", uiVisibility);
return false;
}
AnalyticsResult result = EditorAnalytics.SendEventWithLimit(k_EventTopicName, uiUsageEvent);
if (result != AnalyticsResult.Ok)
return false;
return true;
#else
return false;
#endif
}
public static bool SendUIResizeEvent(UIResizeView uiResizeView, float durationInSeconds, float width, float height, bool isDocked)
{
if (!s_EnableAnalytics)
return false;
#if UNITY_2018_1_OR_NEWER
// Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
float durationInTicks = SecondsToTicks(durationInSeconds);
ProfileAnalyzerUIResizeEvent uiResizeEvent;
switch (uiResizeView)
{
case UIResizeView.Single:
// Screen.width, Screen.height is game view size
uiResizeEvent = new ProfileAnalyzerUIResizeEvent("profileAnalyzerSingle", durationInTicks, width, height, Screen.currentResolution.width, Screen.currentResolution.height, isDocked);
break;
case UIResizeView.Comparison:
uiResizeEvent = new ProfileAnalyzerUIResizeEvent("profileAnalyzerCompare", durationInTicks, width, height, Screen.currentResolution.width, Screen.currentResolution.height, isDocked);
break;
default:
Debug.LogFormat("SendUIResizeEvent: Unsupported view : {0}", uiResizeView);
return false;
}
AnalyticsResult result = EditorAnalytics.SendEventWithLimit(k_EventTopicName, uiResizeEvent);
if (result != AnalyticsResult.Ok)
return false;
return true;
#else
return false;
#endif
}
internal class Analytic
{
double m_StartTime;
float m_DurationInSeconds;
public Analytic()
{
m_StartTime = EditorApplication.timeSinceStartup;
m_DurationInSeconds = 0;
}
public void End()
{
m_DurationInSeconds = (float)(EditorApplication.timeSinceStartup - m_StartTime);
}
public float GetDurationInSeconds()
{
return m_DurationInSeconds;
}
}
static public Analytic BeginAnalytic()
{
return new Analytic();
}
static public void SendUIButtonEvent(UIButton uiButton, Analytic instance)
{
instance.End();
SendUIButtonEvent(uiButton, instance.GetDurationInSeconds());
}
static public void SendUIUsageModeEvent(UIUsageMode uiUsageMode, Analytic instance)
{
instance.End();
SendUIUsageModeEvent(uiUsageMode, instance.GetDurationInSeconds());
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: adf9820979228054693ce2e8224012fb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Unity.Performance.Profile-Analyzer.EditorTests")]

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dcb585fc3518ff949912f2d4fff2e26f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,240 @@
using UnityEngine;
using System;
namespace UnityEditor.Performance.ProfileAnalyzer
{
internal class BoxAndWhiskerPlot
{
Draw2D m_2D;
Color m_ColorBackground;
DisplayUnits m_Units;
string DisplayUnits()
{
return m_Units.Postfix();
}
string ToDisplayUnits(float ms, bool showUnits = false)
{
return m_Units.ToString(ms, showUnits, 5, true);
}
string ToTooltipDisplayUnits(float ms, bool showUnits = false)
{
return m_Units.ToTooltipString(ms, showUnits);
}
public void SetUnits(Units units)
{
m_Units = new DisplayUnits(units);
}
public BoxAndWhiskerPlot(Draw2D draw2D, Units units, Color colorBackground)
{
m_2D = draw2D;
SetUnits(units);
m_ColorBackground = colorBackground;
}
public BoxAndWhiskerPlot(Draw2D draw2D, Units units)
{
m_2D = draw2D;
SetUnits(units);
m_ColorBackground = new Color(0.4f, 0.4f, 0.4f);
}
float ClampToRange(float value, float min, float max)
{
return Math.Max(min, Math.Min(value, max));
}
public void Draw(float width, float height, float min, float lowerQuartile, float median, float upperQuartile, float max, float yAxisStart, float yAxisEnd, Color color, Color colorFilled)
{
if (m_2D.DrawStart(width, height, Draw2D.Origin.BottomLeft))
{
Rect rect = GUILayoutUtility.GetLastRect();
float x = 0;
float y = 0;
float w = width;
float h = height;
Draw(rect, x, y, w, h, min, lowerQuartile, median, upperQuartile, max, yAxisStart, yAxisEnd, color, colorFilled);
m_2D.DrawEnd();
}
}
string GetTooltip(float min, float lowerQuartile, float median, float upperQuartile, float max)
{
string tooltip = string.Format(
"Max :\t\t{0}\n\nUpper Quartile :\t{1}\nMedian :\t\t{2}\nLower Quartile :\t{3}\nInterquartile range : \t{4}\n\nMin :\t\t{5}\nUnits :\t\t{6}",
ToTooltipDisplayUnits(max),
ToTooltipDisplayUnits(upperQuartile),
ToTooltipDisplayUnits(median),
ToTooltipDisplayUnits(lowerQuartile),
ToTooltipDisplayUnits(upperQuartile - lowerQuartile),
ToTooltipDisplayUnits(min),
m_Units.Postfix()
);
return tooltip;
}
public void Draw(Rect rect, float x, float y, float w, float h, float min, float lowerQuartile, float median, float upperQuartile, float max, float yAxisStart, float yAxisEnd, Color color, Color colorFilled, bool clearFirst = true)
{
string tooltip = GetTooltip(min, lowerQuartile, median, upperQuartile, max);
GUI.Label(rect, new GUIContent("", tooltip));
if (clearFirst)
m_2D.DrawFilledBox(x, y, w, h, m_ColorBackground);
float first = yAxisStart;
float last = yAxisEnd;
float range = last - first;
bool startCap = (min >= first) ? true : false;
bool endCap = (max <= last) ? true : false;
// Range clamping
min = ClampToRange(min, first, last);
lowerQuartile = ClampToRange(lowerQuartile, first, last);
median = ClampToRange(median, first, last);
upperQuartile = ClampToRange(upperQuartile, first, last);
max = ClampToRange(max, first, last);
float hMax = h - 1;
float yMin = hMax * (min - first) / range;
float yLowerQuartile = hMax * (lowerQuartile - first) / range;
float yMedian = hMax * (median - first) / range;
float yUpperQuartile = hMax * (upperQuartile - first) / range;
float yMax = hMax * (max - first) / range;
// Min to max line
float xCentre = x + (w / 2);
m_2D.DrawLine(xCentre, y + yMin, xCentre, y + yLowerQuartile, color);
m_2D.DrawLine(xCentre, y + yUpperQuartile, xCentre, y + yMax, color);
// Quartile boxes
float xMargin = (2 * w / 8);
float x1 = x + xMargin;
float x2 = x + (w - xMargin);
float wBox = x2 - x1;
if (colorFilled != color)
m_2D.DrawFilledBox(x1, y + yLowerQuartile, wBox, (yMedian - yLowerQuartile), colorFilled);
m_2D.DrawBox(x1, y + yLowerQuartile, wBox, (yMedian - yLowerQuartile), color);
if (colorFilled != color)
m_2D.DrawFilledBox(x1, y + yMedian, wBox, (yUpperQuartile - yMedian), colorFilled);
m_2D.DrawBox(x1, y + yMedian, wBox, (yUpperQuartile - yMedian), color);
// Median line
//xMargin = (1 * w / 8);
//x1 = x + xMargin;
//x2 = x + (w - xMargin);
m_2D.DrawLine(x1, y + yMedian, x2, y + yMedian, color);
m_2D.DrawLine(x1, y + yMedian + 1, x2, y + yMedian + 1, color);
// Line caps
xMargin = (3 * w / 8);
x1 = x + xMargin;
x2 = x + (w - xMargin);
if (startCap)
m_2D.DrawLine(x1, y + yMin, x2, y + yMin, color);
if (endCap)
m_2D.DrawLine(x1, y + yMax, x2, y + yMax, color);
}
public void DrawHorizontal(float width, float height, float min, float lowerQuartile, float median, float upperQuartile, float max, float xAxisStart, float xAxisEnd, Color color, Color colorFilled, GUIStyle style = null)
{
if (m_2D.DrawStart(width, height, Draw2D.Origin.BottomLeft, style))
{
Rect rect = GUILayoutUtility.GetLastRect();
float x = 0;
float y = 0;
float w = width;
float h = height;
DrawHorizontal(rect, x, y, w, h, min, lowerQuartile, median, upperQuartile, max, xAxisStart, xAxisEnd, color, colorFilled);
m_2D.DrawEnd();
}
}
public void DrawHorizontal(Rect rect, float x, float y, float w, float h, float min, float lowerQuartile, float median, float upperQuartile, float max, float xAxisStart, float xAxisEnd, Color color, Color colorFilled, bool clearFirst = true)
{
string tooltip = GetTooltip(min, lowerQuartile, median, upperQuartile, max);
GUI.Label(rect, new GUIContent("", tooltip));
if (clearFirst)
m_2D.DrawFilledBox(x, y, w, h, m_ColorBackground);
float first = xAxisStart;
float last = xAxisEnd;
float range = last - first;
bool startCap = (min >= first) ? true : false;
bool endCap = (max <= last) ? true : false;
// Range clamping
min = ClampToRange(min, first, last);
lowerQuartile = ClampToRange(lowerQuartile, first, last);
median = ClampToRange(median, first, last);
upperQuartile = ClampToRange(upperQuartile, first, last);
max = ClampToRange(max, first, last);
float xMin = w * (min - first) / range;
float xLowerQuartile = w * (lowerQuartile - first) / range;
float xMedian = w * (median - first) / range;
float xUpperQuartile = w * (upperQuartile - first) / range;
float xMax = w * (max - first) / range;
// Min to max line
m_2D.DrawLine(x + xMin, y + (h / 2), x + xMax, y + (h / 2), color);
// Quartile boxes
float yMargin = (2 * h / 8);
float y1 = y + yMargin;
float y2 = y + (h - yMargin);
float hBox = y2 - y1;
if (colorFilled != color)
m_2D.DrawFilledBox(x + xLowerQuartile, y1, xMedian - xLowerQuartile, hBox, colorFilled);
m_2D.DrawBox(x + xLowerQuartile, y1, xMedian - xLowerQuartile, hBox, color);
if (colorFilled != color)
m_2D.DrawFilledBox(x + xMedian, y1, xUpperQuartile - xMedian, hBox, colorFilled);
m_2D.DrawBox(x + xMedian, y1, xUpperQuartile - xMedian, hBox, color);
// Median line
//yMargin = (1 * h / 8);
//y1 = y + yMargin;
//y2 = y + (h - yMargin);
m_2D.DrawLine(x + xMedian, y1, x + xMedian, y2, color);
m_2D.DrawLine(x + xMedian + 1, y1, x + xMedian + 1, y2, color);
// Line caps
yMargin = (3 * h / 8);
y1 = y + yMargin;
y2 = y + (h - yMargin);
if (startCap)
m_2D.DrawLine(x + xMin, y1, x + xMin, y2, color);
if (endCap)
m_2D.DrawLine(x + xMax, y1, x + xMax, y2, color);
}
public void DrawText(float width, float plotHeight, float min, float max, string minTooltip, string maxTooltip)
{
GUIStyle shiftUpStyle = new GUIStyle(GUI.skin.label);
shiftUpStyle.contentOffset = new Vector2(0, -5);
shiftUpStyle.alignment = TextAnchor.UpperLeft;
EditorGUILayout.BeginVertical(GUILayout.Height(plotHeight));
EditorGUILayout.LabelField(new GUIContent(ToDisplayUnits(max), maxTooltip), shiftUpStyle, GUILayout.Width(width));
GUILayout.FlexibleSpace();
GUIStyle shiftDownStyle = new GUIStyle(GUI.skin.label);
shiftDownStyle.contentOffset = new Vector2(0, 1);
shiftDownStyle.alignment = TextAnchor.LowerLeft;
EditorGUILayout.LabelField(new GUIContent(ToDisplayUnits(min), minTooltip), shiftDownStyle, GUILayout.Width(width));
EditorGUILayout.EndVertical();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ae5c414d7d406467d8ce01807c211f90
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,184 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System;
namespace UnityEditor.Performance.ProfileAnalyzer
{
internal class Columns
{
int[] m_ColumnWidth = new int[4];
public Columns(int a, int b, int c, int d)
{
SetColumnSizes(a, b, c, d);
}
public void SetColumnSizes(int a, int b, int c, int d)
{
m_ColumnWidth[0] = a;
m_ColumnWidth[1] = b;
m_ColumnWidth[2] = c;
m_ColumnWidth[3] = d;
}
public int GetColumnWidth(int n)
{
if (n < 0 || n >= m_ColumnWidth.Length)
return 0;
return m_ColumnWidth[n];
}
public void Draw(int n, string col)
{
if (n < 0 || n >= m_ColumnWidth.Length || m_ColumnWidth[n] <= 0)
EditorGUILayout.LabelField(col);
EditorGUILayout.LabelField(col, GUILayout.Width(m_ColumnWidth[n]));
}
public void Draw(int n, float value)
{
Draw(n, string.Format("{0:f2}", value));
}
public void Draw2(string col1, string col2)
{
EditorGUILayout.BeginHorizontal();
Draw(0, col1);
Draw(1, col2);
EditorGUILayout.EndHorizontal();
}
public void Draw2(string label, float value)
{
EditorGUILayout.BeginHorizontal();
Draw(0, label);
Draw(1, value);
EditorGUILayout.EndHorizontal();
}
public void Draw3(string col1, string col2, string col3)
{
EditorGUILayout.BeginHorizontal();
Draw(0, col1);
Draw(1, col2);
Draw(2, col3);
EditorGUILayout.EndHorizontal();
}
public void Draw3(string col1, float value2, float value3)
{
EditorGUILayout.BeginHorizontal();
Draw(0, col1);
Draw(1, value2);
Draw(2, value3);
EditorGUILayout.EndHorizontal();
}
public void Draw4(string col1, string col2, string col3, string col4)
{
EditorGUILayout.BeginHorizontal();
Draw(0, col1);
Draw(1, col2);
Draw(2, col3);
Draw(3, col4);
EditorGUILayout.EndHorizontal();
}
public void Draw4Diff(string col1, float left, float right)
{
EditorGUILayout.BeginHorizontal();
Draw(0, col1);
Draw(1, left);
Draw(2, right);
Draw(3, right - left);
EditorGUILayout.EndHorizontal();
}
public void Draw4(string col1, float value2, float value3, float value4)
{
EditorGUILayout.BeginHorizontal();
Draw(0, col1);
Draw(1, value2);
Draw(2, value3);
Draw(3, value4);
EditorGUILayout.EndHorizontal();
}
// GUIContent versions
public void Draw(int n, GUIContent col)
{
if (n < 0 || n >= m_ColumnWidth.Length || m_ColumnWidth[n] <= 0)
EditorGUILayout.LabelField(col);
EditorGUILayout.LabelField(col, GUILayout.Width(m_ColumnWidth[n]));
}
public void Draw2(GUIContent col1, GUIContent col2)
{
EditorGUILayout.BeginHorizontal();
Draw(0, col1);
Draw(1, col2);
EditorGUILayout.EndHorizontal();
}
public void Draw2(GUIContent label, float value)
{
EditorGUILayout.BeginHorizontal();
Draw(0, label);
Draw(1, value);
EditorGUILayout.EndHorizontal();
}
public void Draw3(GUIContent col1, GUIContent col2, GUIContent col3)
{
EditorGUILayout.BeginHorizontal();
Draw(0, col1);
Draw(1, col2);
Draw(2, col3);
EditorGUILayout.EndHorizontal();
}
public void Draw3(GUIContent col1, float value2, float value3)
{
EditorGUILayout.BeginHorizontal();
Draw(0, col1);
Draw(1, value2);
Draw(2, value3);
EditorGUILayout.EndHorizontal();
}
public void Draw4(GUIContent col1, GUIContent col2, GUIContent col3, GUIContent col4)
{
EditorGUILayout.BeginHorizontal();
Draw(0, col1);
Draw(1, col2);
Draw(2, col3);
Draw(3, col4);
EditorGUILayout.EndHorizontal();
}
public void Draw4Diff(GUIContent col1, float left, float right)
{
EditorGUILayout.BeginHorizontal();
Draw(0, col1);
Draw(1, left);
Draw(2, right);
Draw(3, right - left);
EditorGUILayout.EndHorizontal();
}
public void Draw4(GUIContent col1, float value2, float value3, float value4)
{
EditorGUILayout.BeginHorizontal();
Draw(0, col1);
Draw(1, value2);
Draw(2, value3);
Draw(3, value4);
EditorGUILayout.EndHorizontal();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a402cbe9b84984bd4a16cb20ca9c3bed
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 281fa44557d2f417aafc937bd5d93ba5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,130 @@
using System;
using UnityEngine;
#if UNITY_2019_1_OR_NEWER
using System.Collections.Generic;
using System.Reflection;
using UnityEditor.IMGUI.Controls;
namespace UnityEditor.Performance.ProfileAnalyzer
{
public class DepthSliceDropdown : AdvancedDropdown
{
class DepthSliceDropdownItem : AdvancedDropdownItem
{
public int depthSlice;
public int depthSliceLeft;
public int depthSliceRight;
public DepthSliceDropdownItem(int depthSlice)
: base(DepthSliceUI.DepthFilterToString(depthSlice))
{
this.depthSlice = depthSlice;
depthSliceLeft = depthSlice;
depthSliceRight = depthSlice;
}
public DepthSliceDropdownItem(int depthSliceLeft, int depthSliceRight, bool leftIsMain)
: base(DepthSliceUI.DepthFilterToString(depthSliceLeft, depthSliceRight, leftIsMain))
{
depthSlice = Math.Max(depthSliceLeft, depthSliceRight);
this.depthSliceLeft = depthSliceLeft;
this.depthSliceRight = depthSliceRight;
}
}
Action<int, int, int> m_Callback = null;
int m_DepthSliceCount;
int m_DepthSliceCountRight;
int m_CurrentDepthSliceA;
int m_CurrentDepthSliceB;
int m_DepthDiff;
static FieldInfo m_DataSourceFieldInfo;
static Type m_DataSourceTypeInfo;
static PropertyInfo m_SelectedIdsFieldInfo;
public DepthSliceDropdown(int depthSliceCount, int currentDepthSliceA, int currentDepthSliceB, Action<int, int, int> callback, int depthDiff, int depthSliceCountRight = ProfileAnalyzer.kDepthAll) : base(new AdvancedDropdownState())
{
m_DepthSliceCount = depthSliceCount;
m_DepthSliceCountRight = depthSliceCountRight;
m_CurrentDepthSliceA = currentDepthSliceA;
m_CurrentDepthSliceB = currentDepthSliceB;
m_Callback = callback;
m_DepthDiff = depthDiff;
if (m_DataSourceFieldInfo == null || m_DataSourceFieldInfo == null || m_SelectedIdsFieldInfo == null)
{
Assembly assem = typeof(AdvancedDropdown).Assembly;
var advancedDropdownTypeInfo = typeof(AdvancedDropdown);
m_DataSourceTypeInfo = assem.GetType("UnityEditor.IMGUI.Controls.CallbackDataSource");
m_DataSourceFieldInfo = advancedDropdownTypeInfo.GetField("m_DataSource", BindingFlags.NonPublic | BindingFlags.Instance);
m_SelectedIdsFieldInfo = m_DataSourceTypeInfo.GetProperty("selectedIDs", BindingFlags.Public | BindingFlags.Instance);
}
}
protected override AdvancedDropdownItem BuildRoot()
{
var root = new AdvancedDropdownItem("Depth Slice");
var allItem = new DepthSliceDropdownItem(ProfileAnalyzer.kDepthAll);
root.AddChild(allItem);
if (m_CurrentDepthSliceA == ProfileAnalyzer.kDepthAll && m_CurrentDepthSliceB == ProfileAnalyzer.kDepthAll)
(m_SelectedIdsFieldInfo.GetValue(m_DataSourceFieldInfo.GetValue(this)) as List<int>).Add(allItem.id);
var count = m_DepthSliceCountRight == ProfileAnalyzer.kDepthAll ? m_DepthSliceCount :
Math.Max(m_DepthSliceCount + Math.Max(0, m_DepthDiff), m_DepthSliceCountRight - Math.Min(0, m_DepthDiff));
var leftIsMain = m_DepthDiff < 0;
var mainThreshold = leftIsMain ? m_DepthSliceCount : m_DepthSliceCountRight;
var secondaryMinThreshold = Math.Abs(m_DepthDiff);
var secondaryMaxThreshold = (leftIsMain ? m_DepthSliceCountRight : m_DepthSliceCount) + secondaryMinThreshold;
var startIndex = 1;
for (int i = startIndex; i <= count; i++)
{
var selected = false;
AdvancedDropdownItem child;
if (m_DepthSliceCountRight != ProfileAnalyzer.kDepthAll)
{
var left = Mathf.Clamp(i - Math.Max(0, m_DepthDiff), 1, m_DepthSliceCount);
var right = Mathf.Clamp(i - Math.Max(0, -m_DepthDiff), 1, m_DepthSliceCountRight);
if (m_DepthSliceCount <= 0)
left = -1;
else if (m_DepthSliceCountRight <= 0)
right = -1;
else
{
// Separators only make sense if there is data on both sides
// did we pass the threshold of the main's max depth and started clamping it down?
if (i == mainThreshold + 1
// ... or the threshold of the secondary's negative depth when adjusted for the depth diff, and stoped clamping it up?
|| (secondaryMinThreshold != 0 && i == secondaryMinThreshold + 1)
// ... or the threshold of the secondary's max depth when adjusted for the depth diff, and started clamping it down?
|| (i == secondaryMaxThreshold + 1))
root.AddSeparator();
}
child = new DepthSliceDropdownItem(left, right, leftIsMain);
selected = m_CurrentDepthSliceA == left && m_CurrentDepthSliceB == right;
}
else
{
child = new DepthSliceDropdownItem(i);
selected = m_CurrentDepthSliceA == i;
}
root.AddChild(child);
if (selected)
(m_SelectedIdsFieldInfo.GetValue(m_DataSourceFieldInfo.GetValue(this)) as List<int>).Add(child.id);
}
return root;
}
protected override void ItemSelected(AdvancedDropdownItem item)
{
base.ItemSelected(item);
if (m_Callback != null)
{
var sliceItem = (item as DepthSliceDropdownItem);
m_Callback(sliceItem.depthSlice, sliceItem.depthSliceLeft, sliceItem.depthSliceRight);
}
}
}
}
#endif

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 71b9e5c19de243c386bd048443d1c5cc
timeCreated: 1608205585

View file

@ -0,0 +1,692 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Video;
namespace UnityEditor.Performance.ProfileAnalyzer
{
[Serializable]
public class DepthSliceUI
{
#if !UNITY_2019_1_OR_NEWER
[NonSerialized]
string[] m_DepthStrings;
[NonSerialized]
int[] m_DepthValues;
[NonSerialized]
string[] m_DepthStringsAuto;
[NonSerialized]
int[] m_DepthValuesAuto;
[NonSerialized]
int m_OldDepthRaw1;
[NonSerialized]
int m_OldDepthRaw2;
[NonSerialized]
string[] m_DepthStrings1;
[NonSerialized]
int[] m_DepthValues1;
[NonSerialized]
string[] m_DepthStrings2;
[NonSerialized]
int[] m_DepthValues2;
#endif
[SerializeField] int m_DepthFilter = ProfileAnalyzer.kDepthAll;
public int depthFilter {get { return m_DepthFilter; }}
[SerializeField] int m_DepthFilter1 = ProfileAnalyzer.kDepthAll;
public int depthFilter1 {get { return m_DepthFilter1; }}
[SerializeField] int m_DepthFilter2 = ProfileAnalyzer.kDepthAll;
public int depthFilter2 {get { return m_DepthFilter2; }}
[SerializeField] bool m_DepthFilterAuto = true;
[SerializeField] int m_MostCommonDepthDiff = 0;
int mostCommonDepthDiff
{
set
{
if (m_MostCommonDepthDiff != value)
{
m_MostCommonDepthDiff = value;
UpdateAutoDepthTitleText();
}
}
get
{
return m_MostCommonDepthDiff;
}
}
void UpdateAutoDepthTitleText()
{
ProfileAnalyzerWindow.Styles.autoDepthTitle.text =
string.Format(ProfileAnalyzerWindow.Styles.autoDepthTitleText, mostCommonDepthDiff);
}
Action<bool> m_UpdateActiveTabCallback = null;
public DepthSliceUI(Action<bool> updateActiveTabCallback)
{
m_UpdateActiveTabCallback = updateActiveTabCallback;
UpdateAutoDepthTitleText();
}
public void OnEnable(Action<bool> updateActiveTabCallback)
{
m_UpdateActiveTabCallback = updateActiveTabCallback;
UpdateAutoDepthTitleText();
}
#if !UNITY_2019_1_OR_NEWER
void SetDepthStringsSingle(int maxDepth, out string[] strings, out int[] values)
{
var count = maxDepth;
List<string> depthStrings = new List<string>();
List<int> depthValues = new List<int>();
depthStrings.Add(DepthFilterToString(ProfileAnalyzer.kDepthAll));
depthValues.Add(ProfileAnalyzer.kDepthAll);
var startIndex = 1;
var depthValue = 1;
for (int depth = startIndex; depth <= count; depth++)
{
depthStrings.Add(DepthFilterToString(depth));
depthValues.Add(depthValue++);
}
strings = depthStrings.ToArray();
values = depthValues.ToArray();
}
void SetDepthStringsCompare(int maxDepth, out string[] strings, out int[] values, int maxDepthRight = ProfileAnalyzer.kDepthAll)
{
var locked = maxDepthRight != ProfileAnalyzer.kDepthAll;
var count = locked ? Math.Max(maxDepth + Math.Max(0, mostCommonDepthDiff), maxDepthRight - Math.Min(0, mostCommonDepthDiff)) : maxDepth;
List<string> depthStrings = new List<string>();
List<int> depthValues = new List<int>();
depthStrings.Add(DepthFilterToString(ProfileAnalyzer.kDepthAll));
depthValues.Add(ProfileAnalyzer.kDepthAll);
var leftIsMain = mostCommonDepthDiff < 0;
var startIndex = 1;
var depthValue = 1;
if (maxDepth <= 0 && mostCommonDepthDiff < 0)
{
depthValue = 1;
startIndex = -mostCommonDepthDiff + 1;
}
else if(maxDepthRight <= 0 && mostCommonDepthDiff > 0)
{
depthValue = 1;
startIndex = mostCommonDepthDiff + 1;
}
for (int depth = startIndex; depth <= count; depth++)
{
if (locked)
{
var left = Mathf.Clamp(depth - Math.Max(0, mostCommonDepthDiff), 1, maxDepth);
var right = Mathf.Clamp(depth - Math.Max(0, -mostCommonDepthDiff), 1, maxDepthRight);
if (maxDepth <= 0)
left = -1;
if (maxDepthRight <= 0)
right = -1;
depthStrings.Add(DepthFilterToString(left, right, leftIsMain));
}
else
depthStrings.Add(DepthFilterToString(depth));
depthValues.Add(depthValue++);
}
strings = depthStrings.ToArray();
values = depthValues.ToArray();
}
#endif
#if UNITY_2019_1_OR_NEWER
enum ViewType
{
Single,
Left,
Right,
Locked,
}
void DrawDepthFilterDropdown(GUIContent title, bool enabled, ProfileDataView view, Action<int, int, int> callback,
ViewType viewType, ProfileDataView profileSingleView, ProfileDataView profileLeftView, ProfileDataView profileRightView)
{
if(title !=null)
EditorGUILayout.LabelField(title, GUILayout.Width(ProfileAnalyzerWindow.LayoutSize.FilterOptionsEnumWidth));
int depthFilter = ProfileAnalyzer.kDepthAll;
int depthFilterOther = ProfileAnalyzer.kDepthAll;
var maxDepth = view.GetMaxDepth();
var maxDepthLeft = ProfileAnalyzer.kDepthAll;
var maxDepthRight = ProfileAnalyzer.kDepthAll;
var oldDepthFilter = ProfileAnalyzer.kDepthAll;
var oldDepthFilterOtherLocked = ProfileAnalyzer.kDepthAll;
var depthDiff = mostCommonDepthDiff;
GUIContent content;
switch (viewType)
{
case ViewType.Single:
oldDepthFilter = m_DepthFilter;
depthFilter = m_DepthFilter =
m_DepthFilter == ProfileAnalyzer.kDepthAll ?
ProfileAnalyzer.kDepthAll :
profileSingleView.ClampToValidDepthValue(m_DepthFilter);
content = new GUIContent(DepthFilterToString(depthFilter));
depthFilterOther = depthFilter;
depthDiff = 0;
break;
case ViewType.Left:
oldDepthFilter = m_DepthFilter1;
depthFilter = m_DepthFilter1 =
m_DepthFilter1 == ProfileAnalyzer.kDepthAll ?
ProfileAnalyzer.kDepthAll :
profileLeftView.ClampToValidDepthValue(m_DepthFilter1);
content = new GUIContent(DepthFilterToString(depthFilter));
depthFilterOther = depthFilter;
break;
case ViewType.Right:
oldDepthFilter = m_DepthFilter2;
depthFilter = m_DepthFilter2 = m_DepthFilter2 == ProfileAnalyzer.kDepthAll
? ProfileAnalyzer.kDepthAll
: profileRightView.ClampToValidDepthValue(m_DepthFilter2);
content = new GUIContent(DepthFilterToString(depthFilter));
depthFilterOther = depthFilter;
break;
case ViewType.Locked:
oldDepthFilter = m_DepthFilter1;
oldDepthFilterOtherLocked = m_DepthFilter2;
maxDepth = maxDepthLeft = profileLeftView.GetMaxDepth();
maxDepthRight = profileRightView.GetMaxDepth();
ClampDepthFilterForAutoRespectingDiff(ref m_DepthFilter1, ref m_DepthFilter2, profileLeftView, profileRightView);
depthFilter = m_DepthFilter1;
depthFilterOther = m_DepthFilter2;
content = new GUIContent(DepthFilterToString(m_DepthFilter1, m_DepthFilter2, mostCommonDepthDiff < 0));
break;
default:
throw new NotImplementedException();
}
var lastEnabled = GUI.enabled;
GUI.enabled = enabled;
var rect = GUILayoutUtility.GetRect(content, EditorStyles.popup, GUILayout.MinWidth(ProfileAnalyzerWindow.LayoutSize.FilterOptionsEnumWidth));
if (GUI.Button(rect, content, EditorStyles.popup))
{
var dropdown = new DepthSliceDropdown(maxDepth, depthFilter, depthFilterOther, (slice, left, right) =>
{
if (slice != depthFilter || (viewType == ViewType.Locked && (left != m_DepthFilter1 || right != m_DepthFilter2)))
{
callback(slice, left, right);
UpdateDepthFilters(viewType == ViewType.Single, profileSingleView, profileLeftView, profileRightView);
m_UpdateActiveTabCallback(true);
}
}, depthDiff, maxDepthRight);
dropdown.Show(rect);
EditorGUIUtility.ExitGUI();
}
else
{
// The depths can change because the data changed, not just because the user selected a different option in the dropdown
// in that case, the depth filters need to perform a refresh
if(oldDepthFilter != depthFilter || viewType == ViewType.Locked && oldDepthFilterOtherLocked != depthFilterOther)
{
UpdateDepthFilters(viewType == ViewType.Single, profileSingleView, profileLeftView, profileRightView);
m_UpdateActiveTabCallback(true);
}
}
GUI.enabled = lastEnabled;
}
#endif
int CalcSliceMenuEntryIndex(int filterDepthLeft, int filterDepthRight, int leftMax, int rightMax)
{
return mostCommonDepthDiff > 0 ?
filterDepthRight + Math.Max(0, filterDepthLeft - rightMax + (rightMax > 0 ? mostCommonDepthDiff : filterDepthLeft > 0 ? 1 : 0)) :
filterDepthLeft + Math.Max(0, filterDepthRight - leftMax - (leftMax > 0 ? mostCommonDepthDiff : filterDepthRight > 0 ? -1 :0));
}
void CalcAutoSlicesFromMenuEntryIndex(int depthSlcieMenuEntryIndex, ref int filterDepthLeft, ref int filterDepthRight, int leftMax, int rightMax)
{
if (mostCommonDepthDiff > 0)
{
filterDepthRight = Mathf.Clamp(depthSlcieMenuEntryIndex, 1, rightMax);
filterDepthLeft = Mathf.Clamp(depthSlcieMenuEntryIndex - (rightMax > 0 ? mostCommonDepthDiff : 0), 1, leftMax);
}
else
{
filterDepthLeft = Mathf.Clamp(depthSlcieMenuEntryIndex, 1, leftMax);
filterDepthRight = Mathf.Clamp(depthSlcieMenuEntryIndex + (leftMax > 0 ? mostCommonDepthDiff : 0), 1, rightMax);
}
// if a side has no depth, only allow All
if (leftMax <= 0)
filterDepthLeft = -1;
if (rightMax <= 0)
filterDepthRight = -1;
}
void ClampDepthFilterForAutoRespectingDiff(ref int filterDepthLeft, ref int filterDepthRight, ProfileDataView profileLeftView, ProfileDataView profileRightView)
{
if (filterDepthLeft == ProfileAnalyzer.kDepthAll && filterDepthRight == ProfileAnalyzer.kDepthAll)
{
// nothing to do here, keep showing all
return;
}
var leftMax = profileLeftView.GetMaxDepth();
var rightMax = profileRightView.GetMaxDepth();
var sliceMenuEntryIndex = CalcSliceMenuEntryIndex(filterDepthLeft, filterDepthRight, leftMax, rightMax);
CalcAutoSlicesFromMenuEntryIndex(sliceMenuEntryIndex, ref filterDepthLeft, ref filterDepthRight, leftMax, rightMax);
}
internal void DrawDepthFilter(bool isAnalysisRunning, bool singleView,
ProfileDataView profileSingleView, ProfileDataView profileLeftView, ProfileDataView profileRightView)
{
#if !UNITY_2019_1_OR_NEWER
bool triggerRefresh = false;
if (!isAnalysisRunning)
{
if (singleView)
{
var maxDepth = profileSingleView.GetMaxDepth();
if (m_DepthStrings == null || maxDepth != m_OldDepthRaw1)
{
SetDepthStringsSingle(maxDepth, out m_DepthStrings, out m_DepthValues);
m_OldDepthRaw1 = maxDepth;
triggerRefresh = true;
}
}
else
{
if (m_DepthFilterAuto)
{
var maxLeftRaw = profileLeftView.GetMaxDepth();
var maxRightRaw = profileRightView.GetMaxDepth();
if (m_DepthStringsAuto == null ||
m_OldDepthRaw1 != maxLeftRaw || m_OldDepthRaw2 != maxRightRaw)
{
SetDepthStringsCompare(maxLeftRaw, out m_DepthStringsAuto, out m_DepthValuesAuto, maxRightRaw);
m_OldDepthRaw1 = maxLeftRaw;
m_OldDepthRaw2 = maxRightRaw;
triggerRefresh = true;
}
}
else
{
var maxDepthLeft = profileLeftView.GetMaxDepth();
if (m_DepthStrings1 == null || m_OldDepthRaw1 != maxDepthLeft)
{
SetDepthStringsSingle(maxDepthLeft, out m_DepthStrings1, out m_DepthValues1);
m_OldDepthRaw1 = maxDepthLeft;
triggerRefresh = true;
}
var maxDepthRight = profileRightView.GetMaxDepth();
if (m_DepthStrings2 == null || m_OldDepthRaw2 != maxDepthRight)
{
SetDepthStringsSingle(maxDepthRight, out m_DepthStrings2, out m_DepthValues2);
m_OldDepthRaw2 = maxDepthRight;
triggerRefresh = true;
}
}
}
}
#endif
bool lastEnabled = GUI.enabled;
bool enabled = !isAnalysisRunning;
EditorGUILayout.BeginHorizontal();
if (singleView)
{
EditorGUILayout.LabelField(ProfileAnalyzerWindow.Styles.depthTitle, GUILayout.Width(ProfileAnalyzerWindow.LayoutSize.FilterOptionsLeftLabelWidth));
#if UNITY_2019_1_OR_NEWER
DrawDepthFilterDropdown(null, enabled,
profileSingleView, (primary, left, right) => m_DepthFilter = primary,
ViewType.Single, profileSingleView, profileLeftView, profileRightView);
#else
if (m_DepthStrings != null)
{
var lastDepthFilter = m_DepthFilter;
m_DepthFilter = m_DepthFilter == ProfileAnalyzer.kDepthAll ? ProfileAnalyzer.kDepthAll : profileSingleView.ClampToValidDepthValue(m_DepthFilter);
GUI.enabled = enabled;
m_DepthFilter = EditorGUILayout.IntPopup(m_DepthFilter, m_DepthStrings, m_DepthValues, GUILayout.Width(ProfileAnalyzerWindow.LayoutSize.FilterOptionsEnumWidth));
GUI.enabled = lastEnabled;
if (m_DepthFilter != lastDepthFilter)
triggerRefresh = true;
}
#endif
}
else
{
EditorGUILayout.LabelField(ProfileAnalyzerWindow.Styles.depthTitle, GUILayout.Width(ProfileAnalyzerWindow.LayoutSize.FilterOptionsLeftLabelWidth));
if (m_DepthFilterAuto)
{
#if UNITY_2019_1_OR_NEWER
DrawDepthFilterDropdown(null, enabled, profileLeftView, (primary, left, right) =>
{
m_DepthFilter1 = left;
m_DepthFilter2 = right;
ClampDepthFilterForAutoRespectingDiff(ref m_DepthFilter1, ref m_DepthFilter2, profileLeftView, profileRightView);
},
ViewType.Locked, profileSingleView, profileLeftView, profileRightView);
#else
if (m_DepthStringsAuto != null)
{
var leftMax = profileLeftView.GetMaxDepth();
var rightMax = profileRightView.GetMaxDepth();
var lastDepthFilterDropdownIndex = CalcSliceMenuEntryIndex(m_DepthFilter1, m_DepthFilter2, leftMax, rightMax);
ClampDepthFilterForAutoRespectingDiff(ref m_DepthFilter1, ref m_DepthFilter2,
profileLeftView, profileRightView);
var layoutOptionWidth = ProfileAnalyzerWindow.LayoutSize.FilterOptionsEnumWidth;
if(m_DepthFilter1 != m_DepthFilter2)
layoutOptionWidth = ProfileAnalyzerWindow.LayoutSize.FilterOptionsLockedEnumWidth;
GUI.enabled = enabled;
var depthFilterDropdownIndex = Mathf.Clamp(lastDepthFilterDropdownIndex, -1, m_DepthStringsAuto.Length - 1);
depthFilterDropdownIndex = EditorGUILayout.IntPopup(depthFilterDropdownIndex, m_DepthStringsAuto, m_DepthValuesAuto, GUILayout.Width(layoutOptionWidth));
GUI.enabled = lastEnabled;
if (depthFilterDropdownIndex != lastDepthFilterDropdownIndex)
{
if(depthFilterDropdownIndex == ProfileAnalyzer.kDepthAll)
m_DepthFilter1 = m_DepthFilter2 = ProfileAnalyzer.kDepthAll;
else
CalcAutoSlicesFromMenuEntryIndex(depthFilterDropdownIndex, ref m_DepthFilter1, ref m_DepthFilter2, leftMax, rightMax);
ClampDepthFilterForAutoRespectingDiff(ref m_DepthFilter1, ref m_DepthFilter2, profileLeftView, profileRightView);
triggerRefresh = true;
}
}
#endif
}
else
{
#if UNITY_2019_1_OR_NEWER
DrawDepthFilterDropdown(ProfileAnalyzerWindow.Styles.leftDepthTitle, enabled, profileLeftView,
(primary, left, right) => m_DepthFilter1 = primary,
ViewType.Left, profileSingleView, profileLeftView, profileRightView);
#else
if (m_DepthStrings1 != null)
{
EditorGUILayout.LabelField(ProfileAnalyzerWindow.Styles.leftDepthTitle, GUILayout.Width(ProfileAnalyzerWindow.LayoutSize.FilterOptionsEnumWidth));
int lastDepthFilter1 = m_DepthFilter1;
m_DepthFilter1 = m_DepthFilter1 == ProfileAnalyzer.kDepthAll ? ProfileAnalyzer.kDepthAll : profileLeftView.ClampToValidDepthValue(m_DepthFilter1);
GUI.enabled = enabled;
m_DepthFilter1 = EditorGUILayout.IntPopup(m_DepthFilter1, m_DepthStrings1, m_DepthValues1, GUILayout.Width(ProfileAnalyzerWindow.LayoutSize.FilterOptionsEnumWidth));
GUI.enabled = lastEnabled;
if (m_DepthFilter1 != lastDepthFilter1)
triggerRefresh = true;
}
#endif
#if UNITY_2019_1_OR_NEWER
DrawDepthFilterDropdown(ProfileAnalyzerWindow.Styles.rightDepthTitle, enabled && !m_DepthFilterAuto, profileRightView,
(primary, left, right) => m_DepthFilter2 = primary,
ViewType.Right, profileSingleView, profileLeftView, profileRightView);
#else
if (m_DepthStrings2 != null)
{
EditorGUILayout.LabelField(ProfileAnalyzerWindow.Styles.rightDepthTitle, GUILayout.Width(ProfileAnalyzerWindow.LayoutSize.FilterOptionsEnumWidth));
int lastDepthFilter2 = m_DepthFilter2;
m_DepthFilter2 = m_DepthFilter2 == ProfileAnalyzer.kDepthAll ? ProfileAnalyzer.kDepthAll : profileRightView.ClampToValidDepthValue(m_DepthFilter2);
GUI.enabled = enabled && !m_DepthFilterAuto;
m_DepthFilter2 = EditorGUILayout.IntPopup(m_DepthFilter2, m_DepthStrings2, m_DepthValues2, GUILayout.Width(ProfileAnalyzerWindow.LayoutSize.FilterOptionsEnumWidth));
GUI.enabled = lastEnabled;
if (m_DepthFilter2 != lastDepthFilter2)
triggerRefresh = true;
}
#endif
}
bool lastDepthFilterLock = m_DepthFilterAuto;
GUI.enabled = enabled;
m_DepthFilterAuto = EditorGUILayout.ToggleLeft(ProfileAnalyzerWindow.Styles.autoDepthTitle, m_DepthFilterAuto);
GUI.enabled = lastEnabled;
if (m_DepthFilterAuto != lastDepthFilterLock)
{
if (UpdateDepthFilters(singleView, profileSingleView, profileLeftView, profileRightView))
m_UpdateActiveTabCallback(true);
#if !UNITY_2019_1_OR_NEWER
m_DepthStringsAuto = null;
m_DepthStrings1 = null;
m_DepthStrings2 = null;
#endif
}
}
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
#if !UNITY_2019_1_OR_NEWER
if (triggerRefresh)
{
UpdateDepthFilters(singleView, profileSingleView, profileLeftView, profileRightView);
m_UpdateActiveTabCallback(true);
}
#endif
}
internal bool UpdateDepthFilters(bool singleView, ProfileDataView profileSingleView, ProfileDataView profileLeftView, ProfileDataView profileRightView)
{
bool changed = false;
if (!singleView)
{
// First respect the auto flag
if (UpdateAutoDepthFilter(profileLeftView, profileRightView))
changed = true;
// Make sure Single matches the updated comparison view
if (profileLeftView.path == profileSingleView.path)
{
// Use same filter on single view if its the same file
if (m_DepthFilter != m_DepthFilter1)
{
m_DepthFilter = m_DepthFilter1;
changed = true;
}
}
if (profileRightView.path == profileSingleView.path)
{
// Use same filter on single view if its the same file
if (m_DepthFilter != m_DepthFilter2)
{
m_DepthFilter = m_DepthFilter2;
changed = true;
}
}
}
else
{
// Make sure comparisons match updated single view
if (profileLeftView.path == profileSingleView.path)
{
// Use same filter on comparison left view if its the same file
if (m_DepthFilter1 != m_DepthFilter)
{
m_DepthFilter1 = m_DepthFilter;
changed = true;
}
if (m_DepthFilterAuto)
{
var newDepthFilter2 = m_DepthFilter;
ClampDepthFilterForAutoRespectingDiff(ref m_DepthFilter1, ref newDepthFilter2, profileLeftView, profileRightView);
if (m_DepthFilter2 != newDepthFilter2)
{
m_DepthFilter2 = newDepthFilter2;
changed = true;
}
if (UpdateAutoDepthFilter(profileLeftView, profileRightView))
changed = true;
}
if (UpdateAutoDepthFilter(profileLeftView, profileRightView))
changed = true;
}
if (profileRightView.path == profileSingleView.path)
{
// Use same filter on comparison right view if its the same file
if (m_DepthFilter2 != m_DepthFilter)
{
m_DepthFilter2 = m_DepthFilter;
changed = true;
}
if (m_DepthFilterAuto)
{
var newDepthFilter1 = m_DepthFilter;
ClampDepthFilterForAutoRespectingDiff(ref newDepthFilter1, ref m_DepthFilter2, profileLeftView, profileRightView);
if (m_DepthFilter1 != newDepthFilter1)
{
m_DepthFilter1 = newDepthFilter1;
changed = true;
}
if (UpdateAutoDepthFilter(profileLeftView, profileRightView))
changed = true;
}
}
}
return changed;
}
int CalculateDepthDifference(ProfileAnalysis leftAnalysis, ProfileAnalysis rightAnalysis, List<MarkerPairing> pairings)
{
if (pairings.Count <= 0)
{
mostCommonDepthDiff = 0;
return 0;
}
var leftMarkers = leftAnalysis.GetMarkers();
var rightMarkers = rightAnalysis.GetMarkers();
int totalCount = 0;
Dictionary<int, int> depthDifferences = new Dictionary<int, int>();
foreach (var pairing in pairings)
{
if (pairing.leftIndex >= 0 && pairing.rightIndex >= 0)
{
MarkerData leftMarker = leftMarkers[pairing.leftIndex];
MarkerData rightMarker = rightMarkers[pairing.rightIndex];
int markerDepthDiff = rightMarker.minDepth - leftMarker.minDepth;
int value = 0;
depthDifferences.TryGetValue(markerDepthDiff, out value);
depthDifferences[markerDepthDiff] = value + 1;
totalCount += 1;
}
}
var newDepthDiff = 0;
// Find most common depth difference
int maxCount = 0;
foreach (var diff in depthDifferences.Keys)
{
if (depthDifferences[diff] > maxCount)
{
maxCount = depthDifferences[diff];
newDepthDiff = diff;
}
}
return mostCommonDepthDiff = newDepthDiff;
}
bool UpdateAutoDepthFilter(ProfileDataView profileLeftView, ProfileDataView profileRightView)
{
if (m_DepthFilterAuto)
{
var newDepthFilter1 = m_DepthFilter1;
var newDepthFilter2 = m_DepthFilter2;
ClampDepthFilterForAutoRespectingDiff(ref newDepthFilter1, ref newDepthFilter2, profileLeftView, profileRightView);
if (m_DepthFilter1 != newDepthFilter1)
{
m_DepthFilter1 = newDepthFilter1;
return true;
}
if (m_DepthFilter2 != newDepthFilter2)
{
m_DepthFilter2 = newDepthFilter2;
return true;
}
}
return false;
}
internal bool UpdateDepthForCompareSync(ProfileAnalysis leftAnalysis, ProfileAnalysis rightAnalysis, List<MarkerPairing> pairings, ProfileDataView profileLeftView, ProfileDataView profileRightView)
{
int originalDepthDiff = mostCommonDepthDiff;
int newDepthDiff = CalculateDepthDifference(leftAnalysis, rightAnalysis, pairings);
if (newDepthDiff != originalDepthDiff)
{
UpdateAutoDepthFilter(profileLeftView, profileRightView);
return true;
}
return false;
}
internal GUIContent GetUIInfo(bool compare)
{
GUIContent info;
if (compare && m_DepthFilter1 == ProfileAnalyzer.kDepthAll && m_DepthFilter2 == ProfileAnalyzer.kDepthAll ||
!compare && depthFilter == ProfileAnalyzer.kDepthAll)
{
info = new GUIContent("(All depths)", string.Format("{0}\n\nSet depth 1 to get an overview of the frame", ProfileAnalyzerWindow.Styles.medianFrameTooltip));
}
else
{
if (compare && depthFilter1 != depthFilter2)
{
if (m_DepthFilter1 == ProfileAnalyzer.kDepthAll)
info = new GUIContent(string.Format("(Filtered to 'all' depths in the first data set, and depth '{0}' in the second)", m_DepthFilter2), ProfileAnalyzerWindow.Styles.medianFrameTooltip);
else if (m_DepthFilter2 == ProfileAnalyzer.kDepthAll)
info = new GUIContent(string.Format("(Filtered to depth '{0}' in the first data set, and 'all' depths in the second)", m_DepthFilter1), ProfileAnalyzerWindow.Styles.medianFrameTooltip);
else
info = new GUIContent(string.Format("(Filtered to depth '{0}' in the first data set, and depth '{1}' in the second)", m_DepthFilter1, depthFilter2), ProfileAnalyzerWindow.Styles.medianFrameTooltip);
}
else
info = new GUIContent(string.Format("(Filtered to depth '{0}' only)", compare ? m_DepthFilter1 : depthFilter), ProfileAnalyzerWindow.Styles.medianFrameTooltip);
}
return info;
}
public static string DepthFilterToString(int depthFilter)
{
return depthFilter == ProfileAnalyzer.kDepthAll ? "All" : depthFilter.ToString();
}
public static string DepthFilterToString(int depthSliceLeft, int depthSliceRight, bool leftIsMain)
{
if(depthSliceLeft != depthSliceRight)
{
if (leftIsMain)
return string.Format("{0} ({1}{2})", DepthFilterToString(depthSliceLeft), ProfileAnalyzerWindow.Styles.rightDepthTitle.text, DepthFilterToString(depthSliceRight));
else
return string.Format("{0} ({1}{2})", DepthFilterToString(depthSliceRight), ProfileAnalyzerWindow.Styles.leftDepthTitle.text, DepthFilterToString(depthSliceLeft));
}
return DepthFilterToString(depthSliceLeft);
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6d4bf3d974bf4f74a15e72b2e0a8ffa2
timeCreated: 1608212438

View file

@ -0,0 +1,198 @@
using UnityEngine;
namespace UnityEditor.Performance.ProfileAnalyzer
{
internal class Draw2D
{
public enum Origin
{
TopLeft,
BottomLeft
};
Origin m_Origin = Origin.TopLeft;
GUIStyle m_GLStyle;
string m_ShaderName;
Material m_Material;
Rect m_Rect;
Vector4 m_ClipRect;
bool m_ClipRectEnabled = false;
public Draw2D(string shaderName)
{
m_ShaderName = shaderName;
CheckAndSetupMaterial();
}
public bool CheckAndSetupMaterial()
{
if (m_Material == null)
{
m_Material = new Material(Shader.Find(m_ShaderName));
}
if (m_Material == null)
return false;
return true;
}
public bool IsMaterialValid()
{
if (m_Material == null)
return false;
return true;
}
public void OnGUI()
{
if (m_GLStyle == null)
{
m_GLStyle = new GUIStyle(GUI.skin.box);
m_GLStyle.padding = new RectOffset(0, 0, 0, 0);
m_GLStyle.margin = new RectOffset(0, 0, 0, 0);
}
}
public void SetClipRect(Rect clipRect)
{
m_ClipRect = new Vector4(clipRect.x, clipRect.y, clipRect.x + clipRect.width, clipRect.y + clipRect.height);
m_ClipRectEnabled = true;
CheckAndSetupMaterial();
m_Material.SetFloat("_UseClipRect", m_ClipRectEnabled ? 1f : 0f);
m_Material.SetVector("_ClipRect", m_ClipRect);
}
public void ClearClipRect()
{
m_ClipRectEnabled = false;
CheckAndSetupMaterial();
m_Material.SetFloat("_UseClipRect", m_ClipRectEnabled ? 1f : 0f);
m_Material.SetVector("_ClipRect", m_ClipRect);
}
public Rect GetClipRect()
{
return new Rect(m_ClipRect.x, m_ClipRect.y, m_ClipRect.z - m_ClipRect.x, m_ClipRect.w - m_ClipRect.y);
}
public bool DrawStart(Rect r, Origin origin = Origin.TopLeft)
{
if (Event.current.type != EventType.Repaint)
return false;
if (!CheckAndSetupMaterial())
return false;
CheckAndSetupMaterial();
m_Material.SetPass(0);
m_Rect = r;
m_Origin = origin;
return true;
}
public bool DrawStart(float w, float h, Origin origin = Origin.TopLeft, GUIStyle style = null)
{
Rect r = GUILayoutUtility.GetRect(w, h, style == null ? m_GLStyle : style, GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(false));
return DrawStart(r, origin);
}
public void DrawEnd()
{
}
public void Translate(ref float x, ref float y)
{
// Translation done CPU side so we have world space coords in the shader for clipping.
if (m_Origin == Origin.BottomLeft)
{
x = m_Rect.xMin + x;
y = m_Rect.yMax - y;
}
else
{
x = m_Rect.xMin + x;
y = m_Rect.yMin + y;
}
}
public void DrawFilledBox(float x, float y, float w, float h, Color col)
{
float x2 = x + w;
float y2 = y + h;
Translate(ref x, ref y);
Translate(ref x2, ref y2);
if (m_Origin == Origin.BottomLeft)
{
GL.Begin(GL.TRIANGLE_STRIP);
GL.Color(col);
GL.Vertex3(x, y, 0);
GL.Vertex3(x, y2, 0);
GL.Vertex3(x2, y, 0);
GL.Vertex3(x2, y2, 0);
GL.End();
}
else
{
GL.Begin(GL.TRIANGLE_STRIP);
GL.Color(col);
GL.Vertex3(x, y, 0);
GL.Vertex3(x2, y, 0);
GL.Vertex3(x, y2, 0);
GL.Vertex3(x2, y2, 0);
GL.End();
}
}
public void DrawFilledBox(float x, float y, float w, float h, float r, float g, float b)
{
DrawFilledBox(x, y, w, h, new Color(r, g, b));
}
public void DrawLine(float x, float y, float x2, float y2, Color col)
{
Translate(ref x, ref y);
Translate(ref x2, ref y2);
GL.Begin(GL.LINES);
GL.Color(col);
GL.Vertex3(x, y, 0);
GL.Vertex3(x2, y2, 0);
GL.End();
}
public void DrawLine(float x, float y, float x2, float y2, float r, float g, float b)
{
DrawLine(x, y, x2, y2, new Color(r, g, b));
}
public void DrawBox(float x, float y, float w, float h, Color col)
{
float x2 = x + w;
float y2 = y + h;
Translate(ref x, ref y);
Translate(ref x2, ref y2);
GL.Begin(GL.LINE_STRIP);
GL.Color(col);
GL.Vertex3(x, y, 0);
GL.Vertex3(x2, y, 0);
GL.Vertex3(x2, y2, 0);
GL.Vertex3(x, y2, 0);
GL.Vertex3(x, y, 0);
GL.End();
}
public void DrawBox(float x, float y, float w, float h, float r, float g, float b)
{
DrawBox(x, y, w, h, new Color(r, g, b));
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c27a543a692ef4bf19459f25898a0ba9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,33 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Performance.ProfileAnalyzer
{
// Using Profiler.Begin/End calls is tricky, because any scope that could throw ExitGUI,
// which is likely most parts of this code base, would cause Begin-End-Mismatches if such an exception is thrown in between of them.
// On versions from 2018.3 and up this issue can be avoided by via using(ProfilerMarker.Auto(){}.
// Faking this behavior for pre 2018.3 versions, so that construction calls Profiler.Begin and Disposal calls Profiler.End
// won't work either and still cause Begin-End-Mismatches, especially in Deep Profiling, because the begin and end calls would be
// outside of the scope they would be expected to occure in
// So any version without ProfilerMarker API can use this stand in instead and just not add instrumentation.
// Paste this into any files that need these markers and then use ProfilerMarkerAbstracted:
// #if UNITY_2018_3_OR_NEWER
// using ProfilerMarkerAbstracted = Unity.Profiling.ProfilerMarker;
// #else
// using ProfilerMarkerAbstracted = UnityEditor.Performance.ProfileAnalyzer.FakeScopedMarker;
// #endif
#if !UNITY_2018_3_OR_NEWER
internal struct FakeScopedMarker
{
public FakeScopedMarker(string markerName)
{
}
public IDisposable Auto() { return null; }
}
#endif
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 02e22b3793b48ba42ad215edef819749
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
namespace UnityEditor.Performance.ProfileAnalyzer
{
[Serializable]
internal class FrameSummary
{
public double msTotal;
public int first;
public int last;
public int count; // Valid frame count may not be last-first
public float msMean;
public float msMedian;
public float msLowerQuartile;
public float msUpperQuartile;
public float msMin;
public float msMax;
public int medianFrameIndex;
public int minFrameIndex;
public int maxFrameIndex;
public int maxMarkerDepth;
public int totalMarkers;
public int markerCountMax; // Largest marker count (over all frames)
public float markerCountMaxMean; // Largest marker count mean
public int[] buckets = new int[20]; // Each bucket contains 'number of frames' for frametime in that range
public List<FrameTime> frames = new List<FrameTime>();
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: af62f7ba5c15e47ceb426c7745e31835
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,112 @@
using System;
namespace UnityEditor.Performance.ProfileAnalyzer
{
[Serializable]
/// <summary>
/// Metrics related to an individual frame
/// </summary>
internal struct FrameTime : IComparable<FrameTime>
{
/// <summary>Duration in the frame in milliseconds</summary>
public float ms;
/// <summary>Index of which frame this time duration occured on. A zero based frame index</summary>
public int frameIndex;
/// <summary>Number of occurrences</summary>
public int count;
/// <summary>Initialise FrameTime</summary>
/// <param name="index"> The frame index</param>
/// <param name="msTime"> The duration of the frame in milliseconds</param>
/// <param name="_count"> The number of occurrences</param>
public FrameTime(int index, float msTime, int _count)
{
frameIndex = index;
ms = msTime;
count = _count;
}
/// <summary>Initialise from another FrameTime</summary>
/// <param name="t"> The FrameTime to assign</param>
public FrameTime(FrameTime t)
{
frameIndex = t.frameIndex;
ms = t.ms;
count = t.count;
}
/// <summary>Compare the time duration between the frames. Used for sorting in ascending order</summary>
/// <param name="other"> The other FrameTime to compare </param>
/// <returns>-1 if this is smaller, 0 if the same, 1 if this is larger</returns>
public int CompareTo(FrameTime other)
{
if (ms == other.ms)
{
// secondary sort by frame index order
return frameIndex.CompareTo(other.frameIndex);
}
return ms.CompareTo(other.ms);
}
/// <summary>Compare the time duration between two FrameTimes. Used for sorting in ascending order</summary>
/// <param name="a"> The first FrameTime to compare </param>
/// <param name="b"> The second FrameTime to compare </param>
/// <returns>-1 if a is smaller, 0 if the same, 1 if a is larger</returns>
public static int CompareMs(FrameTime a, FrameTime b)
{
if (a.ms == b.ms)
{
// secondary sort by frame index order
return a.frameIndex.CompareTo(b.frameIndex);
}
return a.ms.CompareTo(b.ms);
}
/// <summary>Compare the instance count between two FrameTimes. Used for sorting in ascending order</summary>
/// <param name="a"> The first FrameTime to compare </param>
/// <param name="b"> The second FrameTime to compare </param>
/// <returns>-1 if a is smaller, 0 if the same, 1 if a is larger</returns>
public static int CompareCount(FrameTime a, FrameTime b)
{
if (a.count == b.count)
{
// secondary sort by frame index order
return a.frameIndex.CompareTo(b.frameIndex);
}
return a.count.CompareTo(b.count);
}
/// <summary>Compare the time duration between two FrameTimes. Used for sorting in descending order</summary>
/// <param name="a"> The first FrameTime to compare </param>
/// <param name="b"> The second FrameTime to compare </param>
/// <returns>-1 if a is larger, 0 if the same, 1 if a is smaller</returns>
public static int CompareMsDescending(FrameTime a, FrameTime b)
{
if (a.ms == b.ms)
{
// secondary sort by frame index order
return a.frameIndex.CompareTo(b.frameIndex);
}
return -a.ms.CompareTo(b.ms);
}
/// <summary>Compare the instance count between two FrameTimes. Used for sorting in descending order</summary>
/// <param name="a"> The first FrameTime to compare </param>
/// <param name="b"> The second FrameTime to compare </param>
/// <returns>-1 if a is larger, 0 if the same, 1 if a is smaller</returns>
public static int CompareCountDescending(FrameTime a, FrameTime b)
{
if (a.count == b.count)
{
// secondary sort by frame index order
return a.frameIndex.CompareTo(b.frameIndex);
}
return -a.count.CompareTo(b.count);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 11b7852f3babc4a938ba8e18940df7c5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 54a4b8c3a2d924a4fb740b9044db9694
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,131 @@
using UnityEngine;
namespace UnityEditor.Performance.ProfileAnalyzer
{
internal class Histogram
{
Draw2D m_2D;
Color m_ColorBarBackground;
DisplayUnits m_Units;
public void SetUnits(Units units)
{
m_Units = new DisplayUnits(units);
}
public Histogram(Draw2D draw2D, Units units)
{
m_2D = draw2D;
SetUnits(units);
m_ColorBarBackground = new Color(0.5f, 0.5f, 0.5f);
}
public Histogram(Draw2D draw2D, Units units, Color barBackground)
{
m_2D = draw2D;
SetUnits(units);
m_ColorBarBackground = barBackground;
}
public void DrawStart(float width)
{
EditorGUILayout.BeginHorizontal(GUILayout.Width(width + 10));
EditorGUILayout.BeginVertical();
}
public void DrawEnd(float width, float min, float max, float spacing)
{
EditorGUILayout.BeginHorizontal();
float halfWidth = width / 2;
GUIStyle leftAlignStyle = new GUIStyle(GUI.skin.label);
leftAlignStyle.contentOffset = new Vector2(-5, 0);
leftAlignStyle.alignment = TextAnchor.MiddleLeft;
GUIStyle rightAlignStyle = new GUIStyle(GUI.skin.label);
rightAlignStyle.contentOffset = new Vector2(-5, 0);
rightAlignStyle.alignment = TextAnchor.MiddleRight;
EditorGUILayout.LabelField(m_Units.ToString(min, false, 5, true), leftAlignStyle, GUILayout.Width(halfWidth));
EditorGUILayout.LabelField(m_Units.ToString(max, false, 5, true), rightAlignStyle, GUILayout.Width(halfWidth));
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
EditorGUILayout.EndHorizontal();
}
public void DrawBackground(float width, float height, int bucketCount, float min, float max, float spacing)
{
//bucketCount = (range == 0f) ? 1 : bucketCount;
float x = (spacing / 2);
float y = 0;
float w = ((width + spacing) / bucketCount) - spacing;
float h = height;
for (int i = 0; i < bucketCount; i++)
{
m_2D.DrawFilledBox(x, y, w, h, m_ColorBarBackground);
x += w;
x += spacing;
}
}
public void DrawData(float width, float height, int[] buckets, int totalFrameCount, float min, float max, Color barColor, float spacing)
{
float range = max - min;
//int bucketCount = (range == 0f) ? 1 : buckets.Length;
int bucketCount = buckets.Length;
float x = (spacing / 2);
float y = 0;
float w = ((width + spacing) / bucketCount) - spacing;
float h = height;
float bucketWidth = (range / bucketCount);
Rect rect = GUILayoutUtility.GetLastRect();
for (int bucketAt = 0; bucketAt < bucketCount; bucketAt++)
{
var count = buckets[bucketAt];
float barHeight = (h * count) / totalFrameCount;
if (count > 0) // Make sure we always slow a small bar if non zero
barHeight = Mathf.Max(1.0f, barHeight);
m_2D.DrawFilledBox(x, y, w, barHeight, barColor);
float bucketStart = min + (bucketAt * bucketWidth);
float bucketEnd = bucketStart + bucketWidth;
var tooltip = string.Format("{0}-{1}\n{2} {3}\n\nBar width: {4}",
m_Units.ToTooltipString(bucketStart, false),
m_Units.ToTooltipString(bucketEnd, true),
count,
count == 1 ? "frame" : "frames",
m_Units.ToTooltipString(bucketWidth, true)
);
var content = new GUIContent("", tooltip);
GUI.Label(new Rect(rect.x + x, rect.y + y, w, h), content);
x += w;
x += spacing;
}
}
public void Draw(float width, float height, int[] buckets, int totalFrameCount, float min, float max, Color barColor)
{
DrawStart(width);
float spacing = 2;
if (m_2D.DrawStart(width, height, Draw2D.Origin.BottomLeft))
{
DrawBackground(width, height, buckets.Length, min, max, spacing);
DrawData(width, height, buckets, totalFrameCount, min, max, barColor, spacing);
m_2D.DrawEnd();
}
DrawEnd(width, min, max, spacing);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dbcb296fb16194f04af7827f37fb2187
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,17 @@
using System;
using UnityEngine;
#if UNITY_2019_1_OR_NEWER
#else
namespace UnityEditor.Performance.ProfileAnalyzer
{
internal class L10n
{
internal static string Tr(string s)
{
return s;
}
}
}
#endif

View file

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4e01a136cd29c47488d4e77ff76e3f98
timeCreated: 1612946517
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,49 @@
using System;
using UnityEngine.Assertions;
namespace UnityEditor.Performance.ProfileAnalyzer
{
internal class MarkerColumnFilter
{
public enum Mode
{
TimeAndCount,
Time,
Totals,
TimeWithTotals,
CountTotals,
CountPerFrame,
Depth,
Threads,
Custom,
};
public static readonly string[] ModeNames =
{
"Time and Count",
"Time",
"Totals",
"Time With Totals",
"Count Totals",
"Count Per Frame",
"Depths",
"Threads",
"Custom",
};
public static readonly int[] ModeValues = (int[])Enum.GetValues(typeof(Mode));
public Mode mode;
public int[] visibleColumns;
public MarkerColumnFilter(Mode newMode)
{
Assert.AreEqual(ModeNames.Length, ModeValues.Length, "Number of ModeNames should match number of enum values ModeValues: You probably forgot to update one of them.");
mode = newMode;
if (mode == Mode.Custom)
mode = Mode.TimeAndCount;
visibleColumns = null;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 814ccc2d0c490cf4b871d13ab2e4b7c3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,310 @@
using System;
using System.Collections.Generic;
namespace UnityEditor.Performance.ProfileAnalyzer
{
[Serializable]
internal class MarkerData : IComparable<MarkerData>
{
public string name;
public int nameLowerCaseHash; // lower case name hash for faster comparisons
public double msTotal; // total time of this marker on a frame
public int count; // total number of marker calls in the timeline (multiple per frame)
public int countMin; // min count per frame
public int countMax; // max count per frame
public float countMean; // mean over all frames
public int countMedian; // median over all frames
public int countLowerQuartile; // over all frames
public int countUpperQuartile; // over all frames
public int lastFrame;
public int presentOnFrameCount; // number of frames containing this marker
public int firstFrameIndex;
public float msMean; // mean over all frames
public float msMedian; // median over all frames
public float msLowerQuartile; // over all frames
public float msUpperQuartile; // over all frames
public float msMin; // min total time per frame
public float msMax; // max total time per frame
public int minIndividualFrameIndex;
public int maxIndividualFrameIndex;
public float msMinIndividual; // min individual function call
public float msMaxIndividual; // max individual function call
public float msAtMedian; // time at median frame
public int medianFrameIndex; // frame this markers median value is found on
public int minFrameIndex;
public int maxFrameIndex;
public int minDepth;
public int maxDepth;
public List<string> threads;
const int bucketCount = 20;
public int[] buckets; // Each bucket contains 'number of frames' for 'sum of markers in the frame' in that range
public int[] countBuckets; // Each bucket contains 'number of frames' for 'count in the frame' in that range
public List<FrameTime> frames;
public MarkerData(string markerName)
{
buckets = new int[bucketCount];
countBuckets = new int[bucketCount];
frames = new List<FrameTime>();
threads = new List<string>();
name = markerName;
nameLowerCaseHash = markerName.ToLower().GetHashCode();
msTotal = 0.0;
count = 0;
countMin = 0;
countMax = 0;
countMean = 0f;
countMedian = 0;
countLowerQuartile = 0;
countUpperQuartile = 0;
lastFrame = -1;
presentOnFrameCount = 0;
firstFrameIndex = -1;
msMean = 0f;
msMedian = 0f;
msLowerQuartile = 0f;
msUpperQuartile = 0f;
msMin = float.MaxValue;
msMax = float.MinValue;
minIndividualFrameIndex = 0;
maxIndividualFrameIndex = 0;
msMinIndividual = float.MaxValue;
msMaxIndividual = float.MinValue;
msAtMedian = 0f;
medianFrameIndex = 0;
minFrameIndex = 0;
maxFrameIndex = 0;
minDepth = 0;
maxDepth = 0;
for (int b = 0; b < buckets.Length; b++)
{
buckets[b] = 0;
countBuckets[b] = 0;
}
}
/// <summary>Compare the time duration between the marker median times. Used for sorting in descending order</summary>
/// <param name="other"> The other MarkerData to compare </param>
/// <returns>-1 if this is larger, 0 if the same, 1 if this is smaller</returns>
public int CompareTo(MarkerData other)
{
if (msMedian == other.msMedian)
{
if (medianFrameIndex == other.medianFrameIndex)
{
// Tertiary sort by name order
return name.CompareTo(other.name);
}
// Secondary sort by frame index order
return medianFrameIndex.CompareTo(other.medianFrameIndex);
}
return -msMedian.CompareTo(other.msMedian);
}
public float GetFrameMs(int frameIndex)
{
foreach (var frameData in frames)
{
if (frameData.frameIndex == frameIndex)
return frameData.ms;
}
return 0f;
}
public void ComputeBuckets(float min, float max)
{
float first = min;
float last = max;
float range = last - first;
int maxBucketIndex = (buckets.Length - 1);
for (int bucketIndex = 0; bucketIndex < buckets.Length; bucketIndex++)
{
buckets[bucketIndex] = 0;
}
float scale = range > 0 ? buckets.Length / range : 0;
foreach (FrameTime frameTime in frames)
{
var ms = frameTime.ms;
//int frameIndex = frameTime.frameIndex;
int bucketIndex = (int)((ms - first) * scale);
if (bucketIndex < 0 || bucketIndex > maxBucketIndex)
{
// This can happen if a single marker range is longer than the frame start end (which could occur if running on a separate thread)
// It can also occur for the highest entry in the range (max-min/range) = 1
// if (ms > max) // Check for the spilling case
// Debug.Log(string.Format("Marker {0} : {1}ms exceeds range {2}-{3} on frame {4}", marker.name, ms, first, last, 1+frameIndex));
if (bucketIndex > maxBucketIndex)
bucketIndex = maxBucketIndex;
else
bucketIndex = 0;
}
buckets[bucketIndex] += 1;
}
if (range == 0)
{
// All buckets will be the same
for (int bucketIndex = 1; bucketIndex < buckets.Length; bucketIndex++)
{
buckets[bucketIndex] = buckets[0];
}
}
}
public void ComputeCountBuckets(int min, int max)
{
float first = min;
float last = max;
float range = last - first;
int maxBucketIndex = (countBuckets.Length - 1);
for (int bucketIndex = 0; bucketIndex < countBuckets.Length; bucketIndex++)
{
countBuckets[bucketIndex] = 0;
}
float scale = range > 0 ? countBuckets.Length / range : 0;
foreach (FrameTime frameTime in frames)
{
var count = frameTime.count;
int bucketIndex = (int)((count - first) * scale);
if (bucketIndex < 0 || bucketIndex > maxBucketIndex)
{
if (bucketIndex > maxBucketIndex)
bucketIndex = maxBucketIndex;
else
bucketIndex = 0;
}
countBuckets[bucketIndex] += 1;
}
if (range == 0)
{
// All buckets will be the same
for (int bucketIndex = 1; bucketIndex < countBuckets.Length; bucketIndex++)
{
countBuckets[bucketIndex] = countBuckets[0];
}
}
}
public static string GetFirstThread(MarkerData marker)
{
return marker != null ? marker.threads[0] : "";
}
public static float GetMsMax(MarkerData marker)
{
return marker != null ? marker.msMax : 0.0f;
}
public static int GetMaxFrameIndex(MarkerData marker)
{
return marker != null ? marker.maxFrameIndex : 0;
}
public static float GetMsMin(MarkerData marker)
{
return marker != null ? marker.msMin : 0.0f;
}
public static int GetMinFrameIndex(MarkerData marker)
{
return marker != null ? marker.minFrameIndex : 0;
}
public static float GetMsMedian(MarkerData marker)
{
return marker != null ? marker.msMedian : 0.0f;
}
public static int GetMedianFrameIndex(MarkerData marker)
{
return marker != null ? marker.medianFrameIndex : 0;
}
public static float GetMsUpperQuartile(MarkerData marker)
{
return marker != null ? marker.msUpperQuartile : 0.0f;
}
public static float GetMsLowerQuartile(MarkerData marker)
{
return marker != null ? marker.msLowerQuartile : 0.0f;
}
public static float GetMsMean(MarkerData marker)
{
return marker != null ? marker.msMean : 0.0f;
}
public static float GetMsMinIndividual(MarkerData marker)
{
return marker != null ? marker.msMinIndividual : 0.0f;
}
public static float GetMsMaxIndividual(MarkerData marker)
{
return marker != null ? marker.msMaxIndividual : 0.0f;
}
public static int GetPresentOnFrameCount(MarkerData marker)
{
return marker != null ? marker.presentOnFrameCount : 0;
}
public static float GetMsAtMedian(MarkerData marker)
{
return marker != null ? marker.msAtMedian : 0.0f;
}
public static int GetCountMin(MarkerData marker)
{
return marker != null ? marker.countMin : 0;
}
public static int GetCountMax(MarkerData marker)
{
return marker != null ? marker.countMax : 0;
}
public static int GetCount(MarkerData marker)
{
return marker != null ? marker.count : 0;
}
public static float GetCountMean(MarkerData marker)
{
return marker != null ? marker.countMean : 0.0f;
}
public static double GetMsTotal(MarkerData marker)
{
return marker != null ? marker.msTotal : 0.0;
}
public static int GetMinDepth(MarkerData marker)
{
return marker != null ? marker.minDepth : 0;
}
public static int GetMaxDepth(MarkerData marker)
{
return marker != null ? marker.maxDepth : 0;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7636f77e28bd3480f9e22fecae16594a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,12 @@
using System;
namespace UnityEditor.Performance.ProfileAnalyzer
{
[Serializable]
internal class MarkerPairing
{
public string name;
public int leftIndex;
public int rightIndex;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 60fdacb48a6fe46529c3c01c96f7d123
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,369 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Performance.ProfileAnalyzer
{
[Serializable]
internal class ProfileAnalysis
{
FrameSummary m_FrameSummary = new FrameSummary();
List<MarkerData> m_Markers = new List<MarkerData>();
List<ThreadData> m_Threads = new List<ThreadData>();
public ProfileAnalysis()
{
m_FrameSummary.first = 0;
m_FrameSummary.last = 0;
m_FrameSummary.count = 0;
m_FrameSummary.msTotal = 0.0;
m_FrameSummary.msMin = float.MaxValue;
m_FrameSummary.msMax = 0.0f;
m_FrameSummary.minFrameIndex = 0;
m_FrameSummary.maxFrameIndex = 0;
m_FrameSummary.maxMarkerDepth = 0;
m_FrameSummary.totalMarkers = 0;
m_FrameSummary.markerCountMax = 0;
m_FrameSummary.markerCountMaxMean = 0.0f;
for (int b = 0; b < m_FrameSummary.buckets.Length; b++)
m_FrameSummary.buckets[b] = 0;
m_Markers.Clear();
m_Threads.Clear();
}
public void SetRange(int firstFrameIndex, int lastFrameIndex)
{
m_FrameSummary.first = firstFrameIndex;
m_FrameSummary.last = lastFrameIndex;
// Ensure these are initialized to frame indices within the range
m_FrameSummary.minFrameIndex = firstFrameIndex;
// if this wasn't initialized, and all frames had 0 length, it wouldn't be set in the UpdateSummary step of the analysis and point out of range
m_FrameSummary.maxFrameIndex = firstFrameIndex;
}
public void AddMarker(MarkerData marker)
{
m_Markers.Add(marker);
}
public void AddThread(ThreadData thread)
{
m_Threads.Add(thread);
}
public void UpdateSummary(int frameIndex, float msFrame)
{
m_FrameSummary.msTotal += msFrame;
m_FrameSummary.count += 1;
if (msFrame < m_FrameSummary.msMin)
{
m_FrameSummary.msMin = msFrame;
m_FrameSummary.minFrameIndex = frameIndex;
}
if (msFrame > m_FrameSummary.msMax)
{
m_FrameSummary.msMax = msFrame;
m_FrameSummary.maxFrameIndex = frameIndex;
}
m_FrameSummary.frames.Add(new FrameTime(frameIndex, msFrame, 1));
}
FrameTime GetPercentageOffset(List<FrameTime> frames, float percent, out int outputFrameIndex)
{
int index = (int)((frames.Count - 1) * percent / 100);
outputFrameIndex = frames[index].frameIndex;
// True median is half of the sum of the middle 2 frames for an even count. However this would be a value never recorded so we avoid that.
return frames[index];
}
float GetThreadPercentageOffset(List<ThreadFrameTime> frames, float percent, out int outputFrameIndex)
{
int index = (int)((frames.Count - 1) * percent / 100);
outputFrameIndex = frames[index].frameIndex;
// True median is half of the sum of the middle 2 frames for an even count. However this would be a value never recorded so we avoid that.
return frames[index].ms;
}
public void SetupMarkers()
{
int countMax = 0;
float countMaxMean = 0.0f;
foreach (MarkerData marker in m_Markers)
{
marker.msAtMedian = 0.0f;
marker.msMin = float.MaxValue;
marker.msMax = float.MinValue;
marker.minFrameIndex = 0;
marker.maxFrameIndex = 0;
marker.countMin = int.MaxValue;
marker.countMax = int.MinValue;
foreach (FrameTime frameTime in marker.frames)
{
var ms = frameTime.ms;
int frameIndex = frameTime.frameIndex;
// Total time for marker over frame
if (ms < marker.msMin)
{
marker.msMin = ms;
marker.minFrameIndex = frameIndex;
}
if (ms > marker.msMax)
{
marker.msMax = ms;
marker.maxFrameIndex = frameIndex;
}
if (frameIndex == m_FrameSummary.medianFrameIndex)
marker.msAtMedian = ms;
var count = frameTime.count;
// count for marker over frame
if (count < marker.countMin)
{
marker.countMin = count;
}
if (count > marker.countMax)
{
marker.countMax = count;
}
}
int unusedIndex;
marker.msMean = marker.presentOnFrameCount > 0 ? (float)(marker.msTotal / marker.presentOnFrameCount) : 0f;
marker.frames.Sort(FrameTime.CompareCount);
marker.countMedian = GetPercentageOffset(marker.frames, 50, out marker.medianFrameIndex).count;
marker.countLowerQuartile = GetPercentageOffset(marker.frames, 25, out unusedIndex).count;
marker.countUpperQuartile = GetPercentageOffset(marker.frames, 75, out unusedIndex).count;
marker.countMean = marker.presentOnFrameCount > 0 ? (float)marker.count / marker.presentOnFrameCount : 0f;
marker.frames.Sort(FrameTime.CompareMs);
marker.msMedian = GetPercentageOffset(marker.frames, 50, out marker.medianFrameIndex).ms;
marker.msLowerQuartile = GetPercentageOffset(marker.frames, 25, out unusedIndex).ms;
marker.msUpperQuartile = GetPercentageOffset(marker.frames, 75, out unusedIndex).ms;
if (marker.countMax > countMax)
countMax = marker.countMax;
if (marker.countMean > countMaxMean)
countMaxMean = marker.countMean;
}
m_FrameSummary.markerCountMax = countMax;
m_FrameSummary.markerCountMaxMean = countMaxMean;
}
public void SetupMarkerBuckets()
{
foreach (MarkerData marker in m_Markers)
{
marker.ComputeBuckets(marker.msMin, marker.msMax);
marker.ComputeCountBuckets(marker.countMin, marker.countMax);
}
}
public void SetupFrameBuckets(float timeScaleMax)
{
float first = 0;
float last = timeScaleMax;
float range = last - first;
int maxBucketIndex = m_FrameSummary.buckets.Length - 1;
for (int bucketIndex = 0; bucketIndex < m_FrameSummary.buckets.Length; bucketIndex++)
{
m_FrameSummary.buckets[bucketIndex] = 0;
}
float scale = range > 0 ? m_FrameSummary.buckets.Length / range : 0;
foreach (var frameData in m_FrameSummary.frames)
{
var msFrame = frameData.ms;
//var frameIndex = frameData.frameIndex;
int bucketIndex = (int)((msFrame - first) * scale);
if (bucketIndex < 0 || bucketIndex > maxBucketIndex)
{
// It can occur for the highest entry in the range (max-min/range) = 1
// if (ms > max) // Check for the spilling case
// Debug.Log(string.Format("Frame {0}ms exceeds range {1}-{2} on frame {3}", msFrame, first, last, frameIndex));
if (bucketIndex > maxBucketIndex)
bucketIndex = maxBucketIndex;
else
bucketIndex = 0;
}
m_FrameSummary.buckets[bucketIndex] += 1;
}
if (range == 0)
{
// All buckets will be the same
for (int bucketIndex = 1; bucketIndex < m_FrameSummary.buckets.Length; bucketIndex++)
{
m_FrameSummary.buckets[bucketIndex] = m_FrameSummary.buckets[0];
}
}
}
void CalculateThreadMedians()
{
foreach (var thread in m_Threads)
{
if (thread.frames.Count > 0)
{
thread.frames.Sort();
int unusedIndex;
thread.msMin = GetThreadPercentageOffset(thread.frames, 0, out thread.minFrameIndex);
thread.msLowerQuartile = GetThreadPercentageOffset(thread.frames, 25, out unusedIndex);
thread.msMedian = GetThreadPercentageOffset(thread.frames, 50, out thread.medianFrameIndex);
thread.msUpperQuartile = GetThreadPercentageOffset(thread.frames, 75, out unusedIndex);
thread.msMax = GetThreadPercentageOffset(thread.frames, 100, out thread.maxFrameIndex);
// Put back in order of frames
thread.frames.Sort((a, b) => a.frameIndex.CompareTo(b.frameIndex));
}
else
{
thread.msMin = 0f;
thread.msLowerQuartile = 0f;
thread.msMedian = 0f;
thread.msUpperQuartile = 0f;
thread.msMax = 0f;
}
}
}
public void Finalise(float timeScaleMax, int maxMarkerDepth)
{
if (m_FrameSummary.frames.Count > 0)
{
m_FrameSummary.frames.Sort();
m_FrameSummary.msMean = (float)(m_FrameSummary.msTotal / m_FrameSummary.count);
m_FrameSummary.msMedian = GetPercentageOffset(m_FrameSummary.frames, 50, out m_FrameSummary.medianFrameIndex).ms;
int unusedIndex;
m_FrameSummary.msLowerQuartile = GetPercentageOffset(m_FrameSummary.frames, 25, out unusedIndex).ms;
m_FrameSummary.msUpperQuartile = GetPercentageOffset(m_FrameSummary.frames, 75, out unusedIndex).ms;
}
else
{
m_FrameSummary.msMean = 0f;
m_FrameSummary.msMedian = 0f;
m_FrameSummary.msLowerQuartile = 0f;
m_FrameSummary.msUpperQuartile = 0f;
// This started as float.MaxValue and won't have been updated
m_FrameSummary.msMin = 0f;
}
// No longer need the frame time list ?
//m_frameSummary.msFrame.Clear();
m_FrameSummary.maxMarkerDepth = maxMarkerDepth;
if (timeScaleMax <= 0.0f)
{
// If max frame time range not specified then use the max frame value found.
timeScaleMax = m_FrameSummary.msMax;
}
else if (timeScaleMax < m_FrameSummary.msMax)
{
Debug.Log(string.Format("Expanding timeScaleMax {0} to match max value found {1}", timeScaleMax, m_FrameSummary.msMax));
// If max frame time range too small we must expand it.
timeScaleMax = m_FrameSummary.msMax;
}
SetupMarkers();
SetupMarkerBuckets();
SetupFrameBuckets(timeScaleMax);
// Sort in median order (highest first)
m_Markers.Sort(SortByAtMedian);
CalculateThreadMedians();
}
int SortByAtMedian(MarkerData a, MarkerData b)
{
if (a.msAtMedian == b.msAtMedian)
return -a.medianFrameIndex.CompareTo(b.medianFrameIndex);
return -a.msAtMedian.CompareTo(b.msAtMedian);
}
public List<MarkerData> GetMarkers()
{
return m_Markers;
}
public List<ThreadData> GetThreads()
{
return m_Threads;
}
public ThreadData GetThreadByName(string threadNameWithIndex)
{
foreach (var thread in m_Threads)
{
if (thread.threadNameWithIndex == threadNameWithIndex)
return thread;
}
return null;
}
public FrameSummary GetFrameSummary()
{
return m_FrameSummary;
}
public MarkerData GetMarker(int index)
{
if (index < 0 || index >= m_Markers.Count)
return null;
return m_Markers[index];
}
public int GetMarkerIndexByName(string markerName)
{
if (markerName == null)
return -1;
for (int index = 0; index < m_Markers.Count; index++)
{
var marker = m_Markers[index];
if (marker.name == markerName)
{
return index;
}
}
return -1;
}
public MarkerData GetMarkerByName(string markerName)
{
if (markerName == null)
return null;
for (int index = 0; index < m_Markers.Count; index++)
{
var marker = m_Markers[index];
if (marker.name == markerName)
{
return marker;
}
}
return null;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f7cee1025e74448acbbb8bf2b82d9bfc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,414 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEditorInternal;
using System.Text.RegularExpressions;
using System;
namespace UnityEditor.Performance.ProfileAnalyzer
{
internal class ProfileAnalyzer
{
public const int kDepthAll = -1;
int m_Progress = 0;
ProfilerFrameDataIterator m_frameData;
List<string> m_threadNames = new List<string>();
ProfileAnalysis m_analysis;
public ProfileAnalyzer()
{
}
public void QuickScan()
{
var frameData = new ProfilerFrameDataIterator();
m_threadNames.Clear();
int frameIndex = 0;
int threadCount = frameData.GetThreadCount(0);
frameData.SetRoot(frameIndex, 0);
Dictionary<string, int> threadNameCount = new Dictionary<string, int>();
for (int threadIndex = 0; threadIndex < threadCount; ++threadIndex)
{
frameData.SetRoot(frameIndex, threadIndex);
var threadName = frameData.GetThreadName();
var groupName = frameData.GetGroupName();
threadName = ProfileData.GetThreadNameWithGroup(threadName, groupName);
if (!threadNameCount.ContainsKey(threadName))
threadNameCount.Add(threadName, 1);
else
threadNameCount[threadName] += 1;
string threadNameWithIndex = ProfileData.ThreadNameWithIndex(threadNameCount[threadName], threadName);
threadNameWithIndex = ProfileData.CorrectThreadName(threadNameWithIndex);
m_threadNames.Add(threadNameWithIndex);
}
frameData.Dispose();
}
public List<string> GetThreadNames()
{
return m_threadNames;
}
void CalculateFrameTimeStats(ProfileData data, out float median, out float mean, out float standardDeviation)
{
List<float> frameTimes = new List<float>();
for (int frameIndex = 0; frameIndex < data.GetFrameCount(); frameIndex++)
{
var frame = data.GetFrame(frameIndex);
float msFrame = frame.msFrame;
frameTimes.Add(msFrame);
}
frameTimes.Sort();
median = frameTimes[frameTimes.Count / 2];
double total = 0.0f;
foreach (float msFrame in frameTimes)
{
total += msFrame;
}
mean = (float)(total / (double)frameTimes.Count);
if (frameTimes.Count <= 1)
{
standardDeviation = 0f;
}
else
{
total = 0.0f;
foreach (float msFrame in frameTimes)
{
float d = msFrame - mean;
total += (d * d);
}
total /= (frameTimes.Count - 1);
standardDeviation = (float)Math.Sqrt(total);
}
}
int GetClampedOffsetToFrame(ProfileData profileData, int frameIndex)
{
int frameOffset = profileData.DisplayFrameToOffset(frameIndex);
if (frameOffset < 0)
{
Debug.Log(string.Format("Frame index {0} offset {1} < 0, clamping", frameIndex, frameOffset));
frameOffset = 0;
}
if (frameOffset >= profileData.GetFrameCount())
{
Debug.Log(string.Format("Frame index {0} offset {1} >= frame count {2}, clamping", frameIndex, frameOffset, profileData.GetFrameCount()));
frameOffset = profileData.GetFrameCount() - 1;
}
return frameOffset;
}
public static bool MatchThreadFilter(string threadNameWithIndex, List<string> threadFilters)
{
if (threadFilters == null || threadFilters.Count == 0)
return false;
if (threadFilters.Contains(threadNameWithIndex))
return true;
return false;
}
public bool IsNullOrWhiteSpace(string s)
{
// return string.IsNullOrWhiteSpace(parentMarker);
if (s == null || Regex.IsMatch(s, @"^[\s]*$"))
return true;
return false;
}
public ProfileAnalysis Analyze(ProfileData profileData, List<int> selectionIndices, List<string> threadFilters, int depthFilter, bool selfTimes = false, string parentMarker = null, float timeScaleMax = 0)
{
m_Progress = 0;
if (profileData == null)
{
return null;
}
if (profileData.GetFrameCount() <= 0)
{
return null;
}
int frameCount = selectionIndices.Count;
if (frameCount < 0)
{
return null;
}
if (profileData.HasFrames && !profileData.HasThreads)
{
if (!ProfileData.Load(profileData.FilePath, out profileData))
{
return null;
}
}
bool processMarkers = (threadFilters != null);
ProfileAnalysis analysis = new ProfileAnalysis();
if (selectionIndices.Count > 0)
analysis.SetRange(selectionIndices[0], selectionIndices[selectionIndices.Count - 1]);
else
analysis.SetRange(0, 0);
m_threadNames.Clear();
int maxMarkerDepthFound = 0;
var threads = new Dictionary<string, ThreadData>();
var markers = new Dictionary<string, MarkerData>();
var allMarkers = new Dictionary<string, int>();
bool filteringByParentMarker = false;
int parentMarkerIndex = -1;
if (!IsNullOrWhiteSpace(parentMarker))
{
// Returns -1 if this marker doesn't exist in the data set
parentMarkerIndex = profileData.GetMarkerIndex(parentMarker);
filteringByParentMarker = true;
}
int at = 0;
foreach (int frameIndex in selectionIndices)
{
int frameOffset = profileData.DisplayFrameToOffset(frameIndex);
var frameData = profileData.GetFrame(frameOffset);
if (frameData == null)
continue;
var msFrame = frameData.msFrame;
analysis.UpdateSummary(frameIndex, msFrame);
if (processMarkers)
{
// get the file reader in case we need to rebuild the markers rather than opening
// the file for every marker
for (int threadIndex = 0; threadIndex < frameData.threads.Count; threadIndex++)
{
float msTimeOfMinDepthMarkers = 0.0f;
float msIdleTimeOfMinDepthMarkers = 0.0f;
var threadData = frameData.threads[threadIndex];
var threadNameWithIndex = profileData.GetThreadName(threadData);
ThreadData thread;
if (!threads.ContainsKey(threadNameWithIndex))
{
m_threadNames.Add(threadNameWithIndex);
thread = new ThreadData(threadNameWithIndex);
analysis.AddThread(thread);
threads[threadNameWithIndex] = thread;
// Update threadsInGroup for all thread records of the same group name
foreach (var threadAt in threads.Values)
{
if (threadAt == thread)
continue;
if (thread.threadGroupName == threadAt.threadGroupName)
{
threadAt.threadsInGroup += 1;
thread.threadsInGroup += 1;
}
}
}
else
{
thread = threads[threadNameWithIndex];
}
bool include = MatchThreadFilter(threadNameWithIndex, threadFilters);
int parentMarkerDepth = -1;
if (threadData.markers.Count != threadData.markerCount)
{
if (!threadData.ReadMarkers(profileData.FilePath))
{
Debug.LogError("failed to read markers");
}
}
foreach (ProfileMarker markerData in threadData.markers)
{
string markerName = profileData.GetMarkerName(markerData);
if (!allMarkers.ContainsKey(markerName))
allMarkers.Add(markerName, 1);
// No longer counting how many times we see the marker (this saves 1/3 of the analysis time).
float ms = markerData.msMarkerTotal - (selfTimes ? markerData.msChildren : 0);
var markerDepth = markerData.depth;
if (markerDepth > maxMarkerDepthFound)
maxMarkerDepthFound = markerDepth;
if (markerDepth == 1)
{
if (markerName == "Idle")
msIdleTimeOfMinDepthMarkers += ms;
else
msTimeOfMinDepthMarkers += ms;
}
if (!include)
continue;
if (depthFilter != kDepthAll && markerDepth != depthFilter)
continue;
// If only looking for markers below the parent
if (filteringByParentMarker)
{
// If found the parent marker
if (markerData.nameIndex == parentMarkerIndex)
{
// And we are not already below the parent higher in the depth tree
if (parentMarkerDepth < 0)
{
// record the parent marker depth
parentMarkerDepth = markerData.depth;
}
}
else
{
// If we are now above or beside the parent marker then we are done for this level
if (markerData.depth <= parentMarkerDepth)
{
parentMarkerDepth = -1;
}
}
if (parentMarkerDepth < 0)
continue;
}
MarkerData marker;
if (markers.ContainsKey(markerName))
{
marker = markers[markerName];
if (!marker.threads.Contains(threadNameWithIndex))
marker.threads.Add(threadNameWithIndex);
}
else
{
marker = new MarkerData(markerName);
marker.firstFrameIndex = frameIndex;
marker.minDepth = markerDepth;
marker.maxDepth = markerDepth;
marker.threads.Add(threadNameWithIndex);
analysis.AddMarker(marker);
markers.Add(markerName, marker);
}
marker.count += 1;
marker.msTotal += ms;
// Individual marker time (not total over frame)
if (ms < marker.msMinIndividual)
{
marker.msMinIndividual = ms;
marker.minIndividualFrameIndex = frameIndex;
}
if (ms > marker.msMaxIndividual)
{
marker.msMaxIndividual = ms;
marker.maxIndividualFrameIndex = frameIndex;
}
// Record highest depth foun
if (markerDepth < marker.minDepth)
marker.minDepth = markerDepth;
if (markerDepth > marker.maxDepth)
marker.maxDepth = markerDepth;
FrameTime frameTime;
if (frameIndex != marker.lastFrame)
{
marker.presentOnFrameCount += 1;
frameTime = new FrameTime(frameIndex, ms, 1);
marker.frames.Add(frameTime);
marker.lastFrame = frameIndex;
}
else
{
frameTime = marker.frames[marker.frames.Count - 1];
frameTime = new FrameTime(frameTime.frameIndex, frameTime.ms + ms, frameTime.count + 1);
marker.frames[marker.frames.Count - 1] = frameTime;
}
}
if (include)
thread.frames.Add(new ThreadFrameTime(frameIndex, msTimeOfMinDepthMarkers, msIdleTimeOfMinDepthMarkers));
}
}
at++;
m_Progress = (100 * at) / frameCount;
}
analysis.GetFrameSummary().totalMarkers = allMarkers.Count;
analysis.Finalise(timeScaleMax, maxMarkerDepthFound);
/*
foreach (int frameIndex in selectionIndices)
{
int frameOffset = profileData.DisplayFrameToOffset(frameIndex);
var frameData = profileData.GetFrame(frameOffset);
foreach (var threadData in frameData.threads)
{
var threadNameWithIndex = profileData.GetThreadName(threadData);
if (filterThreads && threadFilter != threadNameWithIndex)
continue;
const bool enterChildren = true;
foreach (var markerData in threadData.markers)
{
var markerName = markerData.name;
var ms = markerData.msFrame;
var markerDepth = markerData.depth;
if (depthFilter != kDepthAll && markerDepth != depthFilter)
continue;
MarkerData marker = markers[markerName];
bucketIndex = (range > 0) ? (int)(((marker.buckets.Length-1) * (ms - first)) / range) : 0;
if (bucketIndex<0 || bucketIndex > (marker.buckets.Length - 1))
{
// This can happen if a single marker range is longer than the frame start end (which could occur if running on a separate thread)
// Debug.Log(string.Format("Marker {0} : {1}ms exceeds range {2}-{3} on frame {4}", marker.name, ms, first, last, frameIndex));
if (bucketIndex > (marker.buckets.Length - 1))
bucketIndex = (marker.buckets.Length - 1);
else
bucketIndex = 0;
}
marker.individualBuckets[bucketIndex] += 1;
}
}
}
*/
m_Progress = 100;
return analysis;
}
public int GetProgress()
{
return m_Progress;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 317d76fd107c444c7822b9c99d48bd30
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,255 @@
using UnityEngine;
using System.IO;
using System;
using System.Collections.Generic;
namespace UnityEditor.Performance.ProfileAnalyzer
{
internal class ProfileAnalyzerExportWindow : EditorWindow
{
internal static class Styles
{
public static readonly GUIContent markerTable = new GUIContent("Marker table", "Export data from the single view marker table");
public static readonly GUIContent singleFrameTimes = new GUIContent("Single Frame Times", "Export frame time data from the single view");
public static readonly GUIContent comparisonFrameTimes = new GUIContent("Comparison Frame Times", "Export frame time data from the comparison view");
}
ProfileAnalyzerWindow m_ProfileAnalyzerWindow;
ProfileDataView m_ProfileDataView;
ProfileDataView m_LeftDataView;
ProfileDataView m_RightDataView;
static public ProfileAnalyzerExportWindow FindOpenWindow()
{
UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(typeof(ProfileAnalyzerExportWindow));
if (windows != null && windows.Length > 0)
return windows[0] as ProfileAnalyzerExportWindow;
return null;
}
static public bool IsOpen()
{
if (FindOpenWindow() != null)
return true;
return false;
}
static public ProfileAnalyzerExportWindow Open(float screenX, float screenY, ProfileDataView profileSingleView, ProfileDataView profileLeftView, ProfileDataView profileRightView, ProfileAnalyzerWindow profileAnalyzerWindow)
{
ProfileAnalyzerExportWindow window = GetWindow<ProfileAnalyzerExportWindow>("Export");
window.minSize = new Vector2(200, 140);
window.position = new Rect(screenX, screenY, 200, 140);
window.m_ProfileAnalyzerWindow = profileAnalyzerWindow;
window.SetData(profileSingleView, profileLeftView, profileRightView);
window.Show();
return window;
}
static public void CloseAll()
{
ProfileAnalyzerExportWindow window = GetWindow<ProfileAnalyzerExportWindow>("Export");
window.Close();
}
public void SetData(ProfileDataView profileDataView, ProfileDataView leftDataView, ProfileDataView rightDataView)
{
m_ProfileDataView = profileDataView;
m_LeftDataView = leftDataView;
m_RightDataView = rightDataView;
}
void OnGUI()
{
EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true));
GUILayout.Label("Export as CSV:");
GUILayout.Label("");
GUILayout.Label("Single View");
bool enabled = GUI.enabled;
if (m_ProfileDataView == null || !m_ProfileDataView.IsDataValid())
GUI.enabled = false;
if (GUILayout.Button(Styles.markerTable))
SaveMarkerTableCSV();
GUI.enabled = enabled;
if (m_ProfileDataView == null || m_ProfileDataView.analysis == null)
GUI.enabled = false;
if (GUILayout.Button(Styles.singleFrameTimes))
SaveFrameTimesCSV();
GUI.enabled = enabled;
GUILayout.Label("Comparison View");
if (m_LeftDataView == null || !m_LeftDataView.IsDataValid() || m_RightDataView == null || !m_RightDataView.IsDataValid())
GUI.enabled = false;
if (GUILayout.Button(Styles.comparisonFrameTimes))
SaveComparisonFrameTimesCSV();
GUI.enabled = enabled;
EditorGUILayout.EndVertical();
}
void SaveMarkerTableCSV()
{
if (m_ProfileDataView.analysis == null)
return;
string path = EditorUtility.SaveFilePanel("Save marker table CSV data", "", "markerTable.csv", "csv");
if (path.Length != 0)
{
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
using (StreamWriter file = new StreamWriter(path))
{
file.Write("Name, ");
file.Write("Median Time, Min Time, Max Time, ");
file.Write("Median Frame Index, Min Frame Index, Max Frame Index, ");
file.Write("Min Depth, Max Depth, ");
file.Write("Total Time, ");
file.Write("Mean Time, Time Lower Quartile, Time Upper Quartile, ");
file.Write("Count Total, Count Median, Count Min, Count Max, ");
file.Write("Number of frames containing Marker, ");
file.Write("First Frame Index, ");
file.Write("Time Min Individual, Time Max Individual, ");
file.Write("Min Individual Frame, Max Individual Frame, ");
file.WriteLine("Time at Median Frame");
List<MarkerData> markerData = m_ProfileDataView.analysis.GetMarkers();
markerData.Sort();
foreach (MarkerData marker in markerData)
{
var markerName = marker.name;
if (markerName.IndexOf('\"') >= 0)
// replace all double quotation marks with single ones to avoid this breaking the escaping with double quotation marks
markerName = markerName.Replace('\"', '\'');
int medianFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(marker.medianFrameIndex, m_ProfileDataView);
int minFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(marker.minFrameIndex, m_ProfileDataView);
int maxFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(marker.maxFrameIndex, m_ProfileDataView);
int firstFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(marker.firstFrameIndex, m_ProfileDataView);
int minIndividualFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(marker.minIndividualFrameIndex, m_ProfileDataView);
int maxIndividualFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(marker.maxIndividualFrameIndex, m_ProfileDataView);
// "Escape" marker names in case it has commas in it.
file.Write("\"{0}\",", markerName);
file.Write("{0},{1},{2},",
marker.msMedian, marker.msMin, marker.msMax);
file.Write("{0},{1},{2},",
medianFrameIndex, minFrameIndex, maxFrameIndex);
file.Write("{0},{1},",
marker.minDepth, marker.maxDepth);
file.Write("{0},",
marker.msTotal);
file.Write("{0},{1},{2},",
marker.msMean, marker.msLowerQuartile, marker.msUpperQuartile);
file.Write("{0},{1},{2},{3},",
marker.count, marker.countMedian, marker.countMin, marker.countMax);
file.Write("{0},", marker.presentOnFrameCount);
file.Write("{0},", firstFrameIndex);
file.Write("{0},{1},",
marker.msMinIndividual, marker.msMaxIndividual);
file.Write("{0},{1},",
minIndividualFrameIndex, maxIndividualFrameIndex);
file.WriteLine("{0}", marker.msAtMedian);
}
}
ProfileAnalyzerAnalytics.SendUIButtonEvent(ProfileAnalyzerAnalytics.UIButton.ExportSingleFrames, analytic);
}
}
void SaveFrameTimesCSV()
{
if (m_ProfileDataView == null)
return;
if (!m_ProfileDataView.IsDataValid())
return;
string path = EditorUtility.SaveFilePanel("Save frame time CSV data", "", "frameTime.csv", "csv");
if (path.Length != 0)
{
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
using (StreamWriter file = new StreamWriter(path))
{
file.WriteLine("Frame Offset, Frame Index, Frame Time (ms), Time from first frame (ms)");
float maxFrames = m_ProfileDataView.data.GetFrameCount();
var frame = m_ProfileDataView.data.GetFrame(0);
// msStartTime isn't very accurate so we don't use it
double msTimePassed = 0.0;
for (int frameOffset = 0; frameOffset < maxFrames; frameOffset++)
{
frame = m_ProfileDataView.data.GetFrame(frameOffset);
int frameIndex = m_ProfileDataView.data.OffsetToDisplayFrame(frameOffset);
frameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(frameIndex, m_ProfileDataView);
float msFrame = frame.msFrame;
file.WriteLine("{0},{1},{2},{3}",
frameOffset, frameIndex, msFrame, msTimePassed);
msTimePassed += msFrame;
}
}
ProfileAnalyzerAnalytics.SendUIButtonEvent(ProfileAnalyzerAnalytics.UIButton.ExportSingleFrames, analytic);
}
}
void SaveComparisonFrameTimesCSV()
{
if (m_LeftDataView == null || m_RightDataView == null)
return;
if (!m_LeftDataView.IsDataValid() || !m_RightDataView.IsDataValid())
return;
string path = EditorUtility.SaveFilePanel("Save comparison frame time CSV data", "", "frameTimeComparison.csv", "csv");
if (path.Length != 0)
{
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
using (StreamWriter file = new StreamWriter(path))
{
file.Write("Frame Offset, ");
file.Write("Left Frame Index, Right Frame Index, ");
file.Write("Left Frame Time (ms), Left time from first frame (ms), ");
file.Write("Right Frame Time (ms), Right time from first frame (ms), ");
file.WriteLine("Frame Time Diff (ms)");
float maxFrames = Math.Max(m_LeftDataView.data.GetFrameCount(), m_RightDataView.data.GetFrameCount());
var leftFrame = m_LeftDataView.data.GetFrame(0);
var rightFrame = m_RightDataView.data.GetFrame(0);
// msStartTime isn't very accurate so we don't use it
double msTimePassedLeft = 0.0;
double msTimePassedRight = 0.0;
for (int frameOffset = 0; frameOffset < maxFrames; frameOffset++)
{
leftFrame = m_LeftDataView.data.GetFrame(frameOffset);
rightFrame = m_RightDataView.data.GetFrame(frameOffset);
int leftFrameIndex = m_LeftDataView.data.OffsetToDisplayFrame(frameOffset);
leftFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(leftFrameIndex, m_LeftDataView);
int rightFrameIndex = m_RightDataView.data.OffsetToDisplayFrame(frameOffset);
rightFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(rightFrameIndex, m_RightDataView);
float msFrameLeft = leftFrame != null ? leftFrame.msFrame : 0;
float msFrameRight = rightFrame != null ? rightFrame.msFrame : 0;
float msFrameDiff = msFrameRight - msFrameLeft;
file.Write("{0},", frameOffset);
file.Write("{0},{1},", leftFrameIndex, rightFrameIndex);
file.Write("{0},{1},", msFrameLeft, msTimePassedLeft);
file.Write("{0},{1},", msFrameRight, msTimePassedRight);
file.WriteLine("{0}", msFrameDiff);
msTimePassedLeft += msFrameLeft;
msTimePassedRight += msFrameRight;
}
}
ProfileAnalyzerAnalytics.SendUIButtonEvent(ProfileAnalyzerAnalytics.UIButton.ExportComparisonFrames, analytic);
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ccd88eea7c8284ac18aff2e257aeb84a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,106 @@
Shader "Unlit/ProfileAnalyzerShader"
{
Properties
{
_StencilComp ("Stencil Comparison", Float) = 8
_Stencil ("Stencil ID", Float) = 0
_StencilOp ("Stencil Operation", Float) = 0
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_StencilReadMask ("Stencil Read Mask", Float) = 255
_ColorMask ("Color Mask", Float) = 15
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
_ClipRect ("Clip Rect", vector) = (-32767, -32767, 32767, 32767)
}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}
Stencil
{
Ref [_Stencil]
Comp [_StencilComp]
Pass [_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}
//Tags { "RenderType"="Transparent" }
LOD 100
//ZWrite Off
//Blend SrcAlpha OneMinusSrcAlpha
Cull Off
Lighting Off
ZWrite Off
ZTest [unity_GUIZTestMode]
Blend SrcAlpha OneMinusSrcAlpha
ColorMask [_ColorMask]
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityUI.cginc"
#pragma multi_compile _ UNITY_UI_ALPHACLIP
struct appdata
{
float4 vertex : POSITION;
fixed4 color : COLOR;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float4 worldPosition : TEXCOORD1;
};
bool _UseClipRect;
float4 _ClipRect;
v2f vert (appdata v)
{
v2f o;
o.worldPosition = v.vertex;
o.vertex = UnityObjectToClipPos(v.vertex);
o.color.rgba = v.color;
return o;
}
//fixed4 frag (v2f i) : SV_Target { return i.color; }
fixed4 frag (v2f i) : SV_Target
{
half4 color = i.color;
if (_UseClipRect)
color.a *= UnityGet2DClipping(i.worldPosition.xy, _ClipRect);
#ifdef UNITY_UI_ALPHACLIP
clip (color.a - 0.001);
#endif
return color;
}
ENDCG
}
}
}

View file

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 493b79ea9c9be44ed8b702014d3ac49c
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0dc59854f72ef4a1fb6dbb8d0cd6aea3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,760 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEditorInternal;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text.RegularExpressions;
namespace UnityEditor.Performance.ProfileAnalyzer
{
[Serializable]
internal class ProfileData
{
static int latestVersion = 7;
/*
Version 1 - Initial version. Thread names index:threadName (Some invalid thread names count:threadName index)
Version 2 - Added frame start time.
Version 3 - Saved out marker children times in the data (Never needed so rapidly skipped)
Version 4 - Removed the child times again (at this point data was saved with 1 less frame at start and end)
Version 5 - Updated the thread names to include the thread group as a prefix (index:threadGroup.threadName, index is 1 based, original is 0 based)
Version 6 - fixed msStartTime (previously was 'seconds')
Version 7 - Data now only skips the frame at the end
*/
static Regex trailingDigit = new Regex(@"^(.*[^\s])[\s]+([\d]+)$", RegexOptions.Compiled);
public int Version { get; private set; }
public int FrameIndexOffset { get; private set; }
public bool FirstFrameIncomplete;
public bool LastFrameIncomplete;
List<ProfileFrame> frames = new List<ProfileFrame>();
List<string> markerNames = new List<string>();
List<string> threadNames = new List<string>();
Dictionary<string, int> markerNamesDict = new Dictionary<string, int>();
Dictionary<string, int> threadNameDict = new Dictionary<string, int>();
public string FilePath { get; private set; }
static float s_Progress = 0;
public ProfileData()
{
FrameIndexOffset = 0;
FilePath = string.Empty;
Version = latestVersion;
}
public ProfileData(string filename)
{
FrameIndexOffset = 0;
FilePath = filename;
Version = latestVersion;
}
void Read()
{
if (string.IsNullOrEmpty(FilePath))
throw new Exception("File path is invalid");
using (var reader = new BinaryReader(File.Open(FilePath, FileMode.Open)))
{
s_Progress = 0;
Version = reader.ReadInt32();
if (Version < 0 || Version > latestVersion)
{
throw new Exception(String.Format("File version unsupported: {0} != {1} expected, at path: {2}", Version, latestVersion, FilePath));
}
FrameIndexOffset = reader.ReadInt32();
int frameCount = reader.ReadInt32();
frames.Clear();
for (int frame = 0; frame < frameCount; frame++)
{
frames.Add(new ProfileFrame(reader, Version));
s_Progress = (float)frame / frameCount;
}
int markerCount = reader.ReadInt32();
markerNames.Clear();
for (int marker = 0; marker < markerCount; marker++)
{
markerNames.Add(reader.ReadString());
s_Progress = (float)marker / markerCount;
}
int threadCount = reader.ReadInt32();
threadNames.Clear();
for (int thread = 0; thread < threadCount; thread++)
{
var threadNameWithIndex = reader.ReadString();
threadNameWithIndex = CorrectThreadName(threadNameWithIndex);
threadNames.Add(threadNameWithIndex);
s_Progress = (float)thread / threadCount;
}
}
}
internal void DeleteTmpFiles()
{
if (ProfileAnalyzerWindow.FileInTempDir(FilePath))
File.Delete(FilePath);
}
bool IsFrameSame(int frameIndex, ProfileData other)
{
ProfileFrame thisFrame = GetFrame(frameIndex);
ProfileFrame otherFrame = other.GetFrame(frameIndex);
return thisFrame.IsSame(otherFrame);
}
public bool IsSame(ProfileData other)
{
if (other == null)
return false;
int frameCount = GetFrameCount();
if (frameCount != other.GetFrameCount())
{
// Frame counts differ
return false;
}
if (frameCount == 0)
{
// Both empty
return true;
}
if (!IsFrameSame(0, other))
return false;
if (!IsFrameSame(frameCount - 1, other))
return false;
// Close enough if same number of frames and first/last have exactly the same frame time and time offset.
// If we see false matches we could add a full has of the data on load/pull
return true;
}
static public string ThreadNameWithIndex(int index, string threadName)
{
return string.Format("{0}:{1}", index, threadName);
}
public void SetFrameIndexOffset(int offset)
{
FrameIndexOffset = offset;
}
public int GetFrameCount()
{
return frames.Count;
}
public ProfileFrame GetFrame(int offset)
{
if (offset < 0 || offset >= frames.Count)
return null;
return frames[offset];
}
public List<string> GetMarkerNames()
{
return markerNames;
}
public List<string> GetThreadNames()
{
return threadNames;
}
public int GetThreadCount()
{
return threadNames.Count;
}
public int OffsetToDisplayFrame(int offset)
{
return offset + (1 + FrameIndexOffset);
}
public int DisplayFrameToOffset(int displayFrame)
{
return displayFrame - (1 + FrameIndexOffset);
}
public void AddThreadName(string threadName, ProfileThread thread)
{
threadName = CorrectThreadName(threadName);
int index = -1;
if (!threadNameDict.TryGetValue(threadName, out index))
{
threadNames.Add(threadName);
index = threadNames.Count - 1;
threadNameDict.Add(threadName, index);
}
thread.threadIndex = index;
}
public void AddMarkerName(string markerName, ProfileMarker marker)
{
int index = -1;
if (!markerNamesDict.TryGetValue(markerName, out index))
{
markerNames.Add(markerName);
index = markerNames.Count - 1;
markerNamesDict.Add(markerName, index);
}
marker.nameIndex = index;
}
public string GetThreadName(ProfileThread thread)
{
return threadNames[thread.threadIndex];
}
public string GetMarkerName(ProfileMarker marker)
{
return markerNames[marker.nameIndex];
}
public int GetMarkerIndex(string markerName)
{
for (int nameIndex = 0; nameIndex < markerNames.Count; ++nameIndex)
{
if (markerName == markerNames[nameIndex])
return nameIndex;
}
return -1;
}
public void Add(ProfileFrame frame)
{
frames.Add(frame);
}
void WriteInternal(string filepath)
{
using (var writer = new BinaryWriter(File.Open(filepath, FileMode.OpenOrCreate)))
{
Version = latestVersion;
writer.Write(Version);
writer.Write(FrameIndexOffset);
writer.Write(frames.Count);
foreach (var frame in frames)
{
frame.Write(writer);
}
writer.Write(markerNames.Count);
foreach (var markerName in markerNames)
{
writer.Write(markerName);
}
writer.Write(threadNames.Count);
foreach (var threadName in threadNames)
{
writer.Write(threadName);
}
}
}
internal void Write()
{
//ensure that we can always write to the temp location at least
if (string.IsNullOrEmpty(FilePath))
FilePath = ProfileAnalyzerWindow.TmpPath;
WriteInternal(FilePath);
}
internal void WriteTo(string path)
{
//no point in trying to save on top of ourselves
if (path == FilePath)
return;
if (!string.IsNullOrEmpty(FilePath) && File.Exists(FilePath))
{
if (File.Exists(path))
File.Delete(path);
File.Copy(FilePath, path);
}
else
{
WriteInternal(path);
}
FilePath = path;
}
public static string CorrectThreadName(string threadNameWithIndex)
{
var info = threadNameWithIndex.Split(':');
if (info.Length >= 2)
{
string threadGroupIndexString = info[0];
string threadName = info[1];
if (threadName.Trim() == "")
{
// Scan seen with no thread name
threadNameWithIndex = string.Format("{0}:[Unknown]", threadGroupIndexString);
}
else
{
// Some scans have thread names such as
// "1:Worker Thread 0"
// "1:Worker Thread 1"
// rather than
// "1:Worker Thread"
// "2:Worker Thread"
// Update to the second format so the 'All' case is correctly determined
Match m = trailingDigit.Match(threadName);
if (m.Success)
{
string threadNamePrefix = m.Groups[1].Value;
int threadGroupIndex = 1 + int.Parse(m.Groups[2].Value);
threadNameWithIndex = string.Format("{0}:{1}", threadGroupIndex, threadNamePrefix);
}
}
}
threadNameWithIndex = threadNameWithIndex.Trim();
return threadNameWithIndex;
}
public static string GetThreadNameWithGroup(string threadName, string groupName)
{
if (string.IsNullOrEmpty(groupName))
return threadName;
return string.Format("{0}.{1}", groupName, threadName);
}
public static string GetThreadNameWithoutGroup(string threadNameWithGroup, out string groupName)
{
string[] tokens = threadNameWithGroup.Split('.');
if (tokens.Length <= 1)
{
groupName = "";
return tokens[0];
}
groupName = tokens[0];
return tokens[1].TrimStart();
}
internal bool HasFrames
{
get
{
return frames != null && frames.Count > 0;
}
}
internal bool HasThreads
{
get
{
return frames[0].threads != null && frames[0].threads.Count > 0;
}
}
internal bool NeedsMarkerRebuild
{
get
{
if (frames.Count > 0 && frames[0].threads.Count > 0)
return frames[0].threads[0].markers.Count != frames[0].threads[0].markerCount;
return false;
}
}
public static bool Save(string filename, ProfileData data)
{
if (data == null)
return false;
if (string.IsNullOrEmpty(filename))
return false;
if (filename.EndsWith(".json"))
{
var json = JsonUtility.ToJson(data);
File.WriteAllText(filename, json);
}
else if (filename.EndsWith(".padata"))
{
FileStream stream = File.Create(filename);
var formatter = new BinaryFormatter();
formatter.Serialize(stream, data);
stream.Close();
}
else if (filename.EndsWith(".pdata"))
{
data.WriteTo(filename);
}
return true;
}
public static bool Load(string filename, out ProfileData data)
{
if (filename.EndsWith(".json"))
{
string json = File.ReadAllText(filename);
data = JsonUtility.FromJson<ProfileData>(json);
}
else if (filename.EndsWith(".padata"))
{
FileStream stream = File.OpenRead(filename);
var formatter = new BinaryFormatter();
data = (ProfileData)formatter.Deserialize(stream);
stream.Close();
if (data.Version != latestVersion)
{
Debug.Log(String.Format("Unable to load file. Incorrect file version in {0} : (file {1} != {2} expected", filename, data.Version, latestVersion));
data = null;
return false;
}
}
else if (filename.EndsWith(".pdata"))
{
if (!File.Exists(filename))
{
data = null;
return false;
}
try
{
data = new ProfileData(filename);
data.Read();
}
catch (Exception e)
{
var message = e.Message;
if (!string.IsNullOrEmpty(message))
Debug.Log(e.Message);
data = null;
return false;
}
}
else
{
string errorMessage;
if (filename.EndsWith(".data"))
{
errorMessage = "Unable to load file. Profiler captures (.data) should be loaded in the Profiler Window and then pulled into the Analyzer via its Pull Data button.";
}
else
{
errorMessage = string.Format("Unable to load file. Unsupported file format: '{0}'.", Path.GetExtension(filename));
}
Debug.Log(errorMessage);
data = null;
return false;
}
data.Finalise();
return true;
}
void PushMarker(Stack<ProfileMarker> markerStack, ProfileMarker markerData)
{
Debug.Assert(markerData.depth == markerStack.Count + 1);
markerStack.Push(markerData);
}
ProfileMarker PopMarkerAndRecordTimeInParent(Stack<ProfileMarker> markerStack)
{
ProfileMarker child = markerStack.Pop();
ProfileMarker parentMarker = (markerStack.Count > 0) ? markerStack.Peek() : null;
// Record the last markers time in its parent
if (parentMarker != null)
parentMarker.msChildren += child.msMarkerTotal;
return parentMarker;
}
public void Finalise()
{
CalculateMarkerChildTimes();
markerNamesDict.Clear();
}
void CalculateMarkerChildTimes()
{
var markerStack = new Stack<ProfileMarker>();
for (int frameOffset = 0; frameOffset <= frames.Count; ++frameOffset)
{
var frameData = GetFrame(frameOffset);
if (frameData == null)
continue;
for (int threadIndex = 0; threadIndex < frameData.threads.Count; threadIndex++)
{
var threadData = frameData.threads[threadIndex];
// The markers are in depth first order and the depth is known
// So we can infer a parent child relationship
// Zero them first
foreach (ProfileMarker markerData in threadData.markers)
{
markerData.msChildren = 0.0f;
}
// Update the child times
markerStack.Clear();
foreach (ProfileMarker markerData in threadData.markers)
{
int depth = markerData.depth;
// Update depth stack and record child times in the parent
if (depth >= markerStack.Count)
{
// If at same level then remove the last item at this level
if (depth == markerStack.Count)
{
PopMarkerAndRecordTimeInParent(markerStack);
}
// Assume we can't move down depth without markers between levels.
}
else if (depth < markerStack.Count)
{
// We can move up depth several layers so need to pop off all those markers
while (markerStack.Count >= depth)
{
PopMarkerAndRecordTimeInParent(markerStack);
}
}
PushMarker(markerStack, markerData);
}
}
}
}
public static float GetLoadingProgress()
{
return s_Progress;
}
}
[Serializable]
internal class ProfileFrame
{
public List<ProfileThread> threads = new List<ProfileThread>();
public double msStartTime;
public float msFrame;
public ProfileFrame()
{
msStartTime = 0.0;
msFrame = 0f;
}
public bool IsSame(ProfileFrame otherFrame)
{
if (msStartTime != otherFrame.msStartTime)
return false;
if (msFrame != otherFrame.msFrame)
return false;
if (threads.Count != otherFrame.threads.Count)
return false;
// Close enough.
return true;
}
public void Add(ProfileThread thread)
{
threads.Add(thread);
}
public void Write(BinaryWriter writer)
{
writer.Write(msStartTime);
writer.Write(msFrame);
writer.Write(threads.Count);
foreach (var thread in threads)
{
thread.Write(writer);
}
;
}
public ProfileFrame(BinaryReader reader, int fileVersion)
{
if (fileVersion > 1)
{
if (fileVersion >= 6)
{
msStartTime = reader.ReadDouble();
}
else
{
double sStartTime = reader.ReadDouble();
msStartTime = sStartTime * 1000.0;
}
}
msFrame = reader.ReadSingle();
int threadCount = reader.ReadInt32();
threads.Clear();
for (int thread = 0; thread < threadCount; thread++)
{
threads.Add(new ProfileThread(reader, fileVersion));
}
}
}
[Serializable]
internal class ProfileThread
{
[NonSerialized]
public List<ProfileMarker> markers = new List<ProfileMarker>();
public int threadIndex;
public long streamPos;
public int markerCount = 0;
public int fileVersion;
public ProfileThread()
{
}
public void Write(BinaryWriter writer)
{
writer.Write(threadIndex);
writer.Write(markers.Count);
foreach (var marker in markers)
{
marker.Write(writer);
}
;
}
public ProfileThread(BinaryReader reader, int fileversion)
{
streamPos = reader.BaseStream.Position;
fileVersion = fileversion;
threadIndex = reader.ReadInt32();
markerCount = reader.ReadInt32();
markers.Clear();
for (int marker = 0; marker < markerCount; marker++)
{
markers.Add(new ProfileMarker(reader, fileVersion));
}
}
public bool ReadMarkers(string path)
{
if (streamPos == 0)
return false; // the stream positions havent been written yet.
var stream = File.OpenRead(path);
BinaryReader br = new BinaryReader(stream);
br.BaseStream.Position = streamPos;
threadIndex = br.ReadInt32();
markerCount = br.ReadInt32();
markers.Clear();
for (int marker = 0; marker < markerCount; marker++)
{
markers.Add(new ProfileMarker(br, fileVersion));
}
br.Close();
return true;
}
public void AddMarker(ProfileMarker markerData)
{
markers.Add(markerData);
markerCount++;
}
public void RebuildMarkers(string path)
{
if (!File.Exists(path)) return;
FileStream stream = File.OpenRead(path);
using (var reader = new BinaryReader(stream))
{
reader.BaseStream.Position = streamPos;
threadIndex = reader.ReadInt32();
markerCount = reader.ReadInt32();
markers.Clear();
for (int marker = 0; marker < markerCount; marker++)
{
markers.Add(new ProfileMarker(reader, fileVersion));
}
}
}
}
[Serializable]
internal class ProfileMarker
{
public int nameIndex;
public float msMarkerTotal;
public int depth;
[NonSerialized]
public float msChildren; // Recalculated on load so not saved in file
public ProfileMarker()
{
}
public static ProfileMarker Create(float durationMS, int depth)
{
var item = new ProfileMarker
{
msMarkerTotal = durationMS,
depth = depth,
msChildren = 0.0f
};
return item;
}
public static ProfileMarker Create(ProfilerFrameDataIterator frameData)
{
return Create(frameData.durationMS, frameData.depth);
}
public void Write(BinaryWriter writer)
{
writer.Write(nameIndex);
writer.Write(msMarkerTotal);
writer.Write(depth);
}
public ProfileMarker(BinaryReader reader, int fileVersion)
{
nameIndex = reader.ReadInt32();
msMarkerTotal = reader.ReadSingle();
depth = reader.ReadInt32();
if (fileVersion == 3) // In this version we saved the msChildren value but we don't need to as we now recalculate on load
msChildren = reader.ReadSingle();
else
msChildren = 0.0f;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5cea2082789f54c25b65fd1fef416863
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,161 @@
using System;
using System.Collections.Generic;
namespace UnityEditor.Performance.ProfileAnalyzer
{
[Serializable]
internal class ProfileDataView
{
public string path;
public ProfileData data;
public ProfileAnalysis analysisFullNew;
public ProfileAnalysis analysisFull;
public ProfileAnalysis analysisNew;
public ProfileAnalysis analysis;
public List<int> selectedIndices = new List<int> { 0, 0 };
[NonSerialized]
public bool inSyncWithProfilerData;
public ProfileDataView()
{
}
public ProfileDataView(ProfileDataView dataView)
{
path = dataView.path;
data = dataView.data;
analysisFullNew = dataView.analysisFullNew;
analysisFull = dataView.analysisFull;
analysisNew = dataView.analysisNew;
analysis = dataView.analysis;
selectedIndices = new List<int>(dataView.selectedIndices);
inSyncWithProfilerData = dataView.inSyncWithProfilerData;
}
public bool IsDataValid()
{
if (data == null)
return false;
if (data.GetFrameCount() == 0)
return false;
if (data.NeedsMarkerRebuild)
{
if (!ProfileData.Load(data.FilePath, out data))
{
return false;
}
}
return true;
}
public bool HasValidSelection()
{
if (selectedIndices.Count == 2 && selectedIndices[0] == 0 && selectedIndices[1] == 0)
return false;
return true;
}
public bool HasSelection()
{
if (selectedIndices.Count == 0)
return false;
if (selectedIndices.Count == data.GetFrameCount())
return false;
return HasValidSelection();
}
public bool AllSelected()
{
if (selectedIndices.Count != data.GetFrameCount())
return false;
return true;
}
public int GetMaxDepth()
{
return (analysis == null) ? 1 : analysis.GetFrameSummary().maxMarkerDepth;
}
int Clamp(int value, int min, int max)
{
if (value < min)
value = min;
else if (value > max)
value = max;
return value;
}
public int ClampToValidDepthValue(int depthFilter)
{
// ProfileAnalyzer.kDepthAll is special case that we don't test for here
// If we have no depth values then return -1 for all (as clamp expects min<max)
int maxDepth = GetMaxDepth();
if (maxDepth < 1)
return ProfileAnalyzer.kDepthAll;
return Clamp(depthFilter, 1, maxDepth);
}
bool SelectAllFramesContainingMarker(string markerName, ProfileAnalysis inAnalysis)
{
if (inAnalysis == null)
return false;
selectedIndices.Clear();
MarkerData markerData = inAnalysis.GetMarkerByName(markerName);
if (markerData == null)
return true;
foreach (var frameTime in markerData.frames)
{
selectedIndices.Add(frameTime.frameIndex);
}
// Order from lowest to highest so the start/end frame display makes sense
selectedIndices.Sort();
return true;
}
public bool SelectAllFramesContainingMarker(string markerName, bool inSelection)
{
return SelectAllFramesContainingMarker(markerName, inSelection ? analysis : analysisFull);
}
int ClampToRange(int value, int min, int max)
{
if (value < min)
value = min;
if (value > max)
value = max;
return value;
}
public void ClearSelection()
{
selectedIndices.Clear();
}
public void SelectFullRange()
{
selectedIndices.Clear();
if (data == null)
return;
for (int offset = 0; offset < data.GetFrameCount(); offset++)
{
selectedIndices.Add(data.OffsetToDisplayFrame(offset));
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 59e8a7346ec034a4387b6ca1ab20b83e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,868 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityEngine.Assertions;
namespace UnityEditor.Performance.ProfileAnalyzer
{
class ProfileTreeViewItem : TreeViewItem
{
public MarkerData data { get; set; }
public GUIContent[] cachedRowString;
public ProfileTreeViewItem(int id, int depth, string displayName, MarkerData data) : base(id, depth, displayName)
{
this.data = data;
cachedRowString = null;
}
}
class ProfileTable : TreeView
{
Draw2D m_2D;
ProfileDataView m_DataView;
ProfileAnalyzerWindow m_ProfileAnalyzerWindow;
Color m_BarColor;
float m_MaxMedian;
int m_MaxCount;
float m_MaxCountMean;
double m_MaxTotal;
const float kRowHeights = 20f;
readonly List<TreeViewItem> m_Rows = new List<TreeViewItem>(100);
// All columns
public enum MyColumns
{
Name,
Depth,
Median,
MedianBar,
Mean,
Min,
Max,
Range,
Count,
CountBar,
CountMean,
CountMeanBar,
FirstFrame,
AtMedian,
Total,
TotalBar,
Threads,
}
static int m_MaxColumns;
public enum SortOption
{
Name,
Depth,
Median,
Mean,
Min,
Max,
Range,
Count,
CountMean,
FirstFrame,
AtMedian,
Total,
Threads,
}
// Sort options per column
SortOption[] m_SortOptions =
{
SortOption.Name,
SortOption.Depth,
SortOption.Median,
SortOption.Median,
SortOption.Mean,
SortOption.Min,
SortOption.Max,
SortOption.Range,
SortOption.Count,
SortOption.Count,
SortOption.CountMean,
SortOption.CountMean,
SortOption.FirstFrame,
SortOption.AtMedian,
SortOption.Total,
SortOption.Total,
SortOption.Threads,
};
internal static class Styles
{
public static readonly GUIContent menuItemSelectFramesInAll = new GUIContent("Select Frames that contain this marker (within whole data set)", "");
public static readonly GUIContent menuItemSelectFramesInCurrent = new GUIContent("Select Frames that contain this marker (within current selection)", "");
//public static readonly GUIContent menuItemClearSelection = new GUIContent("Clear Selection");
public static readonly GUIContent menuItemSelectFramesAll = new GUIContent("Select All");
public static readonly GUIContent menuItemAddToIncludeFilter = new GUIContent("Add to Include Filter", "");
public static readonly GUIContent menuItemAddToExcludeFilter = new GUIContent("Add to Exclude Filter", "");
public static readonly GUIContent menuItemRemoveFromIncludeFilter = new GUIContent("Remove from Include Filter", "");
public static readonly GUIContent menuItemRemoveFromExcludeFilter = new GUIContent("Remove from Exclude Filter", "");
public static readonly GUIContent menuItemSetAsParentMarkerFilter = new GUIContent("Set as Parent Marker Filter", "");
public static readonly GUIContent menuItemClearParentMarkerFilter = new GUIContent("Clear Parent Marker Filter", "");
public static readonly GUIContent menuItemCopyToClipboard = new GUIContent("Copy to Clipboard", "");
}
public ProfileTable(TreeViewState state, MultiColumnHeader multicolumnHeader, ProfileDataView dataView, ProfileAnalyzerWindow profileAnalyzerWindow, Draw2D draw2D, Color barColor) : base(state, multicolumnHeader)
{
m_2D = draw2D;
m_DataView = dataView;
m_ProfileAnalyzerWindow = profileAnalyzerWindow;
m_BarColor = barColor;
m_MaxColumns = Enum.GetValues(typeof(MyColumns)).Length;
Assert.AreEqual(m_SortOptions.Length, m_MaxColumns, "Ensure number of sort options are in sync with number of MyColumns enum values");
// Custom setup
rowHeight = kRowHeights;
showAlternatingRowBackgrounds = true;
showBorder = true;
customFoldoutYOffset = (kRowHeights - EditorGUIUtility.singleLineHeight) * 0.5f; // center foldout in the row since we also center content. See RowGUI
// extraSpaceBeforeIconAndLabel = 0;
multicolumnHeader.sortingChanged += OnSortingChanged;
multicolumnHeader.visibleColumnsChanged += OnVisibleColumnsChanged;
Reload();
}
protected override TreeViewItem BuildRoot()
{
int idForhiddenRoot = -1;
int depthForHiddenRoot = -1;
ProfileTreeViewItem root = new ProfileTreeViewItem(idForhiddenRoot, depthForHiddenRoot, "root", null);
List<string> nameFilters = m_ProfileAnalyzerWindow.GetNameFilters();
List<string> nameExcludes = m_ProfileAnalyzerWindow.GetNameExcludes();
m_MaxMedian = 0.0f;
m_MaxTotal = 0.0;
m_MaxCount = 0;
m_MaxCountMean = 0.0f;
var markers = m_DataView.analysis.GetMarkers();
for (int index = 0; index < markers.Count; ++index)
{
var marker = markers[index];
if (nameFilters.Count > 0)
{
if (!m_ProfileAnalyzerWindow.NameInFilterList(marker.name, nameFilters))
continue;
}
if (nameExcludes.Count > 0)
{
if (m_ProfileAnalyzerWindow.NameInExcludeList(marker.name, nameExcludes))
continue;
}
var item = new ProfileTreeViewItem(index, 0, marker.name, marker);
root.AddChild(item);
float ms = item.data.msMedian;
if (ms > m_MaxMedian)
m_MaxMedian = ms;
double msTotal = item.data.msTotal;
if (msTotal > m_MaxTotal)
m_MaxTotal = msTotal;
int count = item.data.count;
if (count > m_MaxCount)
m_MaxCount = count;
float countMean = item.data.countMean;
if (countMean > m_MaxCountMean)
m_MaxCountMean = countMean;
}
return root;
}
protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
{
m_Rows.Clear();
if (rootItem != null && rootItem.children != null)
{
foreach (ProfileTreeViewItem node in rootItem.children)
{
m_Rows.Add(node);
}
}
SortIfNeeded(m_Rows);
return m_Rows;
}
void OnSortingChanged(MultiColumnHeader _multiColumnHeader)
{
SortIfNeeded(GetRows());
}
protected virtual void OnVisibleColumnsChanged(MultiColumnHeader multiColumnHeader)
{
m_ProfileAnalyzerWindow.SetSingleModeColumns(multiColumnHeader.state.visibleColumns);
multiColumnHeader.ResizeToFit();
}
void SortIfNeeded(IList<TreeViewItem> rows)
{
if (rows.Count <= 1)
{
return;
}
if (multiColumnHeader.sortedColumnIndex == -1)
{
return; // No column to sort for (just use the order the data are in)
}
// Sort the roots of the existing tree items
SortByMultipleColumns();
// Update the data with the sorted content
rows.Clear();
foreach (ProfileTreeViewItem node in rootItem.children)
{
rows.Add(node);
}
Repaint();
}
string GetThreadName(ProfileTreeViewItem item)
{
return m_ProfileAnalyzerWindow.GetUIThreadName(item.data.threads[0]);
}
string GetThreadNames(ProfileTreeViewItem item)
{
var uiNames = new List<string>();
foreach (string threadNameWithIndex in item.data.threads)
{
string uiName = m_ProfileAnalyzerWindow.GetUIThreadName(threadNameWithIndex);
uiNames.Add(uiName);
}
uiNames.Sort(m_ProfileAnalyzerWindow.CompareUINames);
System.Text.StringBuilder sb = new System.Text.StringBuilder();
bool first = true;
foreach (var uiName in uiNames)
{
if (first)
first = false;
else
sb.Append(", ");
sb.Append(uiName);
}
return sb.ToString();
}
void SortByMultipleColumns()
{
var sortedColumns = multiColumnHeader.state.sortedColumns;
if (sortedColumns.Length == 0)
{
return;
}
var myTypes = rootItem.children.Cast<ProfileTreeViewItem>();
var orderedQuery = InitialOrder(myTypes, sortedColumns);
for (int i = 1; i < sortedColumns.Length; i++)
{
SortOption sortOption = m_SortOptions[sortedColumns[i]];
bool ascending = multiColumnHeader.IsSortedAscending(sortedColumns[i]);
switch (sortOption)
{
case SortOption.Name:
orderedQuery = orderedQuery.ThenBy(l => l.data.name, ascending);
break;
case SortOption.Depth:
orderedQuery = orderedQuery.ThenBy(l => l.data.minDepth, ascending);
break;
case SortOption.Mean:
orderedQuery = orderedQuery.ThenBy(l => l.data.msMean, ascending);
break;
case SortOption.Median:
orderedQuery = orderedQuery.ThenBy(l => l.data.msMedian, ascending);
break;
case SortOption.Min:
orderedQuery = orderedQuery.ThenBy(l => l.data.msMin, ascending);
break;
case SortOption.Max:
orderedQuery = orderedQuery.ThenBy(l => l.data.msMax, ascending);
break;
case SortOption.Range:
orderedQuery = orderedQuery.ThenBy(l => (l.data.msMax - l.data.msMin), ascending);
break;
case SortOption.Count:
orderedQuery = orderedQuery.ThenBy(l => l.data.count, ascending);
break;
case SortOption.CountMean:
orderedQuery = orderedQuery.ThenBy(l => l.data.countMean, ascending);
break;
case SortOption.FirstFrame:
orderedQuery = orderedQuery.ThenBy(l => l.data.firstFrameIndex, ascending);
break;
case SortOption.AtMedian:
orderedQuery = orderedQuery.ThenBy(l => l.data.msAtMedian, ascending);
break;
case SortOption.Total:
orderedQuery = orderedQuery.ThenBy(l => l.data.msTotal, ascending);
break;
case SortOption.Threads:
orderedQuery = orderedQuery.ThenBy(l => l.cachedRowString != null ? l.cachedRowString[(int)MyColumns.Threads].text : GetThreadNames(l), ascending);
break;
}
}
rootItem.children = orderedQuery.Cast<TreeViewItem>().ToList();
}
IOrderedEnumerable<ProfileTreeViewItem> InitialOrder(IEnumerable<ProfileTreeViewItem> myTypes, int[] history)
{
SortOption sortOption = m_SortOptions[history[0]];
bool ascending = multiColumnHeader.IsSortedAscending(history[0]);
switch (sortOption)
{
case SortOption.Name:
return myTypes.Order(l => l.data.name, ascending);
case SortOption.Depth:
return myTypes.Order(l => l.data.minDepth, ascending);
case SortOption.Mean:
return myTypes.Order(l => l.data.msMean, ascending);
case SortOption.Median:
return myTypes.Order(l => l.data.msMedian, ascending);
case SortOption.Min:
return myTypes.Order(l => l.data.msMin, ascending);
case SortOption.Max:
return myTypes.Order(l => l.data.msMax, ascending);
case SortOption.Range:
return myTypes.Order(l => (l.data.msMax - l.data.msMin), ascending);
case SortOption.Count:
return myTypes.Order(l => l.data.count, ascending);
case SortOption.CountMean:
return myTypes.Order(l => l.data.countMean, ascending);
case SortOption.FirstFrame:
return myTypes.Order(l => l.data.firstFrameIndex, ascending);
case SortOption.AtMedian:
return myTypes.Order(l => l.data.msAtMedian, ascending);
case SortOption.Total:
return myTypes.Order(l => l.data.msTotal, ascending);
case SortOption.Threads:
return myTypes.Order(l => l.cachedRowString != null ? l.cachedRowString[(int)MyColumns.Threads].text : GetThreadNames(l), ascending);
default:
Assert.IsTrue(false, "Unhandled enum");
break;
}
// default
return myTypes.Order(l => l.data.name, ascending);
}
public bool ShowingHorizontalScroll
{
get
{
return showingHorizontalScrollBar;
}
}
protected override void RowGUI(RowGUIArgs args)
{
var item = (ProfileTreeViewItem)args.item;
var clipRect = m_2D.GetClipRect();
clipRect.y = state.scrollPos.y;
clipRect.x = state.scrollPos.x;
m_2D.SetClipRect(clipRect);
if (item.cachedRowString == null)
{
GenerateStrings(item);
}
for (int i = 0; i < args.GetNumVisibleColumns(); ++i)
{
CellGUI(args.GetCellRect(i), item, (MyColumns)args.GetColumn(i), ref args);
}
m_2D.ClearClipRect();
}
string ToDisplayUnits(float ms, bool showUnits = false, bool showFullValueWhenBelowZero = false)
{
return m_ProfileAnalyzerWindow.ToDisplayUnits(ms, showUnits, 0, showFullValueWhenBelowZero);
}
string ToDisplayUnits(double ms, bool showUnits = false, bool showFullValueWhenBelowZero = false)
{
return m_ProfileAnalyzerWindow.ToDisplayUnits(ms, showUnits, 0, showFullValueWhenBelowZero);
}
string ToTooltipDisplayUnits(float ms, bool showUnits = false, int onFrame = -1)
{
return m_ProfileAnalyzerWindow.ToTooltipDisplayUnits(ms, showUnits, onFrame);
}
string ToTooltipDisplayUnits(double ms, bool showUnits = false, int onFrame = -1)
{
return ToTooltipDisplayUnits((float)ms, showUnits, onFrame);
}
GUIContent ToDisplayUnitsWithTooltips(float ms, bool showUnits = false, int onFrame = -1)
{
return m_ProfileAnalyzerWindow.ToDisplayUnitsWithTooltips(ms, showUnits, onFrame);
}
GUIContent ToDisplayUnitsWithTooltips(double ms, bool showUnits = false, int onFrame = -1)
{
return ToDisplayUnitsWithTooltips((float)ms, showUnits, onFrame);
}
void CopyToClipboard(Event current, string text)
{
EditorGUIUtility.systemCopyBuffer = text;
}
GenericMenu GenerateActiveContextMenu(string markerName, Event evt, GUIContent content)
{
GenericMenu menu = new GenericMenu();
menu.AddItem(Styles.menuItemSelectFramesInAll, false, () => m_ProfileAnalyzerWindow.SelectFramesContainingMarker(markerName, false));
menu.AddItem(Styles.menuItemSelectFramesInCurrent, false, () => m_ProfileAnalyzerWindow.SelectFramesContainingMarker(markerName, true));
if (m_ProfileAnalyzerWindow.AllSelected())
menu.AddDisabledItem(Styles.menuItemSelectFramesAll);
else
menu.AddItem(Styles.menuItemSelectFramesAll, false, () => m_ProfileAnalyzerWindow.SelectAllFrames());
menu.AddSeparator("");
if (!m_ProfileAnalyzerWindow.GetNameFilters().Contains(markerName, StringComparer.OrdinalIgnoreCase))
menu.AddItem(Styles.menuItemAddToIncludeFilter, false, () => m_ProfileAnalyzerWindow.AddToIncludeFilter(markerName));
else
menu.AddItem(Styles.menuItemRemoveFromIncludeFilter, false, () => m_ProfileAnalyzerWindow.RemoveFromIncludeFilter(markerName));
if (!m_ProfileAnalyzerWindow.GetNameExcludes().Contains(markerName, StringComparer.OrdinalIgnoreCase))
menu.AddItem(Styles.menuItemAddToExcludeFilter, false, () => m_ProfileAnalyzerWindow.AddToExcludeFilter(markerName));
else
menu.AddItem(Styles.menuItemRemoveFromExcludeFilter, false, () => m_ProfileAnalyzerWindow.RemoveFromExcludeFilter(markerName));
menu.AddSeparator("");
menu.AddItem(Styles.menuItemSetAsParentMarkerFilter, false, () => m_ProfileAnalyzerWindow.SetAsParentMarkerFilter(markerName));
menu.AddItem(Styles.menuItemClearParentMarkerFilter, false, () => m_ProfileAnalyzerWindow.SetAsParentMarkerFilter(""));
menu.AddSeparator("");
if (content != null && !string.IsNullOrEmpty(content.text))
menu.AddItem(Styles.menuItemCopyToClipboard, false, () => CopyToClipboard(evt, content.text));
return menu;
}
GenericMenu GenerateDisabledContextMenu(string markerName, GUIContent content)
{
GenericMenu menu = new GenericMenu();
menu.AddDisabledItem(Styles.menuItemSelectFramesInAll);
menu.AddDisabledItem(Styles.menuItemSelectFramesInCurrent);
menu.AddDisabledItem(Styles.menuItemSelectFramesAll);
menu.AddSeparator("");
if (!m_ProfileAnalyzerWindow.GetNameFilters().Contains(markerName, StringComparer.OrdinalIgnoreCase))
menu.AddDisabledItem(Styles.menuItemAddToIncludeFilter);
else
menu.AddDisabledItem(Styles.menuItemRemoveFromIncludeFilter);
if (!m_ProfileAnalyzerWindow.GetNameExcludes().Contains(markerName, StringComparer.OrdinalIgnoreCase))
menu.AddDisabledItem(Styles.menuItemAddToExcludeFilter);
else
menu.AddDisabledItem(Styles.menuItemRemoveFromExcludeFilter);
menu.AddSeparator("");
menu.AddDisabledItem(Styles.menuItemSetAsParentMarkerFilter);
menu.AddDisabledItem(Styles.menuItemClearParentMarkerFilter);
menu.AddSeparator("");
if (content != null && !string.IsNullOrEmpty(content.text))
menu.AddDisabledItem(Styles.menuItemCopyToClipboard);
return menu;
}
void ShowContextMenu(Rect cellRect, string markerName, GUIContent content)
{
Event current = Event.current;
if (cellRect.Contains(current.mousePosition) && current.type == EventType.ContextClick)
{
GenericMenu menu;
if (!m_ProfileAnalyzerWindow.IsAnalysisRunning())
menu = GenerateActiveContextMenu(markerName, current, content);
else
menu = GenerateDisabledContextMenu(markerName, content);
menu.ShowAsContext();
current.Use();
}
}
void ShowText(Rect rect, string text)
{
EditorGUI.LabelField(rect, text);
//EditorGUI.TextArea(rect, text);
}
void ShowText(Rect rect, GUIContent content)
{
EditorGUI.LabelField(rect, content);
//ShowText(rect, content.text);
}
void GenerateStrings(ProfileTreeViewItem item)
{
item.cachedRowString = new GUIContent[m_MaxColumns];
int medianFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(item.data.medianFrameIndex, m_DataView);
int minFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(item.data.minFrameIndex, m_DataView);
int maxFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(item.data.maxFrameIndex, m_DataView);
int firstFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(item.data.firstFrameIndex, m_DataView);
int frameSummaryMedianFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(m_DataView.analysis.GetFrameSummary().medianFrameIndex, m_DataView);
item.cachedRowString[(int)MyColumns.Name] = new GUIContent(item.data.name, item.data.name);
item.cachedRowString[(int)MyColumns.Mean] = ToDisplayUnitsWithTooltips(item.data.msMean, false);
item.cachedRowString[(int)MyColumns.Depth] = (item.data.minDepth == item.data.maxDepth) ? new GUIContent(string.Format("{0}", item.data.minDepth), "") : new GUIContent(string.Format("{0}-{1}", item.data.minDepth, item.data.maxDepth), "");
item.cachedRowString[(int)MyColumns.Median] = ToDisplayUnitsWithTooltips(item.data.msMedian, false, medianFrameIndex);
string tooltip = ToTooltipDisplayUnits(item.data.msMedian, true, medianFrameIndex);
item.cachedRowString[(int)MyColumns.MedianBar] = new GUIContent("", tooltip);
item.cachedRowString[(int)MyColumns.Min] = ToDisplayUnitsWithTooltips(item.data.msMin, false, minFrameIndex);
item.cachedRowString[(int)MyColumns.Max] = ToDisplayUnitsWithTooltips(item.data.msMax, false, maxFrameIndex);
item.cachedRowString[(int)MyColumns.Range] = ToDisplayUnitsWithTooltips(item.data.msMax - item.data.msMin);
item.cachedRowString[(int)MyColumns.Count] = new GUIContent(string.Format("{0}", item.data.count), "");
item.cachedRowString[(int)MyColumns.CountBar] = new GUIContent("", string.Format("{0}", item.data.count));
item.cachedRowString[(int)MyColumns.CountMean] = new GUIContent(string.Format("{0:f0}", item.data.countMean), "");
item.cachedRowString[(int)MyColumns.CountMeanBar] = new GUIContent("", string.Format("{0:f0}", item.data.countMean));
item.cachedRowString[(int)MyColumns.FirstFrame] = new GUIContent(firstFrameIndex.ToString());
item.cachedRowString[(int)MyColumns.AtMedian] = ToDisplayUnitsWithTooltips(item.data.msAtMedian, false, frameSummaryMedianFrameIndex);
item.cachedRowString[(int)MyColumns.Total] = ToDisplayUnitsWithTooltips(item.data.msTotal);
tooltip = ToTooltipDisplayUnits(item.data.msTotal, true, medianFrameIndex);
item.cachedRowString[(int)MyColumns.TotalBar] = new GUIContent("", tooltip);
string threadNames = GetThreadNames(item);
item.cachedRowString[(int)MyColumns.Threads] = new GUIContent(threadNames, threadNames);
}
void ShowBar(Rect rect, float ms, float range, GUIContent content)
{
if (ms > 0.0f)
{
if (m_2D.DrawStart(rect))
{
float w = Math.Max(1.0f, rect.width * ms / range);
m_2D.DrawFilledBox(0, 1, w, rect.height - 1, m_BarColor);
m_2D.DrawEnd();
}
}
GUI.Label(rect, content);
}
void CellGUI(Rect cellRect, ProfileTreeViewItem item, MyColumns column, ref RowGUIArgs args)
{
// Center cell rect vertically (makes it easier to place controls, icons etc in the cells)
CenterRectUsingSingleLineHeight(ref cellRect);
GUIContent content = item.cachedRowString[(int)column];
switch (column)
{
case MyColumns.Name:
{
args.rowRect = cellRect;
//base.RowGUI(args);
//content = new GUIContent(item.data.name, item.data.name);
ShowText(cellRect, content);
}
break;
case MyColumns.Mean:
case MyColumns.Depth:
case MyColumns.Median:
case MyColumns.Min:
case MyColumns.Max:
case MyColumns.Range:
case MyColumns.Count:
case MyColumns.CountMean:
case MyColumns.AtMedian:
case MyColumns.Total:
case MyColumns.Threads:
ShowText(cellRect, content);
break;
case MyColumns.MedianBar:
ShowBar(cellRect, item.data.msMedian, m_MaxMedian, content);
break;
case MyColumns.TotalBar:
ShowBar(cellRect, (float)item.data.msTotal, (float)m_MaxTotal, content);
break;
case MyColumns.CountBar:
ShowBar(cellRect, item.data.count, m_MaxCount, content);
break;
case MyColumns.CountMeanBar:
ShowBar(cellRect, item.data.countMean, m_MaxCountMean, content);
break;
case MyColumns.FirstFrame:
if (!m_ProfileAnalyzerWindow.IsProfilerWindowOpen() || !m_DataView.inSyncWithProfilerData)
GUI.enabled = false;
if (GUI.Button(cellRect, content))
{
m_ProfileAnalyzerWindow.SelectMarkerByIndex(item.id);
m_ProfileAnalyzerWindow.JumpToFrame(item.data.firstFrameIndex, m_DataView.data);
}
GUI.enabled = true;
break;
}
ShowContextMenu(cellRect, item.data.name, content);
}
// Misc
//--------
protected override bool CanMultiSelect(TreeViewItem item)
{
return false;
}
struct HeaderData
{
public readonly GUIContent content;
public readonly float width;
public readonly float minWidth;
public readonly bool autoResize;
public readonly bool allowToggleVisibility;
public readonly bool ascending;
public HeaderData(string name, string tooltip = "", float width = 50, float minWidth = 30, bool autoResize = true, bool allowToggleVisibility = true, bool ascending = false)
{
content = new GUIContent(name, tooltip);
this.width = width;
this.minWidth = minWidth;
this.autoResize = autoResize;
this.allowToggleVisibility = allowToggleVisibility;
this.ascending = ascending;
}
}
public static MultiColumnHeaderState CreateDefaultMultiColumnHeaderState(MarkerColumnFilter modeFilter)
{
var columnList = new List<MultiColumnHeaderState.Column>();
HeaderData[] headerData = new HeaderData[]
{
new HeaderData("Marker Name", "Marker Name\n\nFrame marker time is total of all instances in frame", width: 300, minWidth: 100, autoResize: false, allowToggleVisibility: false, ascending: true),
new HeaderData("Depth", "Marker depth in marker hierarchy\n\nMay appear at multiple levels"),
new HeaderData("Median", "Central marker time over all selected frames\n\nAlways present in data set\n1st of 2 central values for even frame count"),
new HeaderData("Median Bar", "Central marker time over all selected frames", width: 50),
new HeaderData("Mean", "Per frame marker time / number of non zero frames"),
new HeaderData("Min", "Minimum marker time"),
new HeaderData("Max", "Maximum marker time"),
new HeaderData("Range", "Difference between maximum and minimum"),
new HeaderData("Count", "Marker count over all selected frames\n\nMultiple can occur per frame"),
new HeaderData("Count Bar", "Marker count over all selected frames\n\nMultiple can occur per frame"),
new HeaderData("Count Frame", "Average number of markers per frame\n\ntotal count / number of non zero frames", width: 70, minWidth: 50),
new HeaderData("Count Frame Bar", "Average number of markers per frame\n\ntotal count / number of non zero frames", width: 70, minWidth: 50),
new HeaderData("1st", "First frame index that the marker appears on"),
new HeaderData("At Median Frame", "Marker time on the median frame\n\nI.e. Marker total duration on the average frame", width: 90, minWidth: 50),
new HeaderData("Total", "Marker total time over all selected frames"),
new HeaderData("Total Bar", "Marker total time over all selected frames"),
new HeaderData("Threads", "Threads the marker occurs on (with filtering applied)"),
};
foreach (var header in headerData)
{
columnList.Add(new MultiColumnHeaderState.Column
{
headerContent = header.content,
headerTextAlignment = TextAlignment.Left,
sortedAscending = header.ascending,
sortingArrowAlignment = TextAlignment.Left,
width = header.width,
minWidth = header.minWidth,
autoResize = header.autoResize,
allowToggleVisibility = header.allowToggleVisibility
});
}
;
var columns = columnList.ToArray();
m_MaxColumns = Enum.GetValues(typeof(MyColumns)).Length;
Assert.AreEqual(columns.Length, m_MaxColumns, "Number of columns should match number of enum values: You probably forgot to update one of them.");
var state = new MultiColumnHeaderState(columns);
SetMode(modeFilter, state);
return state;
}
protected override void SelectionChanged(IList<int> selectedIds)
{
base.SelectionChanged(selectedIds);
if (selectedIds.Count > 0)
{
m_ProfileAnalyzerWindow.SelectMarkerByIndex(selectedIds[0]);
// A newly selected marker changes the marker summary's GUI content, conflicting with the previous layout pass. We need to exit GUI and re-layout.
GUIUtility.ExitGUI();
}
}
static int[] GetDefaultVisibleColumns(MarkerColumnFilter.Mode mode)
{
int[] visibleColumns;
switch (mode)
{
default:
case MarkerColumnFilter.Mode.Custom:
case MarkerColumnFilter.Mode.TimeAndCount:
visibleColumns = new int[]
{
(int)MyColumns.Name,
(int)MyColumns.Depth,
(int)MyColumns.Median,
(int)MyColumns.MedianBar,
(int)MyColumns.Mean,
(int)MyColumns.Min,
(int)MyColumns.Max,
(int)MyColumns.Range,
(int)MyColumns.Count,
(int)MyColumns.CountMean,
(int)MyColumns.AtMedian,
};
break;
case MarkerColumnFilter.Mode.Time:
visibleColumns = new int[]
{
(int)MyColumns.Name,
(int)MyColumns.Depth,
(int)MyColumns.Median,
(int)MyColumns.MedianBar,
(int)MyColumns.Min,
(int)MyColumns.Max,
(int)MyColumns.Range,
(int)MyColumns.AtMedian,
};
break;
case MarkerColumnFilter.Mode.Totals:
visibleColumns = new int[]
{
(int)MyColumns.Name,
(int)MyColumns.Depth,
(int)MyColumns.Total,
(int)MyColumns.TotalBar,
};
break;
case MarkerColumnFilter.Mode.TimeWithTotals:
visibleColumns = new int[]
{
(int)MyColumns.Name,
(int)MyColumns.Depth,
(int)MyColumns.Median,
(int)MyColumns.MedianBar,
(int)MyColumns.Min,
(int)MyColumns.Max,
(int)MyColumns.Range,
(int)MyColumns.AtMedian,
(int)MyColumns.Total,
(int)MyColumns.TotalBar,
};
break;
case MarkerColumnFilter.Mode.CountTotals:
visibleColumns = new int[]
{
(int)MyColumns.Name,
(int)MyColumns.Depth,
(int)MyColumns.Count,
(int)MyColumns.CountBar,
};
break;
case MarkerColumnFilter.Mode.CountPerFrame:
visibleColumns = new int[]
{
(int)MyColumns.Name,
(int)MyColumns.Depth,
(int)MyColumns.CountMean,
(int)MyColumns.CountMeanBar,
};
break;
case MarkerColumnFilter.Mode.Depth:
visibleColumns = new int[]
{
(int)MyColumns.Name,
(int)MyColumns.Depth,
};
break;
case MarkerColumnFilter.Mode.Threads:
visibleColumns = new int[]
{
(int)MyColumns.Name,
(int)MyColumns.Threads,
};
break;
}
return visibleColumns;
}
static void SetMode(MarkerColumnFilter modeFilter, MultiColumnHeaderState state)
{
switch (modeFilter.mode)
{
case MarkerColumnFilter.Mode.Custom:
if (modeFilter.visibleColumns == null)
state.visibleColumns = GetDefaultVisibleColumns(modeFilter.mode);
else
state.visibleColumns = modeFilter.visibleColumns;
break;
default:
state.visibleColumns = GetDefaultVisibleColumns(modeFilter.mode);
break;
}
if (modeFilter.visibleColumns == null)
modeFilter.visibleColumns = state.visibleColumns;
}
public void SetMode(MarkerColumnFilter modeFilter)
{
SetMode(modeFilter, multiColumnHeader.state);
multiColumnHeader.ResizeToFit();
}
}
static class MyExtensionMethods
{
public static IOrderedEnumerable<T> Order<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector, bool ascending)
{
if (ascending)
{
return source.OrderBy(selector);
}
else
{
return source.OrderByDescending(selector);
}
}
public static IOrderedEnumerable<T> ThenBy<T, TKey>(this IOrderedEnumerable<T> source, Func<T, TKey> selector, bool ascending)
{
if (ascending)
{
return source.ThenBy(selector);
}
else
{
return source.ThenByDescending(selector);
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 981ca102f47f145c1977153854cce718
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,903 @@
using UnityEditorInternal;
using System.Reflection;
using System;
using System.Collections;
using UnityEngine;
using System.Collections.Generic;
using System.IO;
using UnityEngine.Profiling;
#if UNITY_2020_1_OR_NEWER
using UnityEditor.Profiling;
#endif
#if UNITY_2021_2_OR_NEWER
using Unity.Profiling.Editor;
// stub so that ProfilerWindow can be moved to this namespace in trunk without a need to change PA
namespace Unity.Profiling.Editor {}
#endif
namespace UnityEditor.Performance.ProfileAnalyzer
{
internal class ProfilerWindowInterface
{
bool m_ProfilerWindowInitialized = false;
const float k_NsToMs = 1000000;
#if UNITY_2020_1_OR_NEWER
bool s_UseRawIterator = true;
#endif
ProgressBarDisplay m_progressBar;
[NonSerialized] bool m_SendingSelectionEventToProfilerWindowInProgress = false;
[NonSerialized] int m_LastSelectedFrameInProfilerWindow = 0;
#if UNITY_2021_1_OR_NEWER
[NonSerialized] ProfilerWindow m_ProfilerWindow;
[NonSerialized] IProfilerFrameTimeViewSampleSelectionController m_CpuProfilerModule;
#else
Type m_ProfilerWindowType;
EditorWindow m_ProfilerWindow;
FieldInfo m_CurrentFrameFieldInfo;
FieldInfo m_TimeLineGUIFieldInfo;
FieldInfo m_SelectedEntryFieldInfo;
FieldInfo m_SelectedNameFieldInfo;
FieldInfo m_SelectedTimeFieldInfo;
FieldInfo m_SelectedDurationFieldInfo;
FieldInfo m_SelectedInstanceIdFieldInfo;
FieldInfo m_SelectedFrameIdFieldInfo;
FieldInfo m_SelectedThreadIndexFieldInfo;
FieldInfo m_SelectedNativeIndexFieldInfo;
FieldInfo m_SelectedInstanceCountFieldInfo;
FieldInfo m_SelectedInstanceCountForThreadFieldInfo;
FieldInfo m_SelectedInstanceCountForFrameFieldInfo;
FieldInfo m_SelectedMetaDataFieldInfo;
FieldInfo m_SelectedThreadCountFieldInfo;
FieldInfo m_SelectedCallstackInfoFieldInfo;
MethodInfo m_GetProfilerModuleInfo;
Type m_CPUProfilerModuleType;
#endif
public ProfilerWindowInterface(ProgressBarDisplay progressBar)
{
m_progressBar = progressBar;
#if !UNITY_2021_1_OR_NEWER
Assembly assem = typeof(Editor).Assembly;
m_ProfilerWindowType = assem.GetType("UnityEditor.ProfilerWindow");
m_CurrentFrameFieldInfo = m_ProfilerWindowType.GetField("m_CurrentFrame", BindingFlags.NonPublic | BindingFlags.Instance);
m_TimeLineGUIFieldInfo = m_ProfilerWindowType.GetField("m_CPUTimelineGUI", BindingFlags.NonPublic | BindingFlags.Instance);
if (m_TimeLineGUIFieldInfo == null)
{
// m_CPUTimelineGUI isn't present in 2019.3.0a8 onward
m_GetProfilerModuleInfo = m_ProfilerWindowType.GetMethod("GetProfilerModule", BindingFlags.NonPublic | BindingFlags.Instance);
if (m_GetProfilerModuleInfo == null)
{
Debug.Log("Unable to initialise link to Profiler Timeline, no GetProfilerModule found");
}
m_CPUProfilerModuleType = assem.GetType("UnityEditorInternal.Profiling.CPUProfilerModule");
m_TimeLineGUIFieldInfo = m_CPUProfilerModuleType.GetField("m_TimelineGUI", BindingFlags.NonPublic | BindingFlags.Instance);
if (m_TimeLineGUIFieldInfo == null)
{
Debug.Log("Unable to initialise link to Profiler Timeline");
}
}
if (m_TimeLineGUIFieldInfo != null)
m_SelectedEntryFieldInfo = m_TimeLineGUIFieldInfo.FieldType.GetField("m_SelectedEntry", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (m_SelectedEntryFieldInfo != null)
{
m_SelectedNameFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("name", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
m_SelectedTimeFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("time", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
m_SelectedDurationFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("duration", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
m_SelectedInstanceIdFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("instanceId", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
m_SelectedFrameIdFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("frameId", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
// confusingly this is called threadId but is the thread _index_
m_SelectedThreadIndexFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("threadId", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
m_SelectedNativeIndexFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("nativeIndex", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
m_SelectedInstanceCountFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("instanceCount", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
m_SelectedInstanceCountForThreadFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("instanceCountForThread", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
m_SelectedInstanceCountForFrameFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("instanceCountForFrame", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
m_SelectedThreadCountFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("threadCount", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
m_SelectedMetaDataFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("metaData", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
m_SelectedCallstackInfoFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("callstackInfo", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
}
#endif
}
public bool IsReady()
{
return m_ProfilerWindow != null && m_ProfilerWindowInitialized;
}
public void GetProfilerWindowHandle()
{
Profiler.BeginSample("GetProfilerWindowHandle");
#if UNITY_2021_1_OR_NEWER
if (m_CpuProfilerModule != null)
{
m_CpuProfilerModule.selectionChanged -= OnSelectionChangedInCpuProfilerModule;
m_CpuProfilerModule = null;
}
var windows = Resources.FindObjectsOfTypeAll<ProfilerWindow>();
if (windows != null && windows.Length > 0)
m_ProfilerWindow = windows[0];
if (m_ProfilerWindow != null)
{
#if UNITY_2021_2_OR_NEWER
var cpuModuleIdentifier = ProfilerWindow.cpuModuleIdentifier;
#else
var cpuModuleIdentifier = ProfilerWindow.cpuModuleName;
#endif
m_CpuProfilerModule =
m_ProfilerWindow.GetFrameTimeViewSampleSelectionController(cpuModuleIdentifier);
m_CpuProfilerModule.selectionChanged -= OnSelectionChangedInCpuProfilerModule;
m_CpuProfilerModule.selectionChanged += OnSelectionChangedInCpuProfilerModule;
m_ProfilerWindow.Repaint();
m_ProfilerWindowInitialized = false;
// wait a frame for the Profiler to get Repainted
EditorApplication.delayCall += () => m_ProfilerWindowInitialized = true;
}
#else
UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(m_ProfilerWindowType);
if (windows != null && windows.Length > 0)
m_ProfilerWindow = (EditorWindow)windows[0];
m_ProfilerWindowInitialized = true;
#endif
Profiler.EndSample();
}
public void OpenProfilerOrUseExisting()
{
// Note we use existing if possible to fix a bug after domain reload
// Where calling EditorWindow.GetWindow directly causes a second window to open
if (m_ProfilerWindow == null)
{
#if UNITY_2021_1_OR_NEWER
m_ProfilerWindow = EditorWindow.GetWindow<ProfilerWindow>();
#if UNITY_2021_2_OR_NEWER
var cpuModuleIdentifier = ProfilerWindow.cpuModuleIdentifier;
#else
var cpuModuleIdentifier = ProfilerWindow.cpuModuleName;
#endif
m_CpuProfilerModule = m_ProfilerWindow.GetFrameTimeViewSampleSelectionController(cpuModuleIdentifier);
m_CpuProfilerModule.selectionChanged -= OnSelectionChangedInCpuProfilerModule;
m_CpuProfilerModule.selectionChanged += OnSelectionChangedInCpuProfilerModule;
#else
// Create new
m_ProfilerWindow = EditorWindow.GetWindow(m_ProfilerWindowType);
#endif
}
}
public bool GetFrameRangeFromProfiler(out int first, out int last)
{
if (m_ProfilerWindow != null)
{
first = 1 + ProfilerDriver.firstFrameIndex;
last = 1 + ProfilerDriver.lastFrameIndex;
#if !UNITY_2018_4_OR_NEWER
//Prior to 18.4 we need to clip to the visible frames in the profile which indents 1 in from end
//as the last frame is not visible and sometimes still being processed
if (first < last)
last--;
#endif
return true;
}
first = 1;
last = 1;
return false;
}
public void CloseProfiler()
{
if (m_ProfilerWindow != null)
m_ProfilerWindow.Close();
}
#if !UNITY_2021_1_OR_NEWER
object GetTimeLineGUI()
{
object timeLineGUI = null;
if (m_CPUProfilerModuleType != null)
{
object[] parametersArray = new object[] { ProfilerArea.CPU };
var getCPUProfilerModuleInfo = m_GetProfilerModuleInfo.MakeGenericMethod(m_CPUProfilerModuleType);
var cpuModule = getCPUProfilerModuleInfo.Invoke(m_ProfilerWindow, parametersArray);
timeLineGUI = m_TimeLineGUIFieldInfo.GetValue(cpuModule);
}
else if (m_TimeLineGUIFieldInfo != null)
{
timeLineGUI = m_TimeLineGUIFieldInfo.GetValue(m_ProfilerWindow);
}
return timeLineGUI;
}
#endif
#if UNITY_2021_1_OR_NEWER
private void OnSelectionChangedInCpuProfilerModule(IProfilerFrameTimeViewSampleSelectionController controller, ProfilerTimeSampleSelection selection)
{
if (controller == m_CpuProfilerModule && !m_SendingSelectionEventToProfilerWindowInProgress)
{
if (selection != null && selection.markerNamePath != null && selection.markerNamePath.Count > 0)
{
selectedMarkerChanged(selection.markerNamePath[selection.markerNamePath.Count - 1], selection.threadGroupName, selection.threadName);
}
}
}
#endif
public event Action<string, string, string> selectedMarkerChanged = delegate {};
public void PollProfilerWindowMarkerName()
{
#if !UNITY_2021_1_OR_NEWER
if (m_ProfilerWindow != null)
{
var timeLineGUI = GetTimeLineGUI();
if (timeLineGUI != null && m_SelectedEntryFieldInfo != null)
{
var selectedEntry = m_SelectedEntryFieldInfo.GetValue(timeLineGUI);
if (selectedEntry != null && m_SelectedNameFieldInfo != null)
{
string threadGroupName = null;
string threadName = null;
#if UNITY_2020_1_OR_NEWER
if (m_SelectedFrameIdFieldInfo != null && m_SelectedThreadIndexFieldInfo != null)
{
using (RawFrameDataView frameData = ProfilerDriver.GetRawFrameDataView((int)m_SelectedFrameIdFieldInfo.GetValue(selectedEntry), (int)m_SelectedThreadIndexFieldInfo.GetValue(selectedEntry)))
{
if (frameData != null && frameData.valid)
{
threadGroupName = frameData.threadGroupName;
threadName = frameData.threadName;
}
}
}
#endif
selectedMarkerChanged(m_SelectedNameFieldInfo.GetValue(selectedEntry).ToString(), threadGroupName, threadName);
}
}
}
#endif
}
public ProfileData PullFromProfiler(int firstFrameDisplayIndex, int lastFrameDisplayIndex)
{
Profiler.BeginSample("ProfilerWindowInterface.PullFromProfiler");
bool recording = IsRecording();
if (recording)
StopRecording();
int firstFrameIndex = Mathf.Max(firstFrameDisplayIndex - 1, 0);
int lastFrameIndex = lastFrameDisplayIndex - 1;
ProfileData profileData = GetData(firstFrameIndex, lastFrameIndex);
if (recording)
StartRecording();
Profiler.EndSample();
return profileData;
}
public int GetThreadCountForFrame(int frameIndex)
{
if (!IsReady())
return 0;
ProfilerFrameDataIterator frameData = new ProfilerFrameDataIterator();
frameData.SetRoot(frameIndex, 0);
return frameData.GetThreadCount(frameIndex);
}
public ProfileFrame GetProfileFrameForThread(int frameIndex, int threadIndex)
{
if (!IsReady())
return null;
var frame = new ProfileFrame();
#if UNITY_2020_1_OR_NEWER
using (RawFrameDataView frameData = ProfilerDriver.GetRawFrameDataView(frameIndex, threadIndex))
{
frame.msStartTime = frameData.frameStartTimeMs;
frame.msFrame = frameData.frameTimeMs;
}
#else
ProfilerFrameDataIterator frameData = new ProfilerFrameDataIterator();
frameData.SetRoot(frameIndex, threadIndex);
frame.msStartTime = 1000.0 * frameData.GetFrameStartS(frameIndex);
frame.msFrame = frameData.frameTimeMS;
#endif
return frame;
}
#if UNITY_2020_1_OR_NEWER
ProfileData GetDataRaw(ProfileData data, int firstFrameIndex, int lastFrameIndex)
{
bool firstError = true;
data.SetFrameIndexOffset(firstFrameIndex);
var depthStack = new Stack<int>();
var threadNameCount = new Dictionary<string, int>();
var markerIdToNameIndex = new Dictionary<int, int>();
for (int frameIndex = firstFrameIndex; frameIndex <= lastFrameIndex; ++frameIndex)
{
m_progressBar.AdvanceProgressBar();
int threadIndex = 0;
threadNameCount.Clear();
ProfileFrame frame = null;
while (true)
{
using (RawFrameDataView frameData = ProfilerDriver.GetRawFrameDataView(frameIndex, threadIndex))
{
if (threadIndex == 0)
{
if ((frameIndex == firstFrameIndex || frameIndex == lastFrameIndex)
&& firstFrameIndex != lastFrameIndex && (!frameData.valid || frameData.frameTimeNs == 0))
{
// skip incomplete frames when they are at the beginning or end of the capture
if (++frameIndex <= lastFrameIndex)
{
data.FirstFrameIncomplete = true;
data.SetFrameIndexOffset(frameIndex);
continue;
}
else
{
// break out entirely if this is the last frame
data.LastFrameIncomplete = true;
break;
}
}
frame = new ProfileFrame();
if (frameData.valid)
{
frame.msStartTime = frameData.frameStartTimeMs;
frame.msFrame = frameData.frameTimeMs;
}
data.Add(frame);
}
if (!frameData.valid)
break;
string threadNameWithIndex = null;
string threadName = frameData.threadName;
if (threadName.Trim() == "")
{
Debug.Log(string.Format("Warning: Unnamed thread found on frame {0}. Corrupted data suspected, ignoring frame", frameIndex));
threadIndex++;
continue;
}
var groupName = frameData.threadGroupName;
threadName = ProfileData.GetThreadNameWithGroup(threadName, groupName);
int nameCount = 0;
threadNameCount.TryGetValue(threadName, out nameCount);
threadNameCount[threadName] = nameCount + 1;
threadNameWithIndex = ProfileData.ThreadNameWithIndex(threadNameCount[threadName], threadName);
var thread = new ProfileThread();
data.AddThreadName(threadNameWithIndex, thread);
frame.Add(thread);
// The markers are in depth first order
depthStack.Clear();
// first sample is the thread name
for (int i = 1; i < frameData.sampleCount; i++)
{
float durationMS = frameData.GetSampleTimeMs(i);
int markerId = frameData.GetSampleMarkerId(i);
if (durationMS < 0)
{
if (firstError)
{
int displayIndex = data.OffsetToDisplayFrame(frameIndex);
string name = frameData.GetSampleName(i);
Debug.LogFormat("Ignoring Invalid marker time found for {0} on frame {1} on thread {2} ({3} < 0)",
name, displayIndex, threadName, durationMS);
firstError = false;
}
}
else
{
int depth = 1 + depthStack.Count;
var markerData = ProfileMarker.Create(durationMS, depth);
// Use name index directly if we have already stored this named marker before
int nameIndex;
if (markerIdToNameIndex.TryGetValue(markerId, out nameIndex))
{
markerData.nameIndex = nameIndex;
}
else
{
string name = frameData.GetSampleName(i);
data.AddMarkerName(name, markerData);
markerIdToNameIndex[markerId] = markerData.nameIndex;
}
thread.AddMarker(markerData);
}
int childrenCount = frameData.GetSampleChildrenCount(i);
if (childrenCount > 0)
{
depthStack.Push(childrenCount);
}
else
{
while (depthStack.Count > 0)
{
int remainingChildren = depthStack.Pop();
if (remainingChildren > 1)
{
depthStack.Push(remainingChildren - 1);
break;
}
}
}
}
}
threadIndex++;
}
}
data.Finalise();
return data;
}
#endif
ProfileData GetDataOriginal(ProfileData data, int firstFrameIndex, int lastFrameIndex)
{
ProfilerFrameDataIterator frameData = new ProfilerFrameDataIterator();
bool firstError = true;
data.SetFrameIndexOffset(firstFrameIndex);
Dictionary<string, int> threadNameCount = new Dictionary<string, int>();
for (int frameIndex = firstFrameIndex; frameIndex <= lastFrameIndex; ++frameIndex)
{
m_progressBar.AdvanceProgressBar();
int threadCount = frameData.GetThreadCount(frameIndex);
frameData.SetRoot(frameIndex, 0);
var msFrame = frameData.frameTimeMS;
if ((frameIndex == firstFrameIndex || frameIndex == lastFrameIndex)
&& firstFrameIndex != lastFrameIndex && msFrame == 0)
{
var nextFrame = frameIndex + 1;
// skip incomplete frames when they are at the beginning or end of the capture
if (nextFrame <= lastFrameIndex)
{
data.FirstFrameIncomplete = true;
data.SetFrameIndexOffset(nextFrame);
continue;
}
else
{
// break out entirely if this is the last frame
data.LastFrameIncomplete = true;
break;
}
}
/*
if (frameIndex == lastFrameIndex)
{
// Check if last frame appears to be invalid data
float median;
float mean;
float standardDeviation;
CalculateFrameTimeStats(data, out median, out mean, out standardDeviation);
float execessiveDeviation = (3f * standardDeviation);
if (msFrame > (median + execessiveDeviation))
{
Debug.LogFormat("Dropping last frame as it is significantly larger than the median of the rest of the data set {0} > {1} (median {2} + 3 * standard deviation {3})", msFrame, median + execessiveDeviation, median, standardDeviation);
break;
}
if (msFrame < (median - execessiveDeviation))
{
Debug.LogFormat("Dropping last frame as it is significantly smaller than the median of the rest of the data set {0} < {1} (median {2} - 3 * standard deviation {3})", msFrame, median - execessiveDeviation, median, standardDeviation);
break;
}
}
*/
ProfileFrame frame = new ProfileFrame();
frame.msStartTime = 1000.0 * frameData.GetFrameStartS(frameIndex);
frame.msFrame = msFrame;
data.Add(frame);
threadNameCount.Clear();
for (int threadIndex = 0; threadIndex < threadCount; ++threadIndex)
{
frameData.SetRoot(frameIndex, threadIndex);
var threadName = frameData.GetThreadName();
if (threadName.Trim() == "")
{
Debug.Log(string.Format("Warning: Unnamed thread found on frame {0}. Corrupted data suspected, ignoring frame", frameIndex));
continue;
}
var groupName = frameData.GetGroupName();
threadName = ProfileData.GetThreadNameWithGroup(threadName, groupName);
ProfileThread thread = new ProfileThread();
frame.Add(thread);
int nameCount = 0;
threadNameCount.TryGetValue(threadName, out nameCount);
threadNameCount[threadName] = nameCount + 1;
data.AddThreadName(ProfileData.ThreadNameWithIndex(threadNameCount[threadName], threadName), thread);
const bool enterChildren = true;
// The markers are in depth first order and the depth is known
// So we can infer a parent child relationship
while (frameData.Next(enterChildren))
{
if (frameData.durationMS < 0)
{
if (firstError)
{
int displayIndex = data.OffsetToDisplayFrame(frameIndex);
Debug.LogFormat("Ignoring Invalid marker time found for {0} on frame {1} on thread {2} ({3} < 0) : Instance id : {4}",
frameData.name, displayIndex, threadName, frameData.durationMS, frameData.instanceId);
firstError = false;
}
continue;
}
var markerData = ProfileMarker.Create(frameData);
data.AddMarkerName(frameData.name, markerData);
thread.AddMarker(markerData);
}
}
}
data.Finalise();
frameData.Dispose();
return data;
}
ProfileData GetData(int firstFrameIndex, int lastFrameIndex)
{
ProfileData data = new ProfileData(ProfileAnalyzerWindow.TmpPath);
#if UNITY_2020_1_OR_NEWER
GetDataRaw(data, firstFrameIndex, lastFrameIndex);
#else
GetDataOriginal(data, firstFrameIndex, lastFrameIndex);
#endif
data.Write();
return data;
}
#if UNITY_2020_1_OR_NEWER
public float GetFrameTimeRaw(int frameIndex)
{
using (RawFrameDataView frameData = ProfilerDriver.GetRawFrameDataView(frameIndex, 0))
{
if (!frameData.valid)
return 0f;
return frameData.frameTimeMs;
}
}
#endif
public float GetFrameTime(int frameIndex)
{
#if UNITY_2020_1_OR_NEWER
if (s_UseRawIterator)
return GetFrameTimeRaw(frameIndex);
#endif
ProfilerFrameDataIterator frameData = new ProfilerFrameDataIterator();
frameData.SetRoot(frameIndex, 0);
float ms = frameData.frameTimeMS;
frameData.Dispose();
return ms;
}
struct ThreadIndexIterator
{
public ProfilerFrameDataIterator frameData;
public int threadIndex;
}
IEnumerator<ThreadIndexIterator> GetNextThreadIndexFittingThreadFilters(int frameIndex, List<string> threadFilters)
{
ProfilerFrameDataIterator frameData = new ProfilerFrameDataIterator();
int threadCount = frameData.GetThreadCount(frameIndex);
Dictionary<string, int> threadNameCount = new Dictionary<string, int>();
for (int threadIndex = 0; threadIndex < threadCount; ++threadIndex)
{
frameData.SetRoot(frameIndex, threadIndex);
var threadName = frameData.GetThreadName();
// Name here could be "Worker Thread 1"
var groupName = frameData.GetGroupName();
threadName = ProfileData.GetThreadNameWithGroup(threadName, groupName);
int nameCount = 0;
threadNameCount.TryGetValue(threadName, out nameCount);
threadNameCount[threadName] = nameCount + 1;
var threadNameWithIndex = ProfileData.ThreadNameWithIndex(threadNameCount[threadName], threadName);
// To compare on the filter we need to remove the postfix on the thread name
// "3:Worker Thread 0" -> "1:Worker Thread"
// The index of the thread (0) is used +1 as a prefix
// The preceding number (3) is the count of number of threads with this name
// Unfortunately multiple threads can have the same name
threadNameWithIndex = ProfileData.CorrectThreadName(threadNameWithIndex);
if (threadFilters.Contains(threadNameWithIndex))
{
yield return new ThreadIndexIterator {frameData = frameData, threadIndex = threadIndex};
}
}
frameData.Dispose();
}
bool GetMarkerInfo(string markerName, int frameIndex, List<string> threadFilters, out int outThreadIndex, out int outNativeIndex, out float time, out float duration, out int instanceId)
{
outThreadIndex = 0;
outNativeIndex = 0;
time = 0.0f;
duration = 0.0f;
instanceId = 0;
bool found = false;
var iterator = GetNextThreadIndexFittingThreadFilters(frameIndex, threadFilters);
while (iterator.MoveNext())
{
const bool enterChildren = true;
while (iterator.Current.frameData.Next(enterChildren))
{
if (iterator.Current.frameData.name == markerName)
{
time = iterator.Current.frameData.startTimeMS;
duration = iterator.Current.frameData.durationMS;
instanceId = iterator.Current.frameData.instanceId;
outNativeIndex = iterator.Current.frameData.sampleId;
outThreadIndex = iterator.Current.threadIndex;
found = true;
break;
}
}
if (found)
break;
}
return found;
}
public bool SetProfilerWindowMarkerName(string markerName, List<string> threadFilters)
{
m_SendingSelectionEventToProfilerWindowInProgress = true;
if (m_ProfilerWindow == null)
return false;
#if UNITY_2021_1_OR_NEWER
#if UNITY_2021_2_OR_NEWER
var cpuModuleIdentifier = ProfilerWindow.cpuModuleIdentifier;
var selectedModuleIdentifier = m_ProfilerWindow.selectedModuleIdentifier;
#else
var cpuModuleIdentifier = ProfilerWindow.cpuModuleName;
var selectedModuleIdentifier = m_ProfilerWindow.selectedModuleName;
#endif
m_CpuProfilerModule = m_ProfilerWindow.GetFrameTimeViewSampleSelectionController(cpuModuleIdentifier);
if (m_CpuProfilerModule != null && selectedModuleIdentifier == cpuModuleIdentifier
&& m_ProfilerWindow.firstAvailableFrameIndex >= 0)
{
// Read profiler data direct from profile to find time/duration
int currentFrameIndex = (int)m_ProfilerWindow.selectedFrameIndex;
var iterator = GetNextThreadIndexFittingThreadFilters(currentFrameIndex, threadFilters);
while (iterator.MoveNext())
{
using (var rawFrameDataView = ProfilerDriver.GetRawFrameDataView(currentFrameIndex, iterator.Current.threadIndex))
{
if (m_CpuProfilerModule.SetSelection(currentFrameIndex,
rawFrameDataView.threadGroupName, rawFrameDataView.threadName, markerName,
threadId: rawFrameDataView.threadId))
{
m_ProfilerWindow.Repaint();
m_SendingSelectionEventToProfilerWindowInProgress = false;
return true; // setting the selection was successful, nothing more to do here.
}
}
}
// selection couldn't be found, so clear the current one to avoid confusion
m_CpuProfilerModule.ClearSelection();
m_ProfilerWindow.Repaint();
}
#else
var timeLineGUI = GetTimeLineGUI();
if (timeLineGUI == null)
{
m_SendingSelectionEventToProfilerWindowInProgress = false;
return false;
}
if (m_SelectedEntryFieldInfo != null)
{
var selectedEntry = m_SelectedEntryFieldInfo.GetValue(timeLineGUI);
if (selectedEntry != null)
{
// Read profiler data direct from profile to find time/duration
int currentFrameIndex = (int)m_CurrentFrameFieldInfo.GetValue(m_ProfilerWindow);
float time;
float duration;
int instanceId;
int nativeIndex;
int threadIndex;
if (GetMarkerInfo(markerName, currentFrameIndex, threadFilters, out threadIndex, out nativeIndex, out time, out duration, out instanceId))
{
/*
Debug.Log(string.Format("Setting profiler to {0} on {1} at frame {2} at {3}ms for {4}ms ({5})",
markerName, currentFrameIndex, threadFilter, time, duration, instanceId));
*/
if (m_SelectedNameFieldInfo != null)
m_SelectedNameFieldInfo.SetValue(selectedEntry, markerName);
if (m_SelectedTimeFieldInfo != null)
m_SelectedTimeFieldInfo.SetValue(selectedEntry, time);
if (m_SelectedDurationFieldInfo != null)
m_SelectedDurationFieldInfo.SetValue(selectedEntry, duration);
if (m_SelectedInstanceIdFieldInfo != null)
m_SelectedInstanceIdFieldInfo.SetValue(selectedEntry, instanceId);
if (m_SelectedFrameIdFieldInfo != null)
m_SelectedFrameIdFieldInfo.SetValue(selectedEntry, currentFrameIndex);
if (m_SelectedNativeIndexFieldInfo != null)
m_SelectedNativeIndexFieldInfo.SetValue(selectedEntry, nativeIndex);
if (m_SelectedThreadIndexFieldInfo != null)
m_SelectedThreadIndexFieldInfo.SetValue(selectedEntry, threadIndex);
// TODO : Update to fill in the total and number of instances.
// For now we force Instance count to 1 to avoid the incorrect info showing.
if (m_SelectedInstanceCountFieldInfo != null)
m_SelectedInstanceCountFieldInfo.SetValue(selectedEntry, 1);
if (m_SelectedInstanceCountForThreadFieldInfo != null)
m_SelectedInstanceCountForThreadFieldInfo.SetValue(selectedEntry, 1);
if (m_SelectedInstanceCountForFrameFieldInfo != null)
m_SelectedInstanceCountForFrameFieldInfo.SetValue(selectedEntry, 1);
if (m_SelectedThreadCountFieldInfo != null)
m_SelectedThreadCountFieldInfo.SetValue(selectedEntry, 1);
if (m_SelectedMetaDataFieldInfo != null)
m_SelectedMetaDataFieldInfo.SetValue(selectedEntry, "");
if (m_SelectedCallstackInfoFieldInfo != null)
m_SelectedCallstackInfoFieldInfo.SetValue(selectedEntry, "");
m_ProfilerWindow.Repaint();
m_SendingSelectionEventToProfilerWindowInProgress = false;
return true;
}
}
}
#endif
m_SendingSelectionEventToProfilerWindowInProgress = false;
return false;
}
public bool JumpToFrame(int index)
{
if (m_ProfilerWindow == null)
return false;
if (index - 1 < ProfilerDriver.firstFrameIndex)
return false;
if (index - 1 > ProfilerDriver.lastFrameIndex)
return false;
#if UNITY_2021_1_OR_NEWER
m_ProfilerWindow.selectedFrameIndex = index - 1;
#else
m_CurrentFrameFieldInfo.SetValue(m_ProfilerWindow, index - 1);
#endif
m_ProfilerWindow.Repaint();
return true;
}
public int selectedFrame
{
get
{
if (m_ProfilerWindow == null)
return 0;
#if UNITY_2021_1_OR_NEWER
return (int)m_ProfilerWindow.selectedFrameIndex + 1;
#else
return (int)m_CurrentFrameFieldInfo.GetValue(m_ProfilerWindow) + 1;
#endif
}
}
public event Action<int> selectedFrameChanged = delegate {};
public void PollSelectedFrameChanges()
{
var currentlySelectedFrame = selectedFrame;
if (m_LastSelectedFrameInProfilerWindow != currentlySelectedFrame && !m_SendingSelectionEventToProfilerWindowInProgress)
{
m_LastSelectedFrameInProfilerWindow = currentlySelectedFrame;
selectedFrameChanged(currentlySelectedFrame);
}
}
public bool IsRecording()
{
#if UNITY_2017_4_OR_NEWER
return ProfilerDriver.enabled;
#else
return false;
#endif
}
public void StopRecording()
{
#if UNITY_2017_4_OR_NEWER
// Stop recording first
ProfilerDriver.enabled = false;
#endif
}
public void StartRecording()
{
#if UNITY_2017_4_OR_NEWER
// Stop recording first
ProfilerDriver.enabled = true;
#endif
}
public void OnDisable()
{
if (m_ProfilerWindow != null)
{
m_ProfilerWindow = null;
}
#if UNITY_2021_1_OR_NEWER
if (m_CpuProfilerModule != null)
{
m_CpuProfilerModule.selectionChanged -= OnSelectionChangedInCpuProfilerModule;
m_CpuProfilerModule = null;
}
#endif
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d8e7ffaacab264ab6ba5aa70f432785f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,36 @@
using UnityEngine;
namespace UnityEditor.Performance.ProfileAnalyzer
{
internal class ProgressBarDisplay
{
int m_TotalFrames;
int m_CurrentFrame;
string m_Title;
string m_Description;
public void InitProgressBar(string title, string description, int frames)
{
m_CurrentFrame = 0;
m_TotalFrames = frames;
m_Title = title;
m_Description = description;
EditorUtility.DisplayProgressBar(m_Title, m_Description, m_CurrentFrame);
}
public void AdvanceProgressBar()
{
m_CurrentFrame++;
int currentFrame = Mathf.Clamp(0, m_CurrentFrame, m_TotalFrames);
float progress = m_TotalFrames > 0 ? (float)currentFrame / m_TotalFrames : 0f;
EditorUtility.DisplayProgressBar(m_Title, m_Description, progress);
}
public void ClearProgressBar()
{
EditorUtility.ClearProgressBar();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c27dfe7bd1055403b9dac63caf69c135
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace UnityEditor.Performance.ProfileAnalyzer
{
[Serializable]
internal class ThreadData
{
public string threadNameWithIndex;
public int threadGroupIndex;
public string threadGroupName;
public int threadsInGroup;
public List<ThreadFrameTime> frames = new List<ThreadFrameTime>();
public float msMedian;
public float msLowerQuartile;
public float msUpperQuartile;
public float msMin;
public float msMax;
public int medianFrameIndex;
public int minFrameIndex;
public int maxFrameIndex;
public ThreadData(string _threadName)
{
threadNameWithIndex = _threadName;
var info = threadNameWithIndex.Split(':');
threadGroupIndex = int.Parse(info[0]);
threadGroupName = info[1];
threadsInGroup = 1;
msMedian = 0.0f;
msLowerQuartile = 0.0f;
msUpperQuartile = 0.0f;
msMin = 0.0f;
msMax = 0.0f;
medianFrameIndex = -1;
minFrameIndex = -1;
maxFrameIndex = -1;
}
struct ThreadFrameTimeFrameComparer : IComparer<ThreadFrameTime>
{
#if UNITY_2017_OR_NEWER
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[MethodImpl(256)]
#endif
public int Compare(ThreadFrameTime x, ThreadFrameTime y)
{
if (x.frameIndex == y.frameIndex)
return 0;
return x.frameIndex > y.frameIndex ? 1 : -1;
}
}
#if UNITY_2017_OR_NEWER
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[MethodImpl(256)]
#endif
public ThreadFrameTime? GetFrame(int frameIndex)
{
var index = frames.BinarySearch(new ThreadFrameTime(frameIndex, 0, 0), new ThreadFrameTimeFrameComparer());
return index >= 0 ? (ThreadFrameTime?)frames[index] : null;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b0c2bd11491424d188d23f3ae3e16b79
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,24 @@
using System;
namespace UnityEditor.Performance.ProfileAnalyzer
{
[Serializable]
internal struct ThreadFrameTime : IComparable<ThreadFrameTime>
{
public int frameIndex;
public float ms;
public float msIdle;
public ThreadFrameTime(int index, float msTime, float msTimeIdle)
{
frameIndex = index;
ms = msTime;
msIdle = msTimeIdle;
}
public int CompareTo(ThreadFrameTime other)
{
return ms.CompareTo(other.ms);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fb9ea80c859e94311bdc05e75704cede
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,105 @@
using System;
namespace UnityEditor.Performance.ProfileAnalyzer
{
/// <summary>
/// Individual Thread details
/// </summary>
internal struct ThreadIdentifier
{
/// <summary>Thread name with index combined. A profiler analyzer specific unique thread representation</summary>
public string threadNameWithIndex { get; private set; }
/// <summary>Thread name (may not be unique)</summary>
public string name { get; private set; }
/// <summary>Thread index. A 1 based id for threads with matching names. -1 indicates all threads with the same name, 0 if there is only one thread with this name</summary>
public int index { get; private set; }
/// <summary>Thread index id which means all threads of the same name</summary>
public static int kAll = -1;
/// <summary>Thread index id use when there is only one thread with this name</summary>
public static int kSingle = 0;
/// <summary>Initialise ThreadIdentifier</summary>
/// <param name="name">The thread name</param>
/// <param name="index">The thread index</param>
public ThreadIdentifier(string name, int index)
{
this.name = name;
this.index = index;
if (index == kAll)
threadNameWithIndex = string.Format("All:{0}", name);
else
threadNameWithIndex = string.Format("{0}:{1}", index, name);
}
/// <summary>Initialise ThreadIdentifier from another ThreadIdentifier</summary>
/// <param name="threadIdentifier">The other ThreadIdentifier</param>
public ThreadIdentifier(ThreadIdentifier threadIdentifier)
{
name = threadIdentifier.name;
index = threadIdentifier.index;
threadNameWithIndex = threadIdentifier.threadNameWithIndex;
}
/// <summary>Initialise ThreadIdentifier from a unique name</summary>
/// <param name="threadNameWithIndex">The unique name string (name with index)</param>
public ThreadIdentifier(string threadNameWithIndex)
{
this.threadNameWithIndex = threadNameWithIndex;
string[] tokens = threadNameWithIndex.Split(':');
if (tokens.Length >= 2)
{
name = tokens[1];
string indexString = tokens[0];
if (indexString == "All")
{
index = kAll;
}
else
{
int intValue;
if (Int32.TryParse(tokens[0], out intValue))
index = intValue;
else
index = kSingle;
}
}
else
{
index = kSingle;
name = threadNameWithIndex;
}
}
void UpdateThreadNameWithIndex()
{
if (index == kAll)
threadNameWithIndex = string.Format("All:{0}", name);
else
threadNameWithIndex = string.Format("{0}:{1}", index, name);
}
/// <summary>Set the name of the thread</summary>
/// <param name="newName">The name (without index)</param>
public void SetName(string newName)
{
name = newName;
UpdateThreadNameWithIndex();
}
/// <summary>Set the index of the thread name</summary>
/// <param name="newIndex">The index of the thread with the same name</param>
public void SetIndex(int newIndex)
{
index = newIndex;
UpdateThreadNameWithIndex();
}
/// <summary>Sets the index to indicate we want all threads (used for filtering against other ThreadIdentifiers)</summary>
public void SetAll()
{
SetIndex(kAll);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 977c7fa7681d742d591608f4dbb8b6af
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
namespace UnityEditor.Performance.ProfileAnalyzer
{
[Serializable]
internal class ThreadSelection
{
public List<string> groups;
public List<string> selection;
public bool empty
{
get
{
return groups == null || selection == null || groups.Count == 0 && selection.Count == 0;
}
}
public ThreadSelection()
{
groups = new List<string>();
selection = new List<string>();
}
public ThreadSelection(ThreadSelection threadSelection)
{
groups = new List<string>();
selection = new List<string>();
Set(threadSelection);
}
public void SetAll()
{
groups.Clear();
selection.Clear();
ThreadIdentifier allThreadSelection = new ThreadIdentifier("All", ThreadIdentifier.kAll);
groups.Add(allThreadSelection.threadNameWithIndex);
}
public void Set(string name)
{
groups.Clear();
selection.Clear();
selection.Add(name);
}
public void SetGroup(string groupName)
{
groups.Clear();
selection.Clear();
ThreadIdentifier allThreadSelection = new ThreadIdentifier(groupName, ThreadIdentifier.kAll);
groups.Add(allThreadSelection.threadNameWithIndex);
}
public void Set(ThreadSelection threadSelection)
{
groups.Clear();
selection.Clear();
if (threadSelection.groups != null)
groups.AddRange(threadSelection.groups);
if (threadSelection.selection != null)
selection.AddRange(threadSelection.selection);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: edb0539be3883af4b90ff66c35074ae0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,965 @@
using UnityEngine;
using System.IO;
using System;
using System.Collections.Generic;
using UnityEditor.IMGUI.Controls;
using UnityEngine.Assertions;
using System.Linq;
namespace UnityEditor.Performance.ProfileAnalyzer
{
class ThreadTreeViewItem : TreeViewItem
{
public readonly ThreadIdentifier threadIdentifier;
public ThreadTreeViewItem(int id, int depth, string displayName, ThreadIdentifier threadIdentifier) : base(id, depth, displayName)
{
this.threadIdentifier = threadIdentifier;
}
}
class ThreadTable : TreeView
{
const float kRowHeights = 20f;
readonly List<TreeViewItem> m_Rows = new List<TreeViewItem>(100);
List<string> m_ThreadNames;
List<string> m_ThreadUINames;
ThreadIdentifier m_AllThreadIdentifier;
ThreadSelection m_ThreadSelection;
GUIStyle activeLineStyle;
public bool StateChanged { get; private set; }
// All columns
public enum MyColumns
{
GroupName,
ThreadName,
State,
}
public enum SortOption
{
GroupName,
ThreadName,
State,
}
// Sort options per column
SortOption[] m_SortOptions =
{
SortOption.GroupName,
SortOption.ThreadName,
SortOption.State,
};
public enum ThreadSelected
{
Selected,
Partial,
NotSelected
};
public ThreadTable(TreeViewState state, MultiColumnHeader multicolumnHeader, List<string> threadNames, List<string> threadUINames, ThreadSelection threadSelection) : base(state, multicolumnHeader)
{
StateChanged = false;
m_AllThreadIdentifier = new ThreadIdentifier();
m_AllThreadIdentifier.SetName("All");
m_AllThreadIdentifier.SetAll();
Assert.AreEqual(m_SortOptions.Length, Enum.GetValues(typeof(MyColumns)).Length, "Ensure number of sort options are in sync with number of MyColumns enum values");
// Custom setup
rowHeight = kRowHeights;
showAlternatingRowBackgrounds = true;
columnIndexForTreeFoldouts = (int)(MyColumns.GroupName);
showBorder = true;
customFoldoutYOffset = (kRowHeights - EditorGUIUtility.singleLineHeight) * 0.5f; // center foldout in the row since we also center content. See RowGUI
// extraSpaceBeforeIconAndLabel = 0;
multicolumnHeader.sortingChanged += OnSortingChanged;
multicolumnHeader.visibleColumnsChanged += OnVisibleColumnsChanged;
m_ThreadNames = threadNames;
m_ThreadUINames = threadUINames;
m_ThreadSelection = new ThreadSelection(threadSelection);
#if UNITY_2018_3_OR_NEWER
this.foldoutOverride += DoFoldout;
#endif
Reload();
}
#if UNITY_2018_3_OR_NEWER
bool DoFoldout(Rect position, bool expandedstate, GUIStyle style)
{
return !(position.y < rowHeight) && GUI.Toggle(position, expandedstate, GUIContent.none, style);
}
#endif
public void ClearThreadSelection()
{
m_ThreadSelection.selection.Clear();
m_ThreadSelection.groups.Clear();
StateChanged = true;
Reload();
}
public void SelectMain()
{
m_ThreadSelection.selection.Clear();
m_ThreadSelection.groups.Clear();
foreach (var threadName in m_ThreadNames)
{
if (threadName.StartsWith("All:"))
continue;
if (threadName.EndsWith(":Main Thread")) // Usually just starts with 1:
m_ThreadSelection.selection.Add(threadName);
}
StateChanged = true;
Reload();
}
public void SelectCommon()
{
m_ThreadSelection.selection.Clear();
m_ThreadSelection.groups.Clear();
foreach (var threadName in m_ThreadNames)
{
if (threadName.StartsWith("All:"))
continue;
if (threadName.EndsWith(":Render Thread")) // Usually just starts with 1:
m_ThreadSelection.selection.Add(threadName);
if (threadName.EndsWith(":Main Thread")) // Usually just starts with 1:
m_ThreadSelection.selection.Add(threadName);
if (threadName.EndsWith(":Job.Worker")) // Mulitple jobs, number depends on processor setup
m_ThreadSelection.selection.Add(threadName);
}
StateChanged = true;
Reload();
}
public ThreadSelection GetThreadSelection()
{
return m_ThreadSelection;
}
protected int GetChildCount(ThreadIdentifier selectedThreadIdentifier, out int selected)
{
int count = 0;
int selectedCount = 0;
if (selectedThreadIdentifier.index == ThreadIdentifier.kAll)
{
if (selectedThreadIdentifier.name == "All")
{
for (int index = 0; index < m_ThreadNames.Count; ++index)
{
var threadNameWithIndex = m_ThreadNames[index];
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
if (threadIdentifier.index != ThreadIdentifier.kAll)
{
count++;
if (m_ThreadSelection.selection.Contains(threadNameWithIndex))
selectedCount++;
}
}
}
else
{
for (int index = 0; index < m_ThreadNames.Count; ++index)
{
var threadNameWithIndex = m_ThreadNames[index];
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
if (selectedThreadIdentifier.name == threadIdentifier.name &&
threadIdentifier.index != ThreadIdentifier.kAll)
{
count++;
if (m_ThreadSelection.selection.Contains(threadNameWithIndex))
selectedCount++;
}
}
}
}
selected = selectedCount;
return count;
}
protected override TreeViewItem BuildRoot()
{
int idForHiddenRoot = -1;
int depthForHiddenRoot = -1;
ProfileTreeViewItem root = new ProfileTreeViewItem(idForHiddenRoot, depthForHiddenRoot, "root", null);
int depth = 0;
var top = new ThreadTreeViewItem(-1, depth, m_AllThreadIdentifier.name, m_AllThreadIdentifier);
root.AddChild(top);
var expandList = new List<int>() {-1};
string lastThreadName = "";
TreeViewItem node = root;
for (int index = 0; index < m_ThreadNames.Count; ++index)
{
var threadNameWithIndex = m_ThreadNames[index];
if (threadNameWithIndex == m_AllThreadIdentifier.threadNameWithIndex)
continue;
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
var item = new ThreadTreeViewItem(index, depth, m_ThreadUINames[index], threadIdentifier);
if (threadIdentifier.name != lastThreadName)
{
// New threads at root
node = top;
depth = 0;
}
node.AddChild(item);
if (threadIdentifier.name != lastThreadName)
{
// Extra instances hang of the parent
lastThreadName = threadIdentifier.name;
node = item;
depth = 1;
}
}
SetExpanded(expandList);
SetupDepthsFromParentsAndChildren(root);
return root;
}
void BuildRowRecursive(IList<TreeViewItem> rows, TreeViewItem item)
{
//if (item.children == null)
// return;
if (!IsExpanded(item.id))
return;
foreach (ThreadTreeViewItem subNode in item.children)
{
rows.Add(subNode);
if (subNode.children != null)
BuildRowRecursive(rows, subNode);
}
}
void BuildAllRows(IList<TreeViewItem> rows, TreeViewItem rootItem)
{
rows.Clear();
if (rootItem == null)
return;
if (rootItem.children == null)
return;
foreach (ThreadTreeViewItem node in rootItem.children)
{
rows.Add(node);
if (node.children != null)
BuildRowRecursive(rows, node);
}
}
protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
{
BuildAllRows(m_Rows, root);
SortIfNeeded(m_Rows);
return m_Rows;
}
void OnSortingChanged(MultiColumnHeader _multiColumnHeader)
{
SortIfNeeded(GetRows());
}
void OnVisibleColumnsChanged(MultiColumnHeader _multiColumnHeader)
{
multiColumnHeader.ResizeToFit();
}
void SortIfNeeded(IList<TreeViewItem> rows)
{
if (rows.Count <= 1)
{
return;
}
if (multiColumnHeader.sortedColumnIndex == -1)
{
return; // No column to sort for (just use the order the data are in)
}
SortByMultipleColumns();
BuildAllRows(rows, rootItem);
Repaint();
}
string GetItemGroupName(ThreadTreeViewItem item)
{
string groupName;
string threadName = item.threadIdentifier.name;
threadName = ProfileData.GetThreadNameWithoutGroup(item.threadIdentifier.name, out groupName);
return groupName;
}
List<TreeViewItem> SortChildrenByMultipleColumns(List<TreeViewItem> children)
{
int[] sortedColumns = multiColumnHeader.state.sortedColumns;
if (sortedColumns.Length == 0)
{
return children;
}
var myTypes = children.Cast<ThreadTreeViewItem>();
var orderedQuery = InitialOrder(myTypes, sortedColumns);
for (int i = 0; i < sortedColumns.Length; i++)
{
SortOption sortOption = m_SortOptions[sortedColumns[i]];
bool ascending = multiColumnHeader.IsSortedAscending(sortedColumns[i]);
switch (sortOption)
{
case SortOption.GroupName:
orderedQuery = orderedQuery.ThenBy(l => GetItemGroupName(l), ascending);
break;
case SortOption.ThreadName:
orderedQuery = orderedQuery.ThenBy(l => GetItemDisplayText(l), ascending);
break;
case SortOption.State:
orderedQuery = orderedQuery.ThenBy(l => GetStateSort(l), ascending);
break;
}
}
return orderedQuery.Cast<TreeViewItem>().ToList();
}
void SortByMultipleColumns()
{
rootItem.children = SortChildrenByMultipleColumns(rootItem.children);
// Sort all the next level children too (As 'All' is the only item at the top)
for (int i = 0; i < rootItem.children.Count; i++)
{
var child = rootItem.children[0];
child.children = SortChildrenByMultipleColumns(child.children);
}
}
IOrderedEnumerable<ThreadTreeViewItem> InitialOrder(IEnumerable<ThreadTreeViewItem> myTypes, int[] history)
{
SortOption sortOption = m_SortOptions[history[0]];
bool ascending = multiColumnHeader.IsSortedAscending(history[0]);
switch (sortOption)
{
case SortOption.GroupName:
return myTypes.Order(l => GetItemGroupName(l), ascending);
case SortOption.ThreadName:
return myTypes.Order(l => GetItemDisplayText(l), ascending);
case SortOption.State:
return myTypes.Order(l => GetStateSort(l), ascending);
default:
Assert.IsTrue(false, "Unhandled enum");
break;
}
// default
return myTypes.Order(l => GetItemDisplayText(l), ascending);
}
protected override void RowGUI(RowGUIArgs args)
{
ThreadTreeViewItem item = (ThreadTreeViewItem)args.item;
for (int i = 0; i < args.GetNumVisibleColumns(); ++i)
{
CellGUI(args.GetCellRect(i), item, (MyColumns)args.GetColumn(i), ref args);
}
}
string GetStateSort(ThreadTreeViewItem item)
{
ThreadSelected threadSelected = GetThreadSelectedState(item.threadIdentifier);
string sortString = ((int)threadSelected).ToString() + GetItemDisplayText(item);
return sortString;
}
ThreadSelected GetThreadSelectedState(ThreadIdentifier selectedThreadIdentifier)
{
if (ProfileAnalyzer.MatchThreadFilter(selectedThreadIdentifier.threadNameWithIndex, m_ThreadSelection.selection))
return ThreadSelected.Selected;
// If querying the 'All' filter then check if all selected
if (selectedThreadIdentifier.threadNameWithIndex == m_AllThreadIdentifier.threadNameWithIndex)
{
// Check all threads without All in the name are selected
int count = 0;
int selectedCount = 0;
foreach (var threadNameWithIndex in m_ThreadNames)
{
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
if (threadIdentifier.index == ThreadIdentifier.kAll || threadIdentifier.index == ThreadIdentifier.kSingle)
continue;
if (m_ThreadSelection.selection.Contains(threadNameWithIndex))
selectedCount++;
count++;
}
if (selectedCount == count)
return ThreadSelected.Selected;
if (selectedCount > 0)
return ThreadSelected.Partial;
return ThreadSelected.NotSelected;
}
// Need to check 'All' and thread group All.
if (selectedThreadIdentifier.index == ThreadIdentifier.kAll)
{
// Count all threads that match this thread group
int count = 0;
foreach (var threadNameWithIndex in m_ThreadNames)
{
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
if (threadIdentifier.index == ThreadIdentifier.kAll || threadIdentifier.index == ThreadIdentifier.kSingle)
continue;
if (selectedThreadIdentifier.name != threadIdentifier.name)
continue;
count++;
}
// Count all the threads we have selected that match this thread group
int selectedCount = 0;
foreach (var threadNameWithIndex in m_ThreadSelection.selection)
{
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
if (selectedThreadIdentifier.name != threadIdentifier.name)
continue;
if (threadIdentifier.index > count)
continue;
selectedCount++;
}
if (selectedCount == count)
return ThreadSelected.Selected;
if (selectedCount > 0)
return ThreadSelected.Partial;
}
return ThreadSelected.NotSelected;
}
string GetItemDisplayText(ThreadTreeViewItem item)
{
int selectedChildren;
int childCount = GetChildCount(item.threadIdentifier, out selectedChildren);
string fullThreadName = item.threadIdentifier.name;
string groupName;
string threadName = ProfileData.GetThreadNameWithoutGroup(fullThreadName, out groupName);
string displayThreadName = multiColumnHeader.IsColumnVisible((int)MyColumns.GroupName) ? threadName : fullThreadName;
string text;
if (childCount <= 1)
{
text = item.displayName;
}
else if (selectedChildren != childCount)
{
text = string.Format("{0} ({1} of {2})", displayThreadName, selectedChildren, childCount);
}
else
{
text = string.Format("{0} (All)", displayThreadName);
}
return text;
}
void GetThreadTreeViewItemInfo(ThreadTreeViewItem item, out string text, out string tooltip)
{
text = GetItemDisplayText(item);
int selectedChildren;
int childCount = GetChildCount(item.threadIdentifier, out selectedChildren);
string groupName = GetItemGroupName(item);
if (childCount <= 1)
{
tooltip = (groupName == "") ? text : string.Format("{0}\n{1}", text, groupName);
}
else if (selectedChildren != childCount)
{
tooltip = (groupName == "") ? text : string.Format("{0}\n{1}", text, groupName);
}
else
{
tooltip = (groupName == "") ? text : string.Format("{0}\n{1}", text, groupName);
}
}
Rect DrawIndent(Rect rect, ThreadTreeViewItem item, ref RowGUIArgs args)
{
// The rect is assumed indented and sized after the content when pinging
float indent = GetContentIndent(item) + extraSpaceBeforeIconAndLabel;
rect.xMin += indent;
int iconRectWidth = 16;
int kSpaceBetweenIconAndText = 2;
// Draw icon
Rect iconRect = rect;
iconRect.width = iconRectWidth;
// iconRect.x += 7f;
Texture icon = args.item.icon;
if (icon != null)
GUI.DrawTexture(iconRect, icon, ScaleMode.ScaleToFit);
rect.xMin += icon == null ? 0 : iconRectWidth + kSpaceBetweenIconAndText;
return rect;
}
void CellGUI(Rect cellRect, ThreadTreeViewItem item, MyColumns column, ref RowGUIArgs args)
{
// Center cell rect vertically (makes it easier to place controls, icons etc in the cells)
CenterRectUsingSingleLineHeight(ref cellRect);
switch (column)
{
case MyColumns.ThreadName:
{
args.rowRect = cellRect;
// base.RowGUI(args); // Required to show tree indenting
// Draw manually to keep indenting while add a tooltip
Rect rect = cellRect;
if (Event.current.rawType == EventType.Repaint)
{
string text;
string tooltip;
GetThreadTreeViewItemInfo(item, out text, out tooltip);
var content = new GUIContent(text, tooltip);
if (activeLineStyle == null)
{
// activeLineStyle = DefaultStyles.boldLabel;
activeLineStyle = new GUIStyle(DefaultStyles.label);
activeLineStyle.normal.textColor = DefaultStyles.boldLabel.onActive.textColor;
}
// rect = DrawIndent(rect, item, ref args);
//bool mouseOver = rect.Contains(Event.current.mousePosition);
//DefaultStyles.label.Draw(rect, content, mouseOver, false, args.selected, args.focused);
// Must use this call to draw tooltip
EditorGUI.LabelField(rect, content, args.selected ? activeLineStyle : DefaultStyles.label);
}
}
break;
case MyColumns.GroupName:
{
Rect rect = cellRect;
if (Event.current.rawType == EventType.Repaint)
{
rect = DrawIndent(rect, item, ref args);
string groupName = GetItemGroupName(item);
var content = new GUIContent(groupName, groupName);
EditorGUI.LabelField(rect, content);
}
}
break;
case MyColumns.State:
bool oldState = GetThreadSelectedState(item.threadIdentifier) == ThreadSelected.Selected;
bool newState = EditorGUI.Toggle(cellRect, oldState);
if (newState != oldState)
{
if (item.threadIdentifier.threadNameWithIndex == m_AllThreadIdentifier.threadNameWithIndex)
{
// Record active groups
m_ThreadSelection.groups.Clear();
if (newState)
{
if (!m_ThreadSelection.groups.Contains(item.threadIdentifier.threadNameWithIndex))
m_ThreadSelection.groups.Add(item.threadIdentifier.threadNameWithIndex);
}
// Update selection
m_ThreadSelection.selection.Clear();
if (newState)
{
foreach (string threadNameWithIndex in m_ThreadNames)
{
if (threadNameWithIndex != m_AllThreadIdentifier.threadNameWithIndex)
{
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
if (threadIdentifier.index != ThreadIdentifier.kAll)
{
m_ThreadSelection.selection.Add(threadNameWithIndex);
}
}
}
}
}
else if (item.threadIdentifier.index == ThreadIdentifier.kAll)
{
// Record active groups
if (newState)
{
if (!m_ThreadSelection.groups.Contains(item.threadIdentifier.threadNameWithIndex))
m_ThreadSelection.groups.Add(item.threadIdentifier.threadNameWithIndex);
}
else
{
m_ThreadSelection.groups.Remove(item.threadIdentifier.threadNameWithIndex);
// When turning off a sub group, turn of the 'all' group too
m_ThreadSelection.groups.Remove(m_AllThreadIdentifier.threadNameWithIndex);
}
// Update selection
if (newState)
{
foreach (string threadNameWithIndex in m_ThreadNames)
{
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
if (threadIdentifier.name == item.threadIdentifier.name &&
threadIdentifier.index != ThreadIdentifier.kAll)
{
if (!m_ThreadSelection.selection.Contains(threadNameWithIndex))
m_ThreadSelection.selection.Add(threadNameWithIndex);
}
}
}
else
{
var removeSelection = new List<string>();
foreach (string threadNameWithIndex in m_ThreadSelection.selection)
{
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
if (threadIdentifier.name == item.threadIdentifier.name &&
threadIdentifier.index != ThreadIdentifier.kAll)
{
removeSelection.Add(threadNameWithIndex);
}
}
foreach (string threadNameWithIndex in removeSelection)
{
m_ThreadSelection.selection.Remove(threadNameWithIndex);
}
}
}
else
{
if (newState)
{
m_ThreadSelection.selection.Add(item.threadIdentifier.threadNameWithIndex);
}
else
{
m_ThreadSelection.selection.Remove(item.threadIdentifier.threadNameWithIndex);
// Turn off any group its in too
var groupIdentifier = new ThreadIdentifier(item.threadIdentifier);
groupIdentifier.SetAll();
m_ThreadSelection.groups.Remove(groupIdentifier.threadNameWithIndex);
// Turn of the 'all' group too
m_ThreadSelection.groups.Remove(m_AllThreadIdentifier.threadNameWithIndex);
}
}
StateChanged = true;
// Re-sort
SortIfNeeded(GetRows());
}
break;
}
}
// Misc
//--------
protected override bool CanMultiSelect(TreeViewItem item)
{
return false;
}
struct HeaderData
{
public GUIContent content;
public float width;
public float minWidth;
public bool autoResize;
public bool allowToggleVisibility;
public HeaderData(string name, string tooltip = "", float _width = 50, float _minWidth = 30, bool _autoResize = true, bool _allowToggleVisibility = true)
{
content = new GUIContent(name, tooltip);
width = _width;
minWidth = _minWidth;
autoResize = _autoResize;
allowToggleVisibility = _allowToggleVisibility;
}
}
public static MultiColumnHeaderState CreateDefaultMultiColumnHeaderState(float treeViewWidth)
{
var columnList = new List<MultiColumnHeaderState.Column>();
HeaderData[] headerData = new HeaderData[]
{
new HeaderData("Group", "Thread Group", 200, 100, true, false),
new HeaderData("Thread", "Thread Name", 350, 100, true, false),
new HeaderData("Show", "Check to show this thread in the analysis views", 40, 100, false, false),
};
foreach (var header in headerData)
{
columnList.Add(new MultiColumnHeaderState.Column
{
headerContent = header.content,
headerTextAlignment = TextAlignment.Left,
sortedAscending = true,
sortingArrowAlignment = TextAlignment.Left,
width = header.width,
minWidth = header.minWidth,
autoResize = header.autoResize,
allowToggleVisibility = header.allowToggleVisibility
});
}
;
var columns = columnList.ToArray();
Assert.AreEqual(columns.Length, Enum.GetValues(typeof(MyColumns)).Length, "Number of columns should match number of enum values: You probably forgot to update one of them.");
var state = new MultiColumnHeaderState(columns);
state.visibleColumns = new int[]
{
(int)MyColumns.GroupName,
(int)MyColumns.ThreadName,
(int)MyColumns.State,
};
return state;
}
protected override void SelectionChanged(IList<int> selectedIds)
{
base.SelectionChanged(selectedIds);
if (selectedIds.Count > 0)
{
}
}
}
internal class ThreadSelectionWindow : EditorWindow
{
ProfileAnalyzerWindow m_ProfileAnalyzerWindow;
TreeViewState m_ThreadTreeViewState;
//Static to store state between open/close
static MultiColumnHeaderState m_ThreadMulticolumnHeaderState;
ThreadTable m_ThreadTable;
List<string> m_ThreadNames;
List<string> m_ThreadUINames;
ThreadSelection m_OriginalThreadSelection;
bool m_EnableApplyButton = false;
bool m_EnableResetButton = false;
bool m_RequestClose;
internal static class Styles
{
public static readonly GUIContent reset = new GUIContent("Reset", "Reset selection to previous set");
public static readonly GUIContent clear = new GUIContent("Clear", "Clear selection below");
public static readonly GUIContent main = new GUIContent("Main Only", "Select Main Thread only");
public static readonly GUIContent common = new GUIContent("Common Set", "Select Common threads : Main, Render and Jobs");
public static readonly GUIContent apply = new GUIContent("Apply", "");
}
static public bool IsOpen()
{
#if UNITY_2019_3_OR_NEWER
return HasOpenInstances<ThreadSelectionWindow>();
#else
UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(typeof(ThreadSelectionWindow));
if (windows != null && windows.Length > 0)
return true;
return false;
#endif // UNITY_2019_3_OR_NEWER
}
static public ThreadSelectionWindow Open(float screenX, float screenY, ProfileAnalyzerWindow profileAnalyzerWindow, ThreadSelection threadSelection, List<string> threadNames, List<string> threadUINames)
{
ThreadSelectionWindow window = GetWindow<ThreadSelectionWindow>(true, "Threads");
window.minSize = new Vector2(380, 200);
window.position = new Rect(screenX, screenY, 500, 500);
window.SetData(profileAnalyzerWindow, threadSelection, threadNames, threadUINames);
window.Show();
return window;
}
void OnEnable()
{
m_RequestClose = false;
}
void CreateTable(ProfileAnalyzerWindow profileAnalyzerWindow, List<string> threadNames, List<string> threadUINames, ThreadSelection threadSelection)
{
if (m_ThreadTreeViewState == null)
m_ThreadTreeViewState = new TreeViewState();
int sortedColumn;
bool sortAscending;
if (m_ThreadMulticolumnHeaderState == null)
{
m_ThreadMulticolumnHeaderState = ThreadTable.CreateDefaultMultiColumnHeaderState(700);
sortedColumn = (int)ThreadTable.MyColumns.GroupName;
sortAscending = true;
}
else
{
// Remember last sort key
sortedColumn = m_ThreadMulticolumnHeaderState.sortedColumnIndex;
sortAscending = m_ThreadMulticolumnHeaderState.columns[sortedColumn].sortedAscending;
}
var multiColumnHeader = new MultiColumnHeader(m_ThreadMulticolumnHeaderState);
multiColumnHeader.SetSorting(sortedColumn, sortAscending);
multiColumnHeader.ResizeToFit();
m_ThreadTable = new ThreadTable(m_ThreadTreeViewState, multiColumnHeader, threadNames, threadUINames, threadSelection);
}
void SetData(ProfileAnalyzerWindow profileAnalyzerWindow, ThreadSelection threadSelection, List<string> threadNames, List<string> threadUINames)
{
m_ProfileAnalyzerWindow = profileAnalyzerWindow;
m_OriginalThreadSelection = threadSelection;
m_ThreadNames = threadNames;
m_ThreadUINames = threadUINames;
CreateTable(profileAnalyzerWindow, threadNames, threadUINames, threadSelection);
}
void OnDestroy()
{
// By design we now no longer apply the thread settings when closing the dialog.
// Apply must be clicked to set them.
// m_ProfileAnalyzerWindow.SetThreadSelection(m_ThreadTable.GetThreadSelection());
}
void OnGUI()
{
EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true));
GUIStyle style = new GUIStyle(GUI.skin.label);
style.alignment = TextAnchor.MiddleLeft;
GUILayout.Label("Select Thread : ", style);
EditorGUILayout.BeginHorizontal();
bool lastEnabled = GUI.enabled;
GUI.enabled = m_EnableResetButton;
if (GUILayout.Button(Styles.reset, GUILayout.Width(50)))
{
// Reset the thread window contents only
CreateTable(m_ProfileAnalyzerWindow, m_ThreadNames, m_ThreadUINames, m_OriginalThreadSelection);
m_EnableApplyButton = true;
m_EnableResetButton = false;
}
GUI.enabled = lastEnabled;
if (GUILayout.Button(Styles.clear, GUILayout.Width(50)))
{
m_ThreadTable.ClearThreadSelection();
}
if (GUILayout.Button(Styles.main, GUILayout.Width(100)))
{
m_ThreadTable.SelectMain();
}
if (GUILayout.Button(Styles.common, GUILayout.Width(100)))
{
m_ThreadTable.SelectCommon();
}
GUI.enabled = m_EnableApplyButton && !m_ProfileAnalyzerWindow.IsAnalysisRunning();
EditorGUILayout.Space();
if (GUILayout.Button(Styles.apply, GUILayout.Width(50)))
{
m_ProfileAnalyzerWindow.SetThreadSelection(m_ThreadTable.GetThreadSelection());
m_EnableApplyButton = false;
m_EnableResetButton = true;
}
GUI.enabled = lastEnabled;
EditorGUILayout.EndHorizontal();
if (m_ThreadTable != null)
{
Rect r = EditorGUILayout.GetControlRect(GUILayout.ExpandHeight(true));
m_ThreadTable.OnGUI(r);
}
EditorGUILayout.EndVertical();
}
void Update()
{
if (m_ThreadTable != null && m_ThreadTable.StateChanged)
{
m_EnableApplyButton = true;
m_EnableResetButton = true;
}
if (m_RequestClose)
Close();
}
void OnLostFocus()
{
m_RequestClose = true;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b643b14ae5b894e10b5d393e31f5c347
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,19 @@
namespace UnityEditor.Performance.ProfileAnalyzer
{
internal class TimingOptions
{
public enum TimingOption
{
Time,
Self,
};
public static string[] TimingOptionNames =
{
"Total",
"Self",
};
public static string Tooltip = "Marker timings :\n\nTotal : \tIncluding children\nSelf : \tExcluding children";
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 60eefcdcc882eb14f933f363fc2faef6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,163 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Performance.ProfileAnalyzer
{
internal class TopMarkerList
{
internal static class Styles
{
public static readonly GUIContent frameCosts = new GUIContent(" by frame costs", "Contains accumulated marker cost within the frame");
public static readonly GUIContent frameCounts = new GUIContent(" by frame counts", "Contains marker count within the frame");
}
public delegate float DrawFrameIndexButton(int frameIndex, ProfileDataView frameContext);
DrawFrameIndexButton m_DrawFrameIndexButton;
Draw2D m_2D;
DisplayUnits m_Units;
int m_WidthColumn0;
int m_WidthColumn1;
int m_WidthColumn2;
int m_WidthColumn3;
Color colorBar;
Color colorBarBackground;
public TopMarkerList(Draw2D draw2D, Units units,
int widthColumn0, int widthColumn1, int widthColumn2, int widthColumn3,
Color colorBar, Color colorBarBackground, DrawFrameIndexButton drawFrameIndexButton)
{
m_2D = draw2D;
SetUnits(units);
m_WidthColumn0 = widthColumn0;
m_WidthColumn1 = widthColumn1;
m_WidthColumn2 = widthColumn2;
m_WidthColumn3 = widthColumn3;
this.colorBar = colorBar;
this.colorBarBackground = colorBarBackground;
m_DrawFrameIndexButton = drawFrameIndexButton;
}
void SetUnits(Units units)
{
m_Units = new DisplayUnits(units);
}
public int DrawTopNumber(int topNumber, string[] topStrings, int[] topValues)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Top ", GUILayout.Width(30));
topNumber = EditorGUILayout.IntPopup(topNumber, topStrings, topValues, GUILayout.Width(40));
EditorGUILayout.LabelField(m_Units.Units == Units.Count ? Styles.frameCounts : Styles.frameCosts, GUILayout.Width(100));
EditorGUILayout.EndHorizontal();
return topNumber;
}
public List<FrameTime> GetTopNByCount(MarkerData marker, int n)
{
if (marker.frames.Count <= 0)
return new List<FrameTime>();
List<FrameTime> sortedFrames = new List<FrameTime>(marker.frames);
sortedFrames.Sort(FrameTime.CompareCountDescending);
var frameTimes = new List<FrameTime>();
for (int i = 0; i < Math.Min(n, sortedFrames.Count); i++)
frameTimes.Add(sortedFrames[i]);
return frameTimes;
}
public List<FrameTime> GetTopNByTime(MarkerData marker, int n)
{
if (marker.frames.Count <= 0)
return new List<FrameTime>();
List<FrameTime> sortedFrames = new List<FrameTime>(marker.frames);
sortedFrames.Sort(FrameTime.CompareMsDescending);
var frameTimes = new List<FrameTime>();
for (int i = 0; i < Math.Min(n, sortedFrames.Count); i++)
frameTimes.Add(sortedFrames[i]);
return frameTimes;
}
public List<FrameTime> GetTopN(MarkerData marker, int n, bool showCount)
{
return showCount ? GetTopNByCount(marker, n) : GetTopNByTime(marker, n);
}
public int Draw(MarkerData marker, ProfileDataView markerContext, int topNumber, string[] topStrings, int[] topValues)
{
GUIStyle style = GUI.skin.label;
float w = m_WidthColumn0;
float h = style.lineHeight;
float ySpacing = 2;
float barHeight = h - ySpacing;
EditorGUILayout.BeginVertical(GUILayout.Width(w + m_WidthColumn1 + m_WidthColumn2 + m_WidthColumn3));
topNumber = DrawTopNumber(topNumber, topStrings, topValues);
/*
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("", GUILayout.Width(w));
EditorGUILayout.LabelField("Value", GUILayout.Width(LayoutSize.WidthColumn1));
EditorGUILayout.LabelField("Frame", GUILayout.Width(LayoutSize.WidthColumn2));
EditorGUILayout.EndHorizontal();
*/
// var frameSummary = m_ProfileSingleView.analysis.GetFrameSummary();
float barMax = marker.msMax; // frameSummary.msMax
if (m_Units.Units == Units.Count)
{
barMax = marker.countMax;
}
// Marker frames are ordered by frame time
// If we are sorting by count then we need to apply a sort
bool showCount = m_Units.Units == Units.Count;
List<FrameTime> frames = GetTopN(marker, topNumber, showCount);
foreach (FrameTime frameTime in frames)
{
float barValue = (m_Units.Units == Units.Count) ? frameTime.count : frameTime.ms;
float barLength = Math.Min((w * barValue) / barMax, w);
EditorGUILayout.BeginHorizontal();
if (m_2D.DrawStart(w, h, Draw2D.Origin.TopLeft, style))
{
m_2D.DrawFilledBox(0, ySpacing, barLength, barHeight, colorBar);
m_2D.DrawFilledBox(barLength, ySpacing, w - barLength, barHeight, colorBarBackground);
m_2D.DrawEnd();
Rect rect = GUILayoutUtility.GetLastRect();
GUI.Label(rect, new GUIContent("", m_Units.ToTooltipString(barValue, true)));
}
EditorGUILayout.LabelField(m_Units.ToGUIContentWithTooltips(barValue, true), GUILayout.Width(m_WidthColumn2));
if (m_DrawFrameIndexButton != null)
m_DrawFrameIndexButton(frameTime.frameIndex, markerContext);
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
}
// Show blank space for missing frames
var content = new GUIContent("", "");
Vector2 size = GUI.skin.button.CalcSize(content);
h = Math.Max(barHeight, size.y);
for (int i = frames.Count; i < topNumber; i++)
{
EditorGUILayout.BeginHorizontal();
GUILayout.Label("", GUILayout.Height(h));
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndVertical();
return topNumber;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4e7e7da63135a4cda9b821c5d96c7b4a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,863 @@
using System;
using System.Text;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Profiling;
namespace UnityEditor.Performance.ProfileAnalyzer
{
internal class TopMarkers
{
internal class RangeSettings
{
public readonly ProfileDataView dataView;
public readonly int depthFilter;
public readonly List<string> nameFilters;
public readonly List<string> nameExcludes;
public readonly TimingOptions.TimingOption timingOption;
public readonly int threadSelectionCount;
public RangeSettings(ProfileDataView dataView, int depthFilter, List<string> nameFilters, List<string> nameExcludes, TimingOptions.TimingOption timingOption, int threadSelectionCount)
{
// Make a copy rather than keeping a reference
this.dataView = dataView==null ? new ProfileDataView() : new ProfileDataView(dataView);
this.depthFilter = depthFilter;
this.nameFilters = nameFilters;
this.nameExcludes = nameExcludes;
this.timingOption = timingOption;
this.threadSelectionCount = threadSelectionCount;
}
public override int GetHashCode()
{
int hash = 13;
hash = (hash * 7) + dataView.GetHashCode();
hash = (hash * 7) + depthFilter.GetHashCode();
hash = (hash * 7) + nameFilters.GetHashCode();
hash = (hash * 7) + nameExcludes.GetHashCode();
hash = (hash * 7) + timingOption.GetHashCode();
hash = (hash * 7) + threadSelectionCount.GetHashCode();
return hash;
}
public override bool Equals(object b)
{
if (System.Object.ReferenceEquals(null, b))
{
return false;
}
if (System.Object.ReferenceEquals(this, b))
{
return true;
}
if (b.GetType() != this.GetType())
{
return false;
}
return IsEqual((RangeSettings)b);
}
bool IsEqual(RangeSettings b)
{
if (timingOption != b.timingOption)
return false;
if (b.dataView == null && dataView != null)
return false;
// Check contents of data view (the reference will definitly not match as we made a copy)
if (b.dataView != null)
{
// Only need to check data, analysis and selectedIndices
if (dataView.data != b.dataView.data)
return false;
if (dataView.analysis != b.dataView.analysis)
return false;
if (dataView.selectedIndices != b.dataView.selectedIndices)
return false;
if (dataView.selectedIndices.Count != b.dataView.selectedIndices.Count)
return false;
// Want to check if contents match, not just if reference is the same
for (int i = 0; i < dataView.selectedIndices.Count; i++)
{
if (dataView.selectedIndices[i] != b.dataView.selectedIndices[i])
return false;
}
}
if (depthFilter != b.depthFilter)
return false;
if (threadSelectionCount != b.threadSelectionCount)
return false;
if (nameFilters.Count != b.nameFilters.Count)
return false;
if (nameExcludes.Count != b.nameExcludes.Count)
return false;
// Want to check if contents match, not just if reference is the same
for (int i = 0; i < nameFilters.Count; i++)
{
if (nameFilters[i] != b.nameFilters[i])
return false;
}
for (int i = 0; i < nameExcludes.Count; i++)
{
if (nameExcludes[i] != b.nameExcludes[i])
return false;
}
return true;
}
public static bool operator==(RangeSettings a, RangeSettings b)
{
// If both are null, or both are same instance, return true.
if (System.Object.ReferenceEquals(a, b))
{
return true;
}
// If one is null, but not both, return false.
if (((object)a == null) || ((object)b == null))
{
return false;
}
return a.IsEqual(b);
}
public static bool operator!=(RangeSettings a, RangeSettings b)
{
return !(a == b);
}
}
internal class Settings
{
public readonly int barCount;
public readonly float timeRange;
public readonly bool includeOthers;
public readonly bool includeUnaccounted;
public RangeSettings rangeSettings { get ; private set ; }
public Settings(RangeSettings rangeSettings, int barCount, float timeRange, bool includeOthers, bool includeUnaccounted)
{
this.rangeSettings = rangeSettings;
this.barCount = barCount;
this.timeRange = timeRange;
this.includeOthers = includeOthers;
this.includeUnaccounted = includeUnaccounted;
}
public override int GetHashCode()
{
int hash = 13;
hash = (hash * 7) + rangeSettings.GetHashCode();
hash = (hash * 7) + barCount.GetHashCode();
hash = (hash * 7) + timeRange.GetHashCode();
hash = (hash * 7) + includeOthers.GetHashCode();
hash = (hash * 7) + includeUnaccounted.GetHashCode();
return hash;
}
public override bool Equals(object b)
{
if (System.Object.ReferenceEquals(null, b))
{
return false;
}
if (System.Object.ReferenceEquals(this, b))
{
return true;
}
if (b.GetType() != this.GetType())
{
return false;
}
return IsEqual((Settings)b);
}
bool IsEqual(Settings b)
{
if (rangeSettings != b.rangeSettings)
return false;
if (barCount != b.barCount)
return false;
if (timeRange != b.timeRange)
return false;
if (includeOthers != b.includeOthers)
return false;
if (includeUnaccounted != b.includeUnaccounted)
return false;
return true;
}
public static bool operator==(Settings a, Settings b)
{
// If both are null, or both are same instance, return true.
if (System.Object.ReferenceEquals(a, b))
{
return true;
}
// If one is null, but not both, return false.
if (((object)a == null) || ((object)b == null))
{
return false;
}
return a.IsEqual(b);
}
public static bool operator!=(Settings a, Settings b)
{
return !(a == b);
}
}
internal enum SummaryType
{
Marker,
Other,
Unaccounted
}
internal struct MarkerSummaryEntry
{
public readonly string name;
public readonly float msAtMedian; // At the median frame (Miliseconds)
public readonly float msMedian; // median value for marker over all frames (Miliseconds) on frame medianFrameIndex
public readonly float x;
public readonly float w;
public readonly int medianFrameIndex;
public readonly SummaryType summaryType;
public MarkerSummaryEntry(string name, float msAtMedian, float msMedian, float x, float w, int medianFrameIndex, SummaryType summaryType)
{
this.name = name;
this.msAtMedian = msAtMedian;
this.msMedian = msMedian;
this.x = x;
this.w = w;
this.medianFrameIndex = medianFrameIndex;
this.summaryType = summaryType;
}
}
internal class MarkerSummary
{
public List<MarkerSummaryEntry> entry;
public float totalTime;
public MarkerSummary()
{
entry = new List<MarkerSummaryEntry>();
totalTime = 0f;
}
}
Settings m_CurrentSettings; // Current settings, including latest RangeSettings
RangeSettings m_RequestedRangeSettings; // Next requested range setting set by SetData
float m_TimeRange;
bool m_TimeRangeDirty; // Set when renquested range settings change
MarkerSummary m_MarkerSummary;
internal static class Styles
{
public static readonly GUIContent menuItemSelectFramesInAll = new GUIContent("Select Frames that contain this marker (within whole data set)", "");
public static readonly GUIContent menuItemSelectFramesInCurrent = new GUIContent("Select Frames that contain this marker (within current selection)", "");
//public static readonly GUIContent menuItemClearSelection = new GUIContent("Clear Selection");
public static readonly GUIContent menuItemSelectFramesAll = new GUIContent("Select All");
public static readonly GUIContent menuItemAddToIncludeFilter = new GUIContent("Add to Include Filter", "");
public static readonly GUIContent menuItemAddToExcludeFilter = new GUIContent("Add to Exclude Filter", "");
public static readonly GUIContent menuItemRemoveFromIncludeFilter = new GUIContent("Remove from Include Filter", "");
public static readonly GUIContent menuItemRemoveFromExcludeFilter = new GUIContent("Remove from Exclude Filter", "");
public static readonly GUIContent menuItemSetAsParentMarkerFilter = new GUIContent("Set as Parent Marker Filter", "");
public static readonly GUIContent menuItemClearParentMarkerFilter = new GUIContent("Clear Parent Marker Filter", "");
public static readonly GUIContent menuItemCopyToClipboard = new GUIContent("Copy to Clipboard", "");
}
private static class Content
{
public static readonly string frameTime = L10n.Tr("{0} Frame time on median frame");
public static readonly string multipleThreads = L10n.Tr("Multiple threads selected\nSelect a single thread for an overview");
public static readonly string totalTimeAllDepths = L10n.Tr("{0} Total time of all markers at all depths");
public static readonly string totalTimeAtSpecificDepth = L10n.Tr("{0} Total time of all markers at Depth {1}");
public static readonly string selectSelf = L10n.Tr("For an overview select Analysis Type Self");
public static readonly string selectTotal = L10n.Tr("To include child times select Analysis Type Total");
public static readonly string selfTimeAllDepths = L10n.Tr("{0} Self time of markers at all depths");
public static readonly string selfTimeAtSpecificDepth = L10n.Tr("{0} Self time of markers at Depth {1}");
public static readonly string tooltip = L10n.Tr("{0}\n{1:f2}% ({2} on median frame {3})\n\nMedian marker time (in currently selected frames)\n{4} on frame {5}");
}
ProfileAnalyzerWindow m_ProfileAnalyzerWindow;
Draw2D m_2D;
Color m_BackgroundColor;
Color m_TextColor;
public TopMarkers(ProfileAnalyzerWindow profileAnalyzerWindow, Draw2D draw2D, Color backgroundColor, Color textColor)
{
m_ProfileAnalyzerWindow = profileAnalyzerWindow;
m_2D = draw2D;
m_BackgroundColor = backgroundColor;
m_TextColor = textColor;
m_CurrentSettings = new Settings(new RangeSettings(null, 0, null, null, TimingOptions.TimingOption.Time, 0), 0, 0, false, false);
m_TimeRangeDirty = true;
}
string ToDisplayUnits(float ms, bool showUnits = false, int limitToDigits = 5)
{
return m_ProfileAnalyzerWindow.ToDisplayUnits(ms, showUnits, limitToDigits);
}
public void SetData(ProfileDataView dataView, int depthFilter, List<string> nameFilters, List<string> nameExcludes, TimingOptions.TimingOption timingOption, int threadSelectionCount)
{
m_RequestedRangeSettings = new RangeSettings(dataView, depthFilter, nameFilters, nameExcludes, timingOption, threadSelectionCount);
if (m_CurrentSettings.rangeSettings != m_RequestedRangeSettings)
m_TimeRangeDirty = true;
}
float CalculateTopMarkerTimeRange(RangeSettings rangeSettings)
{
if (rangeSettings == null)
return 0.0f;
if (rangeSettings.dataView == null)
return 0.0f;
ProfileAnalysis analysis = rangeSettings.dataView.analysis;
if (analysis == null)
return 0.0f;
var frameSummary = analysis.GetFrameSummary();
if (frameSummary == null)
return 0.0f;
int depthFilter = rangeSettings.depthFilter;
List<string> nameFilters = rangeSettings.nameFilters;
List<string> nameExcludes = rangeSettings.nameExcludes;
var markers = analysis.GetMarkers();
float range = 0;
foreach (var marker in markers)
{
if (depthFilter != ProfileAnalyzer.kDepthAll && marker.minDepth != depthFilter)
{
continue;
}
if (nameFilters.Count > 0)
{
if (!m_ProfileAnalyzerWindow.NameInFilterList(marker.name, nameFilters))
continue;
}
if (nameExcludes.Count > 0)
{
if (m_ProfileAnalyzerWindow.NameInExcludeList(marker.name, nameExcludes))
continue;
}
range += marker.msAtMedian;
}
// Minimum is the frame time range
// As we can have unaccounted markers
if (range < frameSummary.msMedian)
range = frameSummary.msMedian;
return range;
}
public float GetTopMarkerTimeRange()
{
if (m_TimeRangeDirty)
{
Profiler.BeginSample("CalculateTopMarkerTimeRange");
// Use latest requested rather than current (as current may not yet be updated)
m_TimeRange = CalculateTopMarkerTimeRange(m_RequestedRangeSettings);
m_TimeRangeDirty = false;
Profiler.EndSample();
}
return m_TimeRange;
}
public MarkerSummary CalculateTopMarkers()
{
if (m_CurrentSettings.rangeSettings.dataView == null)
return null;
ProfileAnalysis analysis = m_CurrentSettings.rangeSettings.dataView.analysis;
if (analysis == null)
return null;
FrameSummary frameSummary = analysis.GetFrameSummary();
if (frameSummary == null)
return new MarkerSummary();
var markers = analysis.GetMarkers();
if (markers == null)
return new MarkerSummary();
float timeRange = m_CurrentSettings.timeRange;
int depthFilter = m_CurrentSettings.rangeSettings.depthFilter;
List<string> nameFilters = m_CurrentSettings.rangeSettings.nameFilters;
List<string> nameExcludes = m_CurrentSettings.rangeSettings.nameExcludes;
// Show marker graph
float x = 0;
float width = 1.0f;
int max = m_CurrentSettings.barCount;
int at = 0;
float other = 0.0f;
if (timeRange <= 0.0f)
timeRange = frameSummary.msMedian;
float msToWidth = width / timeRange;
float totalMarkerTime = 0;
MarkerSummary markerSummary = new MarkerSummary();
foreach (var marker in markers)
{
float msAtMedian = MarkerData.GetMsAtMedian(marker);
totalMarkerTime += msAtMedian;
if (depthFilter != ProfileAnalyzer.kDepthAll && marker.minDepth != depthFilter)
{
continue;
}
if (nameFilters.Count > 0)
{
if (!m_ProfileAnalyzerWindow.NameInFilterList(marker.name, nameFilters))
continue;
}
if (nameExcludes.Count > 0)
{
if (m_ProfileAnalyzerWindow.NameInExcludeList(marker.name, nameExcludes))
continue;
}
if (at < max)
{
float w = CaculateWidth(x, msAtMedian, msToWidth, width);
float msMedian = MarkerData.GetMsMedian(marker);
int medianFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(marker.medianFrameIndex, m_CurrentSettings.rangeSettings.dataView);
markerSummary.entry.Add(new MarkerSummaryEntry(marker.name, msAtMedian, msMedian, x, w, medianFrameIndex, SummaryType.Marker));
x += w;
}
else
{
other += msAtMedian;
if (!m_CurrentSettings.includeOthers)
break;
}
at++;
}
if (m_CurrentSettings.includeOthers && other > 0.0f)
{
float w = CaculateWidth(x, other, msToWidth, width);
markerSummary.entry.Add(new MarkerSummaryEntry("Other", other, 0f, x, w, -1, SummaryType.Other));
x += w;
}
if (m_CurrentSettings.includeUnaccounted && totalMarkerTime < frameSummary.msMedian)
{
float unaccounted = frameSummary.msMedian - totalMarkerTime;
float w = CaculateWidth(x, unaccounted, msToWidth, width);
markerSummary.entry.Add(new MarkerSummaryEntry("Unaccounted", unaccounted, 0f, x, w, -1, SummaryType.Unaccounted));
x += w;
}
markerSummary.totalTime = totalMarkerTime;
return markerSummary;
}
GenericMenu GenerateActiveContextMenu(string markerName, Event evt)
{
GenericMenu menu = new GenericMenu();
menu.AddItem(Styles.menuItemSelectFramesInAll, false, () => m_ProfileAnalyzerWindow.SelectFramesContainingMarker(markerName, false));
menu.AddItem(Styles.menuItemSelectFramesInCurrent, false, () => m_ProfileAnalyzerWindow.SelectFramesContainingMarker(markerName, true));
if (m_ProfileAnalyzerWindow.AllSelected())
menu.AddDisabledItem(Styles.menuItemSelectFramesAll);
else
menu.AddItem(Styles.menuItemSelectFramesAll, false, () => m_ProfileAnalyzerWindow.SelectAllFrames());
menu.AddSeparator("");
if (!m_CurrentSettings.rangeSettings.nameFilters.Contains(markerName))
menu.AddItem(Styles.menuItemAddToIncludeFilter, false, () => m_ProfileAnalyzerWindow.AddToIncludeFilter(markerName));
else
menu.AddItem(Styles.menuItemRemoveFromIncludeFilter, false, () => m_ProfileAnalyzerWindow.RemoveFromIncludeFilter(markerName));
if (!m_CurrentSettings.rangeSettings.nameExcludes.Contains(markerName))
menu.AddItem(Styles.menuItemAddToExcludeFilter, false, () => m_ProfileAnalyzerWindow.AddToExcludeFilter(markerName));
else
menu.AddItem(Styles.menuItemRemoveFromExcludeFilter, false, () => m_ProfileAnalyzerWindow.RemoveFromExcludeFilter(markerName));
menu.AddSeparator("");
menu.AddItem(Styles.menuItemSetAsParentMarkerFilter, false, () => m_ProfileAnalyzerWindow.SetAsParentMarkerFilter(markerName));
menu.AddItem(Styles.menuItemClearParentMarkerFilter, false, () => m_ProfileAnalyzerWindow.SetAsParentMarkerFilter(""));
menu.AddSeparator("");
menu.AddItem(Styles.menuItemCopyToClipboard, false, () => CopyToClipboard(evt, markerName));
return menu;
}
GenericMenu GenerateDisabledContextMenu(string markerName)
{
GenericMenu menu = new GenericMenu();
menu.AddDisabledItem(Styles.menuItemSelectFramesInAll);
menu.AddDisabledItem(Styles.menuItemSelectFramesInCurrent);
menu.AddDisabledItem(Styles.menuItemSelectFramesAll);
menu.AddSeparator("");
if (!m_CurrentSettings.rangeSettings.nameFilters.Contains(markerName))
menu.AddDisabledItem(Styles.menuItemAddToIncludeFilter);
else
menu.AddDisabledItem(Styles.menuItemRemoveFromIncludeFilter);
if (!m_CurrentSettings.rangeSettings.nameExcludes.Contains(markerName))
menu.AddDisabledItem(Styles.menuItemAddToExcludeFilter);
else
menu.AddDisabledItem(Styles.menuItemRemoveFromExcludeFilter);
menu.AddSeparator("");
menu.AddDisabledItem(Styles.menuItemSetAsParentMarkerFilter);
menu.AddDisabledItem(Styles.menuItemClearParentMarkerFilter);
menu.AddSeparator("");
menu.AddDisabledItem(Styles.menuItemCopyToClipboard);
return menu;
}
public GUIContent ConstructTimeRangeText()
{
StringBuilder sb = new StringBuilder();
ProfileAnalysis analysis = m_CurrentSettings.rangeSettings.dataView.analysis;
int depthFilter = m_CurrentSettings.rangeSettings.depthFilter;
FrameSummary frameSummary = analysis.GetFrameSummary();
string frameTimeString = ToDisplayUnits(frameSummary.msMedian, true, 0);
string accountedTimeString = ToDisplayUnits(m_MarkerSummary.totalTime, true, 0);
sb.AppendFormat(Content.frameTime, frameTimeString);
// Note m_CurrentSettings.rangeSettings.dataView.analysis.GetThreads contains all thread names, not just the filtered threads
bool singleThread = m_CurrentSettings.rangeSettings.threadSelectionCount == 1;
if (depthFilter == ProfileAnalyzer.kDepthAll)
{
if (m_CurrentSettings.rangeSettings.timingOption == TimingOptions.TimingOption.Time)
{
sb.Append("\n");
sb.AppendFormat(Content.totalTimeAllDepths, accountedTimeString);
if (singleThread)
{
sb.Append("\n\n");
sb.Append(Content.selectSelf);
}
}
else
{
sb.Append("\n");
sb.AppendFormat(Content.selfTimeAllDepths,accountedTimeString);
}
}
else
{
if (m_CurrentSettings.rangeSettings.timingOption == TimingOptions.TimingOption.Self)
{
sb.Append("\n");
sb.AppendFormat(Content.selfTimeAtSpecificDepth,accountedTimeString, depthFilter);
if (singleThread)
{
sb.Append("\n\n");
sb.Append(Content.selectTotal);
}
}
else
{
sb.Append("\n");
sb.AppendFormat(Content.totalTimeAtSpecificDepth, accountedTimeString, depthFilter);
}
}
if (!singleThread)
{
sb.Append("\n\n");
sb.Append(Content.multipleThreads);
}
string timeRangeString = ToDisplayUnits(m_CurrentSettings.timeRange, true);
return new GUIContent(timeRangeString, sb.ToString());
}
public void Draw(Rect rect, Color barColor, int barCount, float timeRange, Color selectedBackground, Color selectedBorder, Color selectedText, bool includeOthers, bool includeUnaccounted)
{
Settings newSettings = new Settings(m_RequestedRangeSettings, barCount, timeRange, includeOthers, includeUnaccounted);
if (m_CurrentSettings != newSettings)
{
Profiler.BeginSample("CalculateTopMarkers");
m_CurrentSettings = newSettings;
m_MarkerSummary = CalculateTopMarkers();
Profiler.EndSample();
}
if (m_CurrentSettings.rangeSettings == null)
return;
if (m_CurrentSettings.rangeSettings.dataView == null)
return;
if (m_CurrentSettings.rangeSettings.dataView.analysis == null)
return;
if (m_MarkerSummary == null || m_MarkerSummary.entry == null)
return;
ProfileAnalysis analysis = m_CurrentSettings.rangeSettings.dataView.analysis;
FrameSummary frameSummary = analysis.GetFrameSummary();
if (frameSummary == null)
return;
if (frameSummary.count <= 0)
return;
var markers = analysis.GetMarkers();
if (markers == null)
return;
Profiler.BeginSample("DrawHeader");
int rangeLabelWidth = 60;
// After the marker graph we want an indication of the time range
if (frameSummary.count > 0)
{
Rect rangeLabelRect = new Rect(rect.x + rect.width - rangeLabelWidth, rect.y, rangeLabelWidth, rect.height);
GUIContent timeRangeText = ConstructTimeRangeText();
GUI.Label(rangeLabelRect, timeRangeText);
}
// Reduce the size of the marker graph for the button/label we just added
rect.width -= rangeLabelWidth;
// Show marker graph
float y = 0;
float width = rect.width;
float height = rect.height;
var selectedPairingMarkerName = m_ProfileAnalyzerWindow.GetSelectedMarkerName();
if (timeRange <= 0.0f)
timeRange = frameSummary.msMedian;
Profiler.EndSample();
if (m_2D.DrawStart(rect, Draw2D.Origin.BottomLeft))
{
Profiler.BeginSample("DrawBars");
m_2D.DrawFilledBox(0, y, width, height, m_BackgroundColor);
foreach (MarkerSummaryEntry entry in m_MarkerSummary.entry)
{
String name = entry.name;
float x = entry.x * width;
float w = entry.w * width;
if (entry.summaryType == SummaryType.Marker)
{
if (name == selectedPairingMarkerName)
{
DrawBar(x, y, w, height, selectedBackground, selectedBorder, true);
}
else
{
DrawBar(x, y, w, height, barColor, selectedBorder, false);
}
}
else
{
// Others / Unaccounted
Color color = entry.summaryType == SummaryType.Unaccounted ? new Color(barColor.r * 0.5f, barColor.g * 0.5f, barColor.b * 0.5f, barColor.a) : barColor;
DrawBar(x, y, w, height, color, selectedBorder, false);
}
}
Profiler.EndSample();
m_2D.DrawEnd();
}
GUIStyle centreAlignStyle = new GUIStyle(GUI.skin.label);
centreAlignStyle.alignment = TextAnchor.MiddleCenter;
centreAlignStyle.normal.textColor = m_TextColor;
GUIStyle leftAlignStyle = new GUIStyle(GUI.skin.label);
leftAlignStyle.alignment = TextAnchor.MiddleLeft;
leftAlignStyle.normal.textColor = m_TextColor;
Color contentColor = GUI.contentColor;
int frameSummaryMedianFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(frameSummary.medianFrameIndex, m_CurrentSettings.rangeSettings.dataView);
Profiler.BeginSample("DrawText");
foreach (MarkerSummaryEntry entry in m_MarkerSummary.entry)
{
String name = entry.name;
float x = entry.x * width;
float w = entry.w * width;
float msAtMedian = entry.msAtMedian;
if (entry.summaryType == SummaryType.Marker)
{
Rect labelRect = new Rect(rect.x + x, rect.y, w, rect.height);
GUIStyle style = centreAlignStyle;
String displayName = "";
if (w >= 20)
{
displayName = name;
Vector2 size = centreAlignStyle.CalcSize(new GUIContent(name));
if (size.x > w)
{
var words = name.Split('.');
displayName = words[words.Length - 1];
style = leftAlignStyle;
}
}
float percentAtMedian = msAtMedian * 100 / timeRange;
string tooltip = string.Format(
Content.tooltip,
name,
percentAtMedian, ToDisplayUnits(msAtMedian, true, 0), frameSummaryMedianFrameIndex,
ToDisplayUnits(entry.msMedian, true, 0), entry.medianFrameIndex);
if (name == selectedPairingMarkerName)
style.normal.textColor = selectedText;
else
style.normal.textColor = m_TextColor;
GUI.Label(labelRect, new GUIContent(displayName, tooltip), style);
Event current = Event.current;
if (labelRect.Contains(current.mousePosition))
{
if (current.type == EventType.ContextClick)
{
GenericMenu menu;
if (!m_ProfileAnalyzerWindow.IsAnalysisRunning())
menu = GenerateActiveContextMenu(name, current);
else
menu = GenerateDisabledContextMenu(name);
menu.ShowAsContext();
current.Use();
}
if (current.type == EventType.MouseDown)
{
m_ProfileAnalyzerWindow.SelectMarker(name);
m_ProfileAnalyzerWindow.RequestRepaint();
}
}
}
else
{
DrawBarText(rect, x, w, msAtMedian, name, timeRange, leftAlignStyle, frameSummaryMedianFrameIndex);
}
}
Profiler.EndSample();
}
static float CaculateWidth(float x, float msTime, float msToWidth, float width)
{
float w = msTime * msToWidth;
if (x + w > width)
w = width - x;
return w;
}
float DrawBar(float x, float y, float w, float height, Color barColor, Color selectedBorder, bool withBorder)
{
if (withBorder)
m_2D.DrawFilledBox(x + 1, y + 1, w, height - 2, selectedBorder);
m_2D.DrawFilledBox(x + 2, y + 2, w - 2, height - 4, barColor);
return w;
}
float DrawBarText(Rect rect, float x, float w, float msTime, string name, float timeRange, GUIStyle leftAlignStyle, int medianFrameIndex)
{
float width = rect.width;
Rect labelRect = new Rect(rect.x + x, rect.y, w, rect.height);
float percent = msTime / timeRange * 100;
GUIStyle style = leftAlignStyle;
string tooltip = string.Format("{0}\n{1:f2}% ({2} on median frame {3})",
name,
percent,
ToDisplayUnits(msTime, true, 0),
medianFrameIndex);
GUI.Label(labelRect, new GUIContent("", tooltip), style);
Event current = Event.current;
if (labelRect.Contains(current.mousePosition))
{
if (current.type == EventType.ContextClick)
{
GenericMenu menu = new GenericMenu();
if (!m_ProfileAnalyzerWindow.IsAnalysisRunning())
menu.AddItem(Styles.menuItemSelectFramesAll, false, m_ProfileAnalyzerWindow.SelectAllFrames);
else
menu.AddDisabledItem(Styles.menuItemSelectFramesAll);
menu.ShowAsContext();
current.Use();
}
if (current.type == EventType.MouseDown)
{
m_ProfileAnalyzerWindow.SelectMarker(null);
m_ProfileAnalyzerWindow.RequestRepaint();
}
}
return w;
}
void CopyToClipboard(Event current, string text)
{
EditorGUIUtility.systemCopyBuffer = text;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 406c1c524495d4e0283f7848bbbfedc9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,236 @@
using System;
using UnityEngine.Assertions;
using UnityEngine;
namespace UnityEditor.Performance.ProfileAnalyzer
{
/// <summary>Unit type identifier</summary>
internal enum Units
{
/// <summary>Time in milliseconds</summary>
Milliseconds,
/// <summary>Time in microseconds</summary>
Microseconds,
/// <summary>Count of number of instances</summary>
Count,
};
internal class DisplayUnits
{
public static readonly string[] UnitNames =
{
"Milliseconds",
"Microseconds",
"Count",
};
public static readonly int[] UnitValues = (int[])Enum.GetValues(typeof(Units));
public readonly Units Units;
const bool kShowFullValueWhenBelowZero = true;
const int kTooltipDigitsNumber = 7;
public DisplayUnits(Units units)
{
Assert.AreEqual(UnitNames.Length, UnitValues.Length, "Number of UnitNames should match number of enum values UnitValues: You probably forgot to update one of them.");
Units = units;
}
public string Postfix()
{
switch (Units)
{
default:
case Units.Milliseconds:
return "ms";
case Units.Microseconds:
return "us";
case Units.Count:
return "";
}
}
int ClampToRange(int value, int min, int max)
{
if (value < min)
value = min;
if (value > max)
value = max;
return value;
}
string RemoveTrailingZeros(string numberStr, int minNumberStringLength)
{
// Find out string length without trailing zeroes.
var strLenWithoutTrailingZeros = numberStr.Length;
while (strLenWithoutTrailingZeros > minNumberStringLength && numberStr[strLenWithoutTrailingZeros - 1] == '0')
strLenWithoutTrailingZeros--;
// Remove hanging '.' in case all zeroes can be omitted.
if (strLenWithoutTrailingZeros > 0 && numberStr[strLenWithoutTrailingZeros - 1] == '.')
strLenWithoutTrailingZeros--;
return strLenWithoutTrailingZeros == numberStr.Length ? numberStr : numberStr.Substring(0, strLenWithoutTrailingZeros);
}
public string ToString(float ms, bool showUnits, int limitToNDigits, bool showFullValueWhenBelowZero = false)
{
float value = ms;
int unitPower = -3;
int minNumberStringLength = -1;
int maxDecimalPlaces = 0;
float minValueShownWhenUsingLimitedDecimalPlaces = 1f;
switch (Units)
{
default:
case Units.Milliseconds:
maxDecimalPlaces = 2;
minValueShownWhenUsingLimitedDecimalPlaces = 0.01f;
break;
case Units.Microseconds:
value *= 1000f;
unitPower -= 3;
if (value < 100)
{
maxDecimalPlaces = 1;
minValueShownWhenUsingLimitedDecimalPlaces = 0.1f;
}
else
{
maxDecimalPlaces = 0;
minValueShownWhenUsingLimitedDecimalPlaces = 1f;
}
break;
case Units.Count:
showUnits = false;
break;
}
int sgn = Math.Sign(value);
if (value < 0)
value = -value;
int numberOfDecimalPlaces = maxDecimalPlaces;
int unitsTextLength = showUnits ? 2 : 0;
int signTextLength = sgn == -1 ? 1 : 0;
if (limitToNDigits > 0 && value > float.Epsilon)
{
int numberOfSignificantFigures = limitToNDigits;
if (!showFullValueWhenBelowZero)
numberOfSignificantFigures -= unitsTextLength + signTextLength;
int valueExp = (int)Math.Log10(value);
// Less than 1 values needs exponent correction as (int) rounds to the upper negative.
if (value < 1)
valueExp -= 1;
int originalUnitPower = unitPower;
float limitRange = (float)Math.Pow(10, numberOfSignificantFigures);
if (limitRange > 0)
{
if (value >= limitRange)
{
while (value >= 1000f && unitPower < 9)
{
value /= 1000f;
unitPower += 3;
valueExp -= 3;
}
}
else if (showFullValueWhenBelowZero) // Only upscale and change unit type if we want to see exact number.
{
while (value < 0.01f && unitPower > -9)
{
value *= 1000f;
unitPower -= 3;
valueExp += 3;
}
}
}
if (unitPower != originalUnitPower)
{
showUnits = true;
unitsTextLength = 2;
numberOfSignificantFigures = limitToNDigits;
if (!showFullValueWhenBelowZero)
numberOfSignificantFigures -= unitsTextLength + signTextLength;
}
// Use all allowed digits to display significant digits if we have any beyond maxDecimalPlaces
int numberOfDigitsBeforeDecimalPoint = 1 + Math.Max(0, valueExp);
if (showFullValueWhenBelowZero)
{
numberOfDecimalPlaces = numberOfSignificantFigures - numberOfDigitsBeforeDecimalPoint;
minNumberStringLength = numberOfDigitsBeforeDecimalPoint + signTextLength + maxDecimalPlaces + 1;
}
else
numberOfDecimalPlaces = ClampToRange(numberOfSignificantFigures - numberOfDigitsBeforeDecimalPoint, 0, maxDecimalPlaces);
}
value *= sgn;
string numberStr;
if (value < minValueShownWhenUsingLimitedDecimalPlaces && showFullValueWhenBelowZero)
{
numberStr = string.Format("{0}", value);
}
else
{
string formatString = string.Concat("{0:f", numberOfDecimalPlaces, "}");
numberStr = string.Format(formatString, value);
}
// Remove trailing 0 if any from string
if (minNumberStringLength > 0 && numberStr.Length > 0)
numberStr = RemoveTrailingZeros(numberStr, minNumberStringLength);
if (!showUnits)
return numberStr;
string siUnitString = GetSIUnitString(unitPower) + "s";
return string.Concat(numberStr, siUnitString);
}
public static string GetSIUnitString(int unitPower)
{
// https://en.wikipedia.org/wiki/Metric_prefix
switch (unitPower)
{
case -9:
return "n";
case -6:
return "u";
case -3:
return "m";
case 0:
return "";
case 3:
return "k";
case 6:
return "M";
case 9:
return "G";
}
return "?";
}
public string ToTooltipString(double ms, bool showUnits, int frameIndex = -1)
{
if (frameIndex >= 0)
return string.Format("{0} on frame {1}", ToString((float)ms, showUnits, kTooltipDigitsNumber, kShowFullValueWhenBelowZero), frameIndex);
return ToString((float)ms, showUnits, kTooltipDigitsNumber, kShowFullValueWhenBelowZero);
}
public GUIContent ToGUIContentWithTooltips(float ms, bool showUnits = false, int limitToNDigits = 5, int frameIndex = -1)
{
return new GUIContent(ToString(ms, showUnits, limitToNDigits), ToTooltipString(ms, true, frameIndex));
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0a92e3733078e4ca68373f1d6c83f584
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,10 @@
{
"name": "Unity.Performance.Profile-Analyzer.Editor",
"references": [],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 211d499e772b640dbb43b9de9b957156
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: