Unity-jump-proj

This commit is contained in:
2024-09-09 11:07:16 +03:00
parent 2c29906bbf
commit fd96a5627d
13707 changed files with 866380 additions and 0 deletions

View File

@ -0,0 +1,373 @@
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()
{
AnalyticsResult result = EditorAnalytics.RegisterEventWithLimit(k_EventTopicName, k_MaxEventsPerHour, k_MaxEventItems, k_VendorKey);
if (result == AnalyticsResult.Ok)
s_EnableAnalytics = true;
}
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;
// 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;
}
public static bool SendUIUsageModeEvent(UIUsageMode uiUsageMode, float durationInSeconds)
{
if (!s_EnableAnalytics)
return false;
// 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;
}
public static bool SendUIVisibilityEvent(UIVisibility uiVisibility, float durationInSeconds, bool show)
{
if (!s_EnableAnalytics)
return false;
// 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;
}
public static bool SendUIResizeEvent(UIResizeView uiResizeView, float durationInSeconds, float width, float height, bool isDocked)
{
if (!s_EnableAnalytics)
return false;
// 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;
}
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,1511 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityEngine.Assertions;
namespace UnityEditor.Performance.ProfileAnalyzer
{
class ComparisonTreeViewItem : TreeViewItem
{
public MarkerPairing data { get; set; }
public GUIContent[] cachedRowString;
public ComparisonTreeViewItem(int id, int depth, string displayName, MarkerPairing data) : base(id, depth, displayName)
{
this.data = data;
cachedRowString = null;
}
}
class ComparisonTable : TreeView
{
Draw2D m_2D;
ProfileDataView m_LeftDataView;
ProfileDataView m_RightDataView;
Color m_LeftColor;
Color m_RightColor;
List<MarkerPairing> m_Pairings;
bool m_HideRemovedMarkers;
ProfileAnalyzerWindow m_ProfileAnalyzerWindow;
float m_DiffRange;
float m_CountDiffRange;
float m_CountMeanDiffRange;
double m_TotalDiffRange;
const float kRowHeights = 20f;
readonly List<TreeViewItem> m_Rows = new List<TreeViewItem>(100);
// All columns
public enum MyColumns
{
Name,
State,
LeftMedian,
LeftBar,
RightBar,
RightMedian,
Diff,
DiffPercent,
AbsDiff,
LeftCount,
LeftCountBar,
RightCountBar,
RightCount,
CountDiff,
CountDiffPercent,
AbsCountDiff,
LeftCountMean,
LeftCountMeanBar,
RightCountMeanBar,
RightCountMean,
CountMeanDiff,
CountMeanDiffPercent,
AbsCountMeanDiff,
LeftTotal,
LeftTotalBar,
RightTotalBar,
RightTotal,
TotalDiff,
TotalDiffPercent,
AbsTotalDiff,
LeftDepth,
RightDepth,
DepthDiff,
LeftThreads,
RightThreads,
}
static int m_MaxColumns;
public enum SortOption
{
Name,
State,
LeftMedian,
RightMedian,
Diff,
ReverseDiff,
DiffPercent,
AbsDiff,
LeftCount,
RightCount,
CountDiff,
ReverseCountDiff,
CountDiffPercent,
AbsCountDiff,
LeftCountMean,
RightCountMean,
CountMeanDiff,
ReverseCountMeanDiff,
CountMeanDiffPercent,
AbsCountMeanDiff,
LeftTotal,
RightTotal,
TotalDiff,
ReverseTotalDiff,
TotalDiffPercent,
AbsTotalDiff,
LeftDepth,
RightDepth,
DepthDiff,
LeftThreads,
RightThreads,
}
// Sort options per column
SortOption[] m_SortOptions =
{
SortOption.Name,
SortOption.State,
SortOption.LeftMedian,
SortOption.ReverseDiff,
SortOption.Diff,
SortOption.RightMedian,
SortOption.Diff,
SortOption.DiffPercent,
SortOption.AbsDiff,
SortOption.LeftCount,
SortOption.ReverseCountDiff,
SortOption.CountDiff,
SortOption.RightCount,
SortOption.CountDiff,
SortOption.CountDiffPercent,
SortOption.AbsCountDiff,
SortOption.LeftCountMean,
SortOption.ReverseCountMeanDiff,
SortOption.CountMeanDiff,
SortOption.RightCountMean,
SortOption.CountMeanDiff,
SortOption.CountMeanDiffPercent,
SortOption.AbsCountMeanDiff,
SortOption.LeftTotal,
SortOption.ReverseTotalDiff,
SortOption.TotalDiff,
SortOption.RightTotal,
SortOption.TotalDiff,
SortOption.TotalDiffPercent,
SortOption.AbsTotalDiff,
SortOption.LeftDepth,
SortOption.RightDepth,
SortOption.DepthDiff,
SortOption.LeftThreads,
SortOption.RightThreads,
};
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 menuItemSetAsRemoveMarker = new GUIContent("Remove Marker", "");
public static readonly GUIContent menuItemCopyToClipboard = new GUIContent("Copy to Clipboard", "");
public static readonly GUIContent invalidEntry = new GUIContent("-");
}
public ComparisonTable(TreeViewState state, MultiColumnHeader multicolumnHeader, ProfileDataView left, ProfileDataView right, List<MarkerPairing> pairings, bool hideRemovedMarkers, ProfileAnalyzerWindow profileAnalyzerWindow, Draw2D draw2D, Color leftColor, Color rightColor) : base(state, multicolumnHeader)
{
m_2D = draw2D;
m_LeftDataView = left;
m_RightDataView = right;
m_LeftColor = leftColor;
m_RightColor = rightColor;
m_Pairings = pairings;
m_HideRemovedMarkers = hideRemovedMarkers;
m_ProfileAnalyzerWindow = profileAnalyzerWindow;
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);
float minDiff = float.MaxValue;
float maxDiff = 0.0f;
double totalMinDiff = float.MaxValue;
double totalMaxDiff = 0.0f;
float countMinDiff = float.MaxValue;
float countMaxDiff = 0.0f;
float countMeanMinDiff = float.MaxValue;
float countMeanMaxDiff = 0.0f;
for (int index = 0; index < m_Pairings.Count; ++index)
{
var pairing = m_Pairings[index];
if (!m_ProfileAnalyzerWindow.DoesMarkerPassFilter(pairing.name))
continue;
double timeIgnored;
double msTotal;
if (m_HideRemovedMarkers && IsFullyIgnored(pairing, out timeIgnored, out msTotal))
continue;
var item = new ComparisonTreeViewItem(index, 0, pairing.name, pairing);
root.AddChild(item);
float diff = Diff(item);
if (diff < minDiff)
minDiff = diff;
if (diff > maxDiff && diff < float.MaxValue)
maxDiff = diff;
double totalDiff = TotalDiff(item);
if (totalDiff < totalMinDiff)
totalMinDiff = totalDiff;
if (totalDiff > totalMaxDiff && totalDiff < float.MaxValue)
totalMaxDiff = totalDiff;
float countDiff = CountDiff(item);
if (countDiff < countMinDiff)
countMinDiff = countDiff;
if (countDiff > countMaxDiff && countDiff < float.MaxValue)
countMaxDiff = countDiff;
float countMeanDiff = CountMeanDiff(item);
if (countMeanDiff < countMeanMinDiff)
countMeanMinDiff = countMeanDiff;
if (countMeanDiff > countMeanMaxDiff && countMeanDiff < float.MaxValue)
countMeanMaxDiff = countMeanDiff;
}
m_DiffRange = Math.Max(Math.Abs(minDiff), Math.Abs(maxDiff));
m_TotalDiffRange = Math.Max(Math.Abs(totalMinDiff), Math.Abs(totalMaxDiff));
m_CountDiffRange = Math.Max(Math.Abs(countMinDiff), Math.Abs(countMaxDiff));
m_CountMeanDiffRange = Math.Max(Math.Abs(countMeanMinDiff), Math.Abs(countMeanMaxDiff));
return root;
}
protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
{
m_Rows.Clear();
if (rootItem != null && rootItem.children != null)
{
foreach (ComparisonTreeViewItem 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.SetComparisonModeColumns(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 (var node in rootItem.children)
{
rows.Add(node);
}
Repaint();
}
void SortByMultipleColumns()
{
var sortedColumns = multiColumnHeader.state.sortedColumns;
if (sortedColumns.Length == 0)
{
return;
}
var myTypes = rootItem.children.Cast<ComparisonTreeViewItem>();
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.State:
orderedQuery = orderedQuery.ThenBy(l => State(l), ascending);
break;
case SortOption.LeftMedian:
orderedQuery = orderedQuery.ThenBy(l => LeftMedianSorting(l), ascending);
break;
case SortOption.RightMedian:
orderedQuery = orderedQuery.ThenBy(l => RightMedianSorting(l), ascending);
break;
case SortOption.Diff:
orderedQuery = orderedQuery.ThenBy(l => Diff(l), ascending);
break;
case SortOption.ReverseDiff:
orderedQuery = orderedQuery.ThenBy(l => - Diff(l), ascending);
break;
case SortOption.DiffPercent:
orderedQuery = orderedQuery.ThenBy(l => DiffPercent(l), ascending).ThenBy(l => Diff(l), ascending);
break;
case SortOption.AbsDiff:
orderedQuery = orderedQuery.ThenBy(l => AbsDiff(l), ascending);
break;
case SortOption.LeftCount:
orderedQuery = orderedQuery.ThenBy(l => LeftCount(l), ascending);
break;
case SortOption.RightCount:
orderedQuery = orderedQuery.ThenBy(l => RightCount(l), ascending);
break;
case SortOption.CountDiff:
orderedQuery = orderedQuery.ThenBy(l => CountDiff(l), ascending);
break;
case SortOption.ReverseCountDiff:
orderedQuery = orderedQuery.ThenBy(l => - CountDiff(l), ascending);
break;
case SortOption.CountDiffPercent:
orderedQuery = orderedQuery.ThenBy(l => CountDiffPercent(l), ascending).ThenBy(l => CountDiff(l), ascending);
break;
case SortOption.AbsCountDiff:
orderedQuery = orderedQuery.ThenBy(l => AbsCountDiff(l), ascending);
break;
case SortOption.LeftCountMean:
orderedQuery = orderedQuery.ThenBy(l => LeftCountMean(l), ascending);
break;
case SortOption.RightCountMean:
orderedQuery = orderedQuery.ThenBy(l => RightCountMean(l), ascending);
break;
case SortOption.CountMeanDiff:
orderedQuery = orderedQuery.ThenBy(l => CountMeanDiff(l), ascending);
break;
case SortOption.ReverseCountMeanDiff:
orderedQuery = orderedQuery.ThenBy(l => - CountMeanDiff(l), ascending);
break;
case SortOption.CountMeanDiffPercent:
orderedQuery = orderedQuery.ThenBy(l => CountMeanDiffPercent(l), ascending).ThenBy(l => CountMeanDiff(l), ascending);
break;
case SortOption.AbsCountMeanDiff:
orderedQuery = orderedQuery.ThenBy(l => AbsCountMeanDiff(l), ascending);
break;
case SortOption.LeftTotal:
orderedQuery = orderedQuery.ThenBy(l => LeftTotal(l), ascending);
break;
case SortOption.RightTotal:
orderedQuery = orderedQuery.ThenBy(l => RightTotal(l), ascending);
break;
case SortOption.TotalDiff:
orderedQuery = orderedQuery.ThenBy(l => TotalDiff(l), ascending);
break;
case SortOption.ReverseTotalDiff:
orderedQuery = orderedQuery.ThenBy(l => - TotalDiff(l), ascending);
break;
case SortOption.TotalDiffPercent:
orderedQuery = orderedQuery.ThenBy(l => TotalDiffPercent(l), ascending).ThenBy(l => TotalDiff(l), ascending);
break;
case SortOption.AbsTotalDiff:
orderedQuery = orderedQuery.ThenBy(l => AbsTotalDiff(l), ascending);
break;
case SortOption.LeftDepth:
orderedQuery = orderedQuery.ThenBy(l => LeftMinDepth(l), ascending);
break;
case SortOption.RightDepth:
orderedQuery = orderedQuery.ThenBy(l => RightMinDepth(l), ascending);
break;
case SortOption.DepthDiff:
orderedQuery = orderedQuery.ThenBy(l => DepthDiff(l), ascending);
break;
case SortOption.LeftThreads:
orderedQuery = orderedQuery.ThenBy(l => l.cachedRowString != null ? l.cachedRowString[(int)MyColumns.LeftThreads].text : LeftThreads(l), ascending);
break;
case SortOption.RightThreads:
orderedQuery = orderedQuery.ThenBy(l => l.cachedRowString != null ? l.cachedRowString[(int)MyColumns.RightThreads].text : RightThreads(l), ascending);
break;
}
}
rootItem.children = orderedQuery.Cast<TreeViewItem>().ToList();
}
MarkerData GetLeftMarker(MarkerPairing pairing)
{
if (pairing.leftIndex < 0)
return null;
List<MarkerData> markers = m_LeftDataView.analysis.GetMarkers();
if (pairing.leftIndex >= markers.Count)
return null;
return markers[pairing.leftIndex];
}
MarkerData GetRightMarker(MarkerPairing pairing)
{
if (pairing.rightIndex < 0)
return null;
List<MarkerData> markers = m_RightDataView.analysis.GetMarkers();
if (pairing.rightIndex >= markers.Count)
return null;
return markers[pairing.rightIndex];
}
MarkerData GetLeftMarker(ComparisonTreeViewItem item)
{
return GetLeftMarker(item.data);
}
MarkerData GetRightMarker(ComparisonTreeViewItem item)
{
return GetRightMarker(item.data);
}
string LeftFirstThread(ComparisonTreeViewItem item)
{
return m_ProfileAnalyzerWindow.GetUIThreadName(MarkerData.GetFirstThread(GetLeftMarker(item)));
}
string RightFirstThread(ComparisonTreeViewItem item)
{
return m_ProfileAnalyzerWindow.GetUIThreadName(MarkerData.GetFirstThread(GetRightMarker(item)));
}
string GetThreadNames(MarkerData marker)
{
if (marker == null)
return "";
var uiNames = new List<string>();
foreach (string threadNameWithIndex in marker.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();
}
string LeftThreads(ComparisonTreeViewItem item)
{
return GetThreadNames(GetLeftMarker(item));
}
string RightThreads(ComparisonTreeViewItem item)
{
return GetThreadNames(GetRightMarker(item));
}
float LeftMedianSorting(ComparisonTreeViewItem item)
{
var marker = GetLeftMarker(item);
if (marker == null)
return -1f;
return MarkerData.GetMsMedian(marker);
}
float LeftMedian(ComparisonTreeViewItem item)
{
return MarkerData.GetMsMedian(GetLeftMarker(item));
}
int LeftMedianFrameIndex(ComparisonTreeViewItem item)
{
return MarkerData.GetMedianFrameIndex(GetLeftMarker(item));
}
float RightMedianSorting(ComparisonTreeViewItem item)
{
var marker = GetRightMarker(item);
if (marker == null)
return -1f;
return MarkerData.GetMsMedian(marker);
}
float RightMedian(ComparisonTreeViewItem item)
{
return MarkerData.GetMsMedian(GetRightMarker(item));
}
int RightMedianFrameIndex(ComparisonTreeViewItem item)
{
return MarkerData.GetMedianFrameIndex(GetRightMarker(item));
}
float Diff(ComparisonTreeViewItem item)
{
return RightMedian(item) - LeftMedian(item);
}
float DiffPercent(float left, float right)
{
if (left == 0f)
return float.MaxValue;
if (right == 0f)
return float.MinValue;
float diff = right - left;
return (100f * diff) / left;
}
float DiffPercent(double left, double right)
{
if (left == 0.0)
return float.MaxValue;
if (right == 0.0)
return float.MinValue;
double diff = right - left;
return (float)((100.0 * diff) / left);
}
float DiffPercent(ComparisonTreeViewItem item)
{
float right = RightMedian(item);
float left = LeftMedian(item);
return DiffPercent(left, right);
}
float AbsDiff(ComparisonTreeViewItem item)
{
return Math.Abs(Diff(item));
}
float LeftCount(ComparisonTreeViewItem item)
{
return MarkerData.GetCount(GetLeftMarker(item));
}
float RightCount(ComparisonTreeViewItem item)
{
return MarkerData.GetCount(GetRightMarker(item));
}
float CountDiff(ComparisonTreeViewItem item)
{
return RightCount(item) - LeftCount(item);
}
float CountDiffPercent(ComparisonTreeViewItem item)
{
float right = RightCount(item);
float left = LeftCount(item);
return DiffPercent(left, right);
}
float AbsCountDiff(ComparisonTreeViewItem item)
{
return Math.Abs(CountDiff(item));
}
float LeftCountMean(ComparisonTreeViewItem item)
{
return MarkerData.GetCountMean(GetLeftMarker(item));
}
float RightCountMean(ComparisonTreeViewItem item)
{
return MarkerData.GetCountMean(GetRightMarker(item));
}
float CountMeanDiff(ComparisonTreeViewItem item)
{
return RightCountMean(item) - LeftCountMean(item);
}
float CountMeanDiffPercent(ComparisonTreeViewItem item)
{
float right = RightCountMean(item);
float left = LeftCountMean(item);
return DiffPercent(left, right);
}
float AbsCountMeanDiff(ComparisonTreeViewItem item)
{
return Math.Abs(CountMeanDiff(item));
}
double LeftTotal(ComparisonTreeViewItem item)
{
return MarkerData.GetMsTotal(GetLeftMarker(item));
}
double RightTotal(ComparisonTreeViewItem item)
{
return MarkerData.GetMsTotal(GetRightMarker(item));
}
double TotalDiff(ComparisonTreeViewItem item)
{
return RightTotal(item) - LeftTotal(item);
}
float TotalDiffPercent(ComparisonTreeViewItem item)
{
double right = RightTotal(item);
double left = LeftTotal(item);
return DiffPercent(left, right);
}
double AbsTotalDiff(ComparisonTreeViewItem item)
{
return Math.Abs(TotalDiff(item));
}
int LeftMinDepth(ComparisonTreeViewItem item)
{
return MarkerData.GetMinDepth(GetLeftMarker(item));
}
int RightMinDepth(ComparisonTreeViewItem item)
{
return MarkerData.GetMinDepth(GetRightMarker(item));
}
int LeftMaxDepth(ComparisonTreeViewItem item)
{
return MarkerData.GetMaxDepth(GetLeftMarker(item));
}
int RightMaxDepth(ComparisonTreeViewItem item)
{
return MarkerData.GetMaxDepth(GetRightMarker(item));
}
int DepthDiff(ComparisonTreeViewItem item)
{
if (item.data.leftIndex < 0)
return int.MaxValue;
if (item.data.rightIndex < 0)
return int.MaxValue - 1;
return RightMinDepth(item) - LeftMinDepth(item);
}
double TimeRemoved(ComparisonTreeViewItem item)
{
double removed;
removed = MarkerData.GetTimeRemoved(GetLeftMarker(item));
if (removed > 0.0)
return removed;
removed = MarkerData.GetTimeRemoved(GetRightMarker(item));
if (removed > 0.0)
return removed;
return 0.0;
}
double TimeIgnored(MarkerPairing pairing)
{
double ignored;
ignored = MarkerData.GetTimeIgnored(GetLeftMarker(pairing));
if (ignored > 0.0)
return ignored;
ignored = MarkerData.GetTimeIgnored(GetRightMarker(pairing));
if (ignored > 0.0)
return ignored;
return 0.0;
}
double TimeIgnored(ComparisonTreeViewItem item)
{
double ignored;
ignored = MarkerData.GetTimeIgnored(GetLeftMarker(item));
if (ignored > 0.0)
return ignored;
ignored = MarkerData.GetTimeIgnored(GetRightMarker(item));
if (ignored > 0.0)
return ignored;
return 0.0;
}
bool IsFullyIgnored(MarkerPairing pairing, out double timeIgnored, out double msTotal)
{
MarkerData left = GetLeftMarker(pairing);
MarkerData right = GetRightMarker(pairing);
double leftTimeIgnored = MarkerData.GetTimeIgnored(left);
double rightTimeIgnored = MarkerData.GetTimeIgnored(right);
double leftMsTotal = MarkerData.GetMsTotal(left);
double rightMsTotal = MarkerData.GetMsTotal(right);
bool result = false;
if (leftTimeIgnored > 0.0 || rightTimeIgnored > 0.0)
{
if (leftMsTotal == 0.0 && rightMsTotal == 0.0)
result = true;
}
if (leftTimeIgnored > 0.0)
{
timeIgnored = leftTimeIgnored;
msTotal = leftMsTotal;
}
else
{
timeIgnored = rightTimeIgnored;
msTotal = rightMsTotal;
}
return result;
}
bool IsFullyIgnored(ComparisonTreeViewItem item, out double timeIgnored, out double msTotal)
{
return IsFullyIgnored(item.data, out timeIgnored, out msTotal);
}
int State(ComparisonTreeViewItem item)
{
if (TimeRemoved(item) > 0.0)
{
return -3;
}
if (TimeIgnored(item) > 0.0)
{
double timeIgnored;
double msTotal;
bool removed = IsFullyIgnored(item, out timeIgnored, out msTotal);
if (removed)
return -2;
else
return -1;
}
return 0;
}
IOrderedEnumerable<ComparisonTreeViewItem> InitialOrder(IEnumerable<ComparisonTreeViewItem> 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.State:
return myTypes.Order(l => State(l), ascending);
case SortOption.LeftMedian:
return myTypes.Order(l => LeftMedianSorting(l), ascending);
case SortOption.RightMedian:
return myTypes.Order(l => RightMedianSorting(l), ascending);
case SortOption.Diff:
return myTypes.Order(l => Diff(l), ascending);
case SortOption.ReverseDiff:
return myTypes.Order(l => - Diff(l), ascending);
case SortOption.DiffPercent:
return myTypes.Order(l => DiffPercent(l), ascending).ThenBy(l => Diff(l), ascending);
case SortOption.AbsDiff:
return myTypes.Order(l => AbsDiff(l), ascending);
case SortOption.LeftCount:
return myTypes.Order(l => LeftCount(l), ascending);
case SortOption.RightCount:
return myTypes.Order(l => RightCount(l), ascending);
case SortOption.CountDiff:
return myTypes.Order(l => CountDiff(l), ascending);
case SortOption.ReverseCountDiff:
return myTypes.Order(l => - CountDiff(l), ascending);
case SortOption.CountDiffPercent:
return myTypes.Order(l => CountDiffPercent(l), ascending).ThenBy(l => CountDiff(l), ascending);
case SortOption.AbsCountDiff:
return myTypes.Order(l => AbsCountDiff(l), ascending);
case SortOption.LeftCountMean:
return myTypes.Order(l => LeftCountMean(l), ascending);
case SortOption.RightCountMean:
return myTypes.Order(l => RightCountMean(l), ascending);
case SortOption.CountMeanDiff:
return myTypes.Order(l => CountMeanDiff(l), ascending);
case SortOption.ReverseCountMeanDiff:
return myTypes.Order(l => - CountMeanDiff(l), ascending);
case SortOption.CountMeanDiffPercent:
return myTypes.Order(l => CountMeanDiffPercent(l), ascending).ThenBy(l => CountMeanDiff(l), ascending);
case SortOption.AbsCountMeanDiff:
return myTypes.Order(l => AbsCountMeanDiff(l), ascending);
case SortOption.LeftTotal:
return myTypes.Order(l => LeftTotal(l), ascending);
case SortOption.RightTotal:
return myTypes.Order(l => RightTotal(l), ascending);
case SortOption.TotalDiff:
return myTypes.Order(l => TotalDiff(l), ascending);
case SortOption.ReverseTotalDiff:
return myTypes.Order(l => - TotalDiff(l), ascending);
case SortOption.TotalDiffPercent:
return myTypes.Order(l => TotalDiffPercent(l), ascending).ThenBy(l => TotalDiff(l), ascending);
case SortOption.AbsTotalDiff:
return myTypes.Order(l => AbsTotalDiff(l), ascending);
case SortOption.LeftDepth:
return myTypes.Order(l => LeftMinDepth(l), ascending);
case SortOption.RightDepth:
return myTypes.Order(l => RightMinDepth(l), ascending);
case SortOption.DepthDiff:
return myTypes.Order(l => DepthDiff(l), ascending);
case SortOption.LeftThreads:
return myTypes.Order(l => l.cachedRowString != null ? l.cachedRowString[(int)MyColumns.LeftThreads].text : LeftThreads(l), ascending);
case SortOption.RightThreads:
return myTypes.Order(l => l.cachedRowString != null ? l.cachedRowString[(int)MyColumns.RightThreads].text : RightThreads(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 = (ComparisonTreeViewItem)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();
}
static bool IsBar(MyColumns col)
{
switch (col)
{
case MyColumns.LeftBar:
case MyColumns.RightBar:
case MyColumns.LeftCountBar:
case MyColumns.RightCountBar:
case MyColumns.LeftTotalBar:
case MyColumns.RightTotalBar:
case MyColumns.LeftCountMeanBar:
case MyColumns.RightCountMeanBar:
return true;
}
return false;
}
internal void WriteTableContentsCSV(System.IO.StreamWriter writer)
{
var visibleColumns = multiColumnHeader.state.visibleColumns.Where(c => !IsBar((MyColumns)c)).ToArray();
for (int i = 0, n = visibleColumns.Length; i < n; i++)
{
if (i != 0)
writer.Write(';');
var colIdx = visibleColumns[i];
writer.Write(s_HeaderData[colIdx].content.text);
}
writer.WriteLine();
foreach (var child in rootItem.children)
{
var item = (ComparisonTreeViewItem)child;
if (item.cachedRowString == null)
GenerateStrings(item);
for (int i = 0, n = visibleColumns.Length; i < n; i++)
{
if (i != 0)
writer.Write(';');
var colIdx = visibleColumns[i];
writer.Write(item.cachedRowString[colIdx].text);
}
writer.WriteLine();
}
}
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("");
menu.AddItem(Styles.menuItemSetAsRemoveMarker, false, () => m_ProfileAnalyzerWindow.SetAsRemoveMarker(markerName));
menu.AddSeparator("");
if (markerName != null && !string.IsNullOrEmpty(markerName))
menu.AddItem(Styles.menuItemCopyToClipboard, false, () => CopyToClipboard(evt, markerName));
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("");
menu.AddDisabledItem(Styles.menuItemSetAsRemoveMarker);
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);
}
string PercentString(float percent)
{
if (percent == float.MinValue)
return "-";
if (percent == float.MaxValue)
return "-";
return string.Format(CultureInfo.InvariantCulture, "{0:+0.##;-0.##;0}%", percent);
}
string DiffPercentString(ComparisonTreeViewItem item)
{
return PercentString(DiffPercent(item));
}
string CountDiffPercentString(ComparisonTreeViewItem item)
{
return PercentString(CountDiffPercent(item));
}
string CountMeanDiffPercentString(ComparisonTreeViewItem item)
{
return PercentString(CountMeanDiffPercent(item));
}
string TotalDiffPercentString(ComparisonTreeViewItem item)
{
return PercentString(TotalDiffPercent(item));
}
void GenerateStrings(ComparisonTreeViewItem item)
{
item.cachedRowString = new GUIContent[m_MaxColumns];
int leftMedianFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(LeftMedianFrameIndex(item), m_LeftDataView);
int rightMedianFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(RightMedianFrameIndex(item), m_RightDataView);
if (TimeRemoved(item) > 0.0)
{
item.cachedRowString[(int)MyColumns.Name] = new GUIContent(item.data.name + " [Modified]", item.data.name + "\n\nTime reduced by removing child marker time");
item.cachedRowString[(int)MyColumns.State] = new GUIContent("Modified", "Time reduced by removing child marker time");
}
else if (TimeIgnored(item) > 0.0)
{
double timeIgnored;
double msTotal;
bool removed = IsFullyIgnored(item, out timeIgnored, out msTotal);
if (removed)
{
item.cachedRowString[(int)MyColumns.Name] = new GUIContent(item.data.name + " [Removed]", item.data.name + "\n\nAll marker time removed");
item.cachedRowString[(int)MyColumns.State] = new GUIContent("Removed", "All marker time removed");
}
else
{
item.cachedRowString[(int)MyColumns.Name] = new GUIContent(item.data.name + " [Partial Removal]", item.data.name + "\n\nSome marker time removed (some instances)");
item.cachedRowString[(int)MyColumns.State] = new GUIContent("Partial Removal", "Some marker time removed (some instances)");
}
}
else
{
item.cachedRowString[(int)MyColumns.Name] = new GUIContent(item.data.name, item.data.name);
item.cachedRowString[(int)MyColumns.State] = new GUIContent("", "");
}
item.cachedRowString[(int)MyColumns.LeftMedian] = item.data.leftIndex < 0 ? Styles.invalidEntry : ToDisplayUnitsWithTooltips(LeftMedian(item), false, leftMedianFrameIndex);
item.cachedRowString[(int)MyColumns.RightMedian] = item.data.rightIndex < 0 ? Styles.invalidEntry : ToDisplayUnitsWithTooltips(RightMedian(item), false, rightMedianFrameIndex);
string tooltip = ToTooltipDisplayUnits(Diff(item), true);
item.cachedRowString[(int)MyColumns.LeftBar] = Diff(item) < 0 ? new GUIContent("", tooltip) : new GUIContent("", "");
item.cachedRowString[(int)MyColumns.RightBar] = Diff(item) > 0 ? new GUIContent("", tooltip) : new GUIContent("", "");
item.cachedRowString[(int)MyColumns.Diff] = ToDisplayUnitsWithTooltips(Diff(item));
item.cachedRowString[(int)MyColumns.DiffPercent] = new GUIContent(DiffPercentString(item), "");
item.cachedRowString[(int)MyColumns.AbsDiff] = ToDisplayUnitsWithTooltips(AbsDiff(item));
item.cachedRowString[(int)MyColumns.LeftCount] = item.data.leftIndex < 0 ? Styles.invalidEntry : new GUIContent(string.Format("{0}", LeftCount(item)));
item.cachedRowString[(int)MyColumns.RightCount] = item.data.rightIndex < 0 ? Styles.invalidEntry : new GUIContent(string.Format("{0}", RightCount(item)));
tooltip = string.Format("{0}", CountDiff(item));
item.cachedRowString[(int)MyColumns.LeftCountBar] = CountDiff(item) < 0 ? new GUIContent("", tooltip) : new GUIContent("", "");
item.cachedRowString[(int)MyColumns.RightCountBar] = CountDiff(item) > 0 ? new GUIContent("", tooltip) : new GUIContent("", "");
item.cachedRowString[(int)MyColumns.CountDiff] = (item.data.leftIndex < 0 && item.data.rightIndex < 0) ? Styles.invalidEntry : new GUIContent(string.Format("{0}", CountDiff(item)), CountDiffPercentString(item));
item.cachedRowString[(int)MyColumns.CountDiffPercent] = (item.data.leftIndex < 0 && item.data.rightIndex < 0) ? Styles.invalidEntry : new GUIContent(CountDiffPercentString(item), "");
item.cachedRowString[(int)MyColumns.AbsCountDiff] = (item.data.leftIndex < 0 && item.data.rightIndex < 0) ? Styles.invalidEntry : new GUIContent(string.Format("{0}", AbsCountDiff(item)));
item.cachedRowString[(int)MyColumns.LeftCountMean] = item.data.leftIndex < 0 ? Styles.invalidEntry : new GUIContent(string.Format(CultureInfo.InvariantCulture, "{0:f0}", LeftCountMean(item)));
item.cachedRowString[(int)MyColumns.RightCountMean] = item.data.rightIndex < 0 ? Styles.invalidEntry : new GUIContent(string.Format(CultureInfo.InvariantCulture, "{0:f0}", RightCountMean(item)));
tooltip = string.Format("{0}", CountMeanDiff(item));
item.cachedRowString[(int)MyColumns.LeftCountMeanBar] = CountMeanDiff(item) < 0 ? new GUIContent("", tooltip) : new GUIContent("", "");
item.cachedRowString[(int)MyColumns.RightCountMeanBar] = CountMeanDiff(item) > 0 ? new GUIContent("", tooltip) : new GUIContent("", "");
item.cachedRowString[(int)MyColumns.CountMeanDiff] = (item.data.leftIndex < 0 && item.data.rightIndex < 0) ? Styles.invalidEntry : new GUIContent(string.Format("{0:f0}", CountMeanDiff(item)), CountMeanDiffPercentString(item));
item.cachedRowString[(int)MyColumns.CountMeanDiffPercent] = (item.data.leftIndex < 0 && item.data.rightIndex < 0) ? Styles.invalidEntry : new GUIContent(CountMeanDiffPercentString(item), "");
item.cachedRowString[(int)MyColumns.AbsCountMeanDiff] = (item.data.leftIndex < 0 && item.data.rightIndex < 0) ? Styles.invalidEntry : new GUIContent(string.Format("{0:f0}", AbsCountMeanDiff(item)));
item.cachedRowString[(int)MyColumns.LeftTotal] = item.data.leftIndex < 0 ? Styles.invalidEntry : ToDisplayUnitsWithTooltips(LeftTotal(item));
item.cachedRowString[(int)MyColumns.RightTotal] = item.data.rightIndex < 0 ? Styles.invalidEntry : ToDisplayUnitsWithTooltips(RightTotal(item));
tooltip = ToTooltipDisplayUnits(TotalDiff(item), true);
item.cachedRowString[(int)MyColumns.LeftTotalBar] = TotalDiff(item) < 0 ? new GUIContent("", tooltip) : new GUIContent("", "");
item.cachedRowString[(int)MyColumns.RightTotalBar] = TotalDiff(item) > 0 ? new GUIContent("", tooltip) : new GUIContent("", "");
item.cachedRowString[(int)MyColumns.TotalDiff] = (item.data.leftIndex < 0 && item.data.rightIndex < 0) ? Styles.invalidEntry : ToDisplayUnitsWithTooltips(TotalDiff(item));
item.cachedRowString[(int)MyColumns.TotalDiffPercent] = (item.data.leftIndex < 0 && item.data.rightIndex < 0) ? Styles.invalidEntry : new GUIContent(TotalDiffPercentString(item), "");
item.cachedRowString[(int)MyColumns.AbsTotalDiff] = (item.data.leftIndex < 0 && item.data.rightIndex < 0) ? Styles.invalidEntry : ToDisplayUnitsWithTooltips(AbsTotalDiff(item));
item.cachedRowString[(int)MyColumns.LeftDepth] = item.data.leftIndex < 0 ? Styles.invalidEntry : (LeftMinDepth(item) == LeftMaxDepth(item)) ? new GUIContent(string.Format("{0}", LeftMinDepth(item)), "") : new GUIContent(string.Format("{0}-{1}", LeftMinDepth(item), LeftMaxDepth(item)), "");
item.cachedRowString[(int)MyColumns.RightDepth] = item.data.rightIndex < 0 ? Styles.invalidEntry : (RightMinDepth(item) == RightMaxDepth(item)) ? new GUIContent(string.Format("{0}", RightMinDepth(item)), "") : new GUIContent(string.Format("{0}-{1}", RightMinDepth(item), RightMaxDepth(item)), "");
item.cachedRowString[(int)MyColumns.DepthDiff] = (item.data.leftIndex < 0 || item.data.rightIndex < 0) ? Styles.invalidEntry : new GUIContent(string.Format("{0}", DepthDiff(item)));
item.cachedRowString[(int)MyColumns.LeftThreads] = item.data.leftIndex < 0 ? Styles.invalidEntry : new GUIContent(string.Format("{0}", LeftThreads(item)), LeftThreads(item));
item.cachedRowString[(int)MyColumns.RightThreads] = item.data.rightIndex < 0 ? Styles.invalidEntry : new GUIContent(string.Format("{0}", RightThreads(item)), RightThreads(item));
}
void ShowBar(Rect rect, float ms, float range, GUIContent content, Color color, bool rightAlign)
{
if (ms > 0.0f)
{
if (m_2D.DrawStart(rect))
{
float w = Math.Max(1.0f, rect.width * ms / range);
float x = rightAlign ? rect.width - w : 0.0f;
m_2D.DrawFilledBox(x, 1, w, rect.height - 1, color);
m_2D.DrawEnd();
}
}
GUI.Label(rect, content);
}
void CellGUI(Rect cellRect, ComparisonTreeViewItem 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);
ShowText(cellRect, content);
}
break;
case MyColumns.State:
case MyColumns.LeftMedian:
case MyColumns.Diff:
case MyColumns.RightMedian:
case MyColumns.DiffPercent:
case MyColumns.AbsDiff:
case MyColumns.LeftCount:
case MyColumns.RightCount:
case MyColumns.CountDiff:
case MyColumns.CountDiffPercent:
case MyColumns.AbsCountDiff:
case MyColumns.LeftCountMean:
case MyColumns.RightCountMean:
case MyColumns.CountMeanDiff:
case MyColumns.CountMeanDiffPercent:
case MyColumns.AbsCountMeanDiff:
case MyColumns.LeftTotal:
case MyColumns.RightTotal:
case MyColumns.TotalDiff:
case MyColumns.TotalDiffPercent:
case MyColumns.AbsTotalDiff:
case MyColumns.LeftDepth:
case MyColumns.RightDepth:
case MyColumns.LeftThreads:
case MyColumns.RightThreads:
case MyColumns.DepthDiff:
ShowText(cellRect, content);
break;
case MyColumns.LeftBar:
ShowBar(cellRect, -Diff(item), m_DiffRange, content, m_LeftColor, true);
break;
case MyColumns.RightBar:
ShowBar(cellRect, Diff(item), m_DiffRange, content, m_RightColor, false);
break;
case MyColumns.LeftCountBar:
ShowBar(cellRect, -CountDiff(item), m_CountDiffRange, content, m_LeftColor, true);
break;
case MyColumns.RightCountBar:
ShowBar(cellRect, CountDiff(item), m_CountDiffRange, content, m_RightColor, false);
break;
case MyColumns.LeftCountMeanBar:
ShowBar(cellRect, -CountMeanDiff(item), m_CountMeanDiffRange, content, m_LeftColor, true);
break;
case MyColumns.RightCountMeanBar:
ShowBar(cellRect, CountMeanDiff(item), m_CountMeanDiffRange, content, m_RightColor, false);
break;
case MyColumns.LeftTotalBar:
ShowBar(cellRect, (float)-TotalDiff(item), (float)m_TotalDiffRange, content, m_LeftColor, true);
break;
case MyColumns.RightTotalBar:
ShowBar(cellRect, (float)TotalDiff(item), (float)m_TotalDiffRange, content, m_RightColor, false);
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;
}
}
static HeaderData[] s_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("State", "Status of marker entry (if modified or removed from frame time due to 'Remove' filter)"),
new HeaderData("Left Median", "Left median time\n\nCentral marker time over all selected frames"),
new HeaderData("<", "Difference if left data set is a larger value", width: 50),
new HeaderData(">", "Difference if right data set is a larger value", width: 50),
new HeaderData("Right Median", "Right median time\n\nCentral marker time over all selected frames"),
new HeaderData("Diff", "Difference between left and right times"),
new HeaderData("Diff Percent", "Difference between left and right times as percentage of left marker time"),
new HeaderData("Abs Diff", "Absolute difference between left and right times"),
new HeaderData("Count Left", "Left marker count over all selected frames\n\nMultiple can occur per frame"),
new HeaderData("< Count", "Count Difference if left data set count is a larger value", width: 50),
new HeaderData("> Count", "Count Difference if right data set count is a larger value", width: 50),
new HeaderData("Count Right", "Right marker count over all selected frames\n\nMultiple can occur per frame"),
new HeaderData("Count Delta", "Difference in marker count"),
new HeaderData("Count Delta Percent", "Difference in marker count as percentage of left marker count"),
new HeaderData("Abs Count", "Absolute difference in marker count"),
new HeaderData("Count Left Frame", "Average number of markers per frame in left data set\n\ntotal count / number of non zero frames"),
new HeaderData("< Frame Count", "Per frame Count Difference if left data set count is a larger value", width: 50),
new HeaderData("> Frame Count", "Per frame Count Difference if right data set count is a larger value", width: 50),
new HeaderData("Count Right Frame", "Average number of markers per frame in right data set\n\ntotal count / number of non zero frames"),
new HeaderData("Count Delta Frame", "Difference in per frame marker count"),
new HeaderData("Count Delta Percent Frame", "Difference in per frame marker count as percentage of left count"),
new HeaderData("Abs Frame Count", "Absolute difference in per frame marker count"),
new HeaderData("Total Left", "Left marker total time over all selected frames"),
new HeaderData("< Total", "Total Difference if left data set total is a larger value", width: 50),
new HeaderData("> Total", "Total Difference if right data set total is a larger value", width: 50),
new HeaderData("Total Right", "Right marker total time over all selected frames"),
new HeaderData("Total Delta", "Difference in total time over all selected frames"),
new HeaderData("Total Delta Percent", "Difference in total time over all selected frames as percentage of left marker total time"),
new HeaderData("Abs Total", "Absolute difference in total time over all selected frames"),
new HeaderData("Depth Left", "Marker depth in marker hierarchy\n\nMay appear at multiple levels"),
new HeaderData("Depth Right", "Marker depth in marker hierarchy\n\nMay appear at multiple levels"),
new HeaderData("Depth Diff", "Absolute difference in min marker depth total over all selected frames"),
new HeaderData("Threads Left", "Threads the marker occurs on in left data set (with filtering applied)"),
new HeaderData("Threads Right", "Threads the marker occurs on in right data set (with filtering applied)"),
};
public static MultiColumnHeaderState CreateDefaultMultiColumnHeaderState(MarkerColumnFilter modeFilter)
{
var columnList = new List<MultiColumnHeaderState.Column>();
foreach (var header in s_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.SelectPairing(selectedIds[0]);
}
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.LeftMedian,
(int)MyColumns.LeftBar,
(int)MyColumns.RightBar,
(int)MyColumns.RightMedian,
(int)MyColumns.Diff,
(int)MyColumns.AbsDiff,
(int)MyColumns.LeftCount,
(int)MyColumns.RightCount,
(int)MyColumns.CountDiff,
};
break;
case MarkerColumnFilter.Mode.Time:
visibleColumns = new int[]
{
(int)MyColumns.Name,
(int)MyColumns.LeftMedian,
(int)MyColumns.LeftBar,
(int)MyColumns.RightBar,
(int)MyColumns.RightMedian,
(int)MyColumns.Diff,
(int)MyColumns.AbsDiff,
};
break;
case MarkerColumnFilter.Mode.Totals:
visibleColumns = new int[]
{
(int)MyColumns.Name,
(int)MyColumns.LeftTotal,
(int)MyColumns.LeftTotalBar,
(int)MyColumns.RightTotalBar,
(int)MyColumns.RightTotal,
(int)MyColumns.TotalDiff,
(int)MyColumns.AbsTotalDiff,
};
break;
case MarkerColumnFilter.Mode.TimeWithTotals:
visibleColumns = new int[]
{
(int)MyColumns.Name,
(int)MyColumns.LeftMedian,
(int)MyColumns.LeftBar,
(int)MyColumns.RightBar,
(int)MyColumns.RightMedian,
(int)MyColumns.AbsDiff,
(int)MyColumns.LeftTotal,
(int)MyColumns.LeftTotalBar,
(int)MyColumns.RightTotalBar,
(int)MyColumns.RightTotal,
(int)MyColumns.AbsTotalDiff,
};
break;
case MarkerColumnFilter.Mode.CountTotals:
visibleColumns = new int[]
{
(int)MyColumns.Name,
(int)MyColumns.LeftCount,
(int)MyColumns.LeftCountBar,
(int)MyColumns.RightCountBar,
(int)MyColumns.RightCount,
(int)MyColumns.CountDiff,
(int)MyColumns.AbsCountDiff,
};
break;
case MarkerColumnFilter.Mode.CountPerFrame:
visibleColumns = new int[]
{
(int)MyColumns.Name,
(int)MyColumns.LeftCountMean,
(int)MyColumns.LeftCountMeanBar,
(int)MyColumns.RightCountMeanBar,
(int)MyColumns.RightCountMean,
(int)MyColumns.CountMeanDiff,
(int)MyColumns.AbsCountMeanDiff,
};
break;
case MarkerColumnFilter.Mode.Depth:
visibleColumns = new int[]
{
(int)MyColumns.Name,
(int)MyColumns.LeftDepth,
(int)MyColumns.RightDepth,
(int)MyColumns.DepthDiff,
};
break;
case MarkerColumnFilter.Mode.Threads:
visibleColumns = new int[]
{
(int)MyColumns.Name,
(int)MyColumns.LeftThreads,
(int)MyColumns.RightThreads,
};
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();
}
}
}

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,128 @@
using System;
using UnityEngine;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor.IMGUI.Controls;
namespace UnityEditor.Performance.ProfileAnalyzer
{
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);
}
}
}
}

View File

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

View File

@ -0,0 +1,460 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Video;
namespace UnityEditor.Performance.ProfileAnalyzer
{
[Serializable]
public class DepthSliceUI
{
[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();
}
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;
}
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)
{
bool lastEnabled = GUI.enabled;
bool enabled = !isAnalysisRunning;
EditorGUILayout.BeginHorizontal();
if (singleView)
{
EditorGUILayout.LabelField(ProfileAnalyzerWindow.Styles.depthTitle, GUILayout.Width(ProfileAnalyzerWindow.LayoutSize.FilterOptionsLeftLabelWidth));
DrawDepthFilterDropdown(null, enabled,
profileSingleView, (primary, left, right) => m_DepthFilter = primary,
ViewType.Single, profileSingleView, profileLeftView, profileRightView);
}
else
{
EditorGUILayout.LabelField(ProfileAnalyzerWindow.Styles.depthTitle, GUILayout.Width(ProfileAnalyzerWindow.LayoutSize.FilterOptionsLeftLabelWidth));
if (m_DepthFilterAuto)
{
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
{
DrawDepthFilterDropdown(ProfileAnalyzerWindow.Styles.leftDepthTitle, enabled, profileLeftView,
(primary, left, right) => m_DepthFilter1 = primary,
ViewType.Left, profileSingleView, profileLeftView, profileRightView);
DrawDepthFilterDropdown(ProfileAnalyzerWindow.Styles.rightDepthTitle, enabled && !m_DepthFilterAuto, profileRightView,
(primary, left, right) => m_DepthFilter2 = primary,
ViewType.Right, profileSingleView, profileLeftView, profileRightView);
}
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);
}
}
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
}
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,210 @@
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)
{
var shader = Shader.Find(m_ShaderName);
if (shader == null)
{
Debug.LogFormat("Unable to locate shader {0}", m_ShaderName);
return false;
}
m_Material = new Material(shader);
if (m_Material == null)
{
Debug.LogFormat("Unable to create material for {0}", m_ShaderName);
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;
if (CheckAndSetupMaterial())
{
m_Material.SetFloat("_UseClipRect", m_ClipRectEnabled ? 1f : 0f);
m_Material.SetVector("_ClipRect", m_ClipRect);
}
}
public void ClearClipRect()
{
m_ClipRectEnabled = false;
if (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;
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.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,2310 @@
using UnityEngine;
using System;
using System.Collections.Generic;
using UnityEngine.Profiling;
namespace UnityEditor.Performance.ProfileAnalyzer
{
[Serializable]
internal class FrameTimeGraphGlobalSettings
{
public bool showThreads = false;
public bool showSelectedMarker = true;
public bool showFrameLines = true;
public bool showFrameLineText = true;
public bool showOrderedByFrameDuration = false;
}
internal class FrameTimeGraph
{
static FrameTimeGraphGlobalSettings m_GlobalSettings = new FrameTimeGraphGlobalSettings();
static public void SetGlobalSettings(FrameTimeGraphGlobalSettings globalSettings)
{
m_GlobalSettings = globalSettings;
}
public struct Data
{
public readonly float ms;
public readonly int frameOffset;
public Data(float _ms, int _index)
{
ms = _ms;
frameOffset = _index;
}
};
public delegate void SetRange(List<int> selected, int clickCount, FrameTimeGraph.State inputStatus);
public delegate void SetActive(bool active);
public enum State
{
None,
Dragging,
DragComplete
};
enum DragDirection
{
Start,
Forward,
Backward,
None
};
enum AxisMode
{
One60HzFrame,
Two60HzFrames,
Four60HzFrames,
Max,
Custom
};
Draw2D m_2D;
int m_DragBeginFirstOffset;
int m_DragBeginLastOffset;
bool m_Dragging;
int m_DragFirstOffset;
int m_DragLastOffset;
bool m_Moving;
int m_MoveHandleOffset;
bool m_SingleControlAction;
int m_ClickCount;
double m_LastClickTime;
bool m_MouseReleased;
bool m_Zoomed;
int m_ZoomStartOffset;
int m_ZoomEndOffset;
Color m_ColorBarBackground;
Color m_ColorBarBackgroundSelected;
Color m_ColorBar;
Color m_ColorBarOutOfRange;
Color m_ColorBarSelected;
Color m_ColorBarThreads;
Color m_ColorBarThreadsOutOfRange;
Color m_ColorBarThreadsSelected;
Color m_ColorBarMarker;
Color m_ColorBarMarkerOutOfRange;
Color m_ColorBarMarkerSelected;
Color m_ColorGridLine;
FrameTimeGraph m_PairedWithFrameTimeGraph;
internal static class Styles
{
public static readonly GUIContent menuItemClearSelection = new GUIContent("Clear Selection");
public static readonly GUIContent menuItemSelectAll = new GUIContent("Select All");
public static readonly GUIContent menuItemInvertSelection = new GUIContent("Invert Selection");
public static readonly GUIContent menuItemZoomSelection = new GUIContent("Zoom Selection");
public static readonly GUIContent menuItemZoomAll = new GUIContent("Zoom All");
public static readonly GUIContent menuItemSelectMin = new GUIContent("Select Shortest Frame");
public static readonly GUIContent menuItemSelectMax = new GUIContent("Select Longest Frame");
public static readonly GUIContent menuItemSelectMedian = new GUIContent("Select Median Frame");
public static readonly GUIContent menuItemSelectPrevious = new GUIContent("Move selection left _LEFT");
public static readonly GUIContent menuItemSelectNext = new GUIContent("Move selection right _RIGHT");
public static readonly GUIContent menuItemSelectGrow = new GUIContent("Grow selection _=");
public static readonly GUIContent menuItemSelectShrink = new GUIContent("Shrink selection _-");
public static readonly GUIContent menuItemSelectGrowLeft = new GUIContent("Grow selection left _<");
public static readonly GUIContent menuItemSelectGrowRight = new GUIContent("Grow selection right _>");
public static readonly GUIContent menuItemSelectShrinkLeft = new GUIContent("Shrink selection left _&<");
public static readonly GUIContent menuItemSelectShrinkRight = new GUIContent("Shrink selection right _&>");
public static readonly GUIContent menuItemSelectGrowFast = new GUIContent("Grow selection (fast) _#=");
public static readonly GUIContent menuItemSelectShrinkFast = new GUIContent("Shrink selection (fast) _#-");
public static readonly GUIContent menuItemSelectGrowLeftFast = new GUIContent("Grow selection left (fast) _#<");
public static readonly GUIContent menuItemSelectGrowRightFast = new GUIContent("Grow selection right (fast) _#>");
public static readonly GUIContent menuItemSelectShrinkLeftFast = new GUIContent("Shrink selection left (fast) _#&<");
public static readonly GUIContent menuItemSelectShrinkRightFast = new GUIContent("Shrink selection right (fast) _#&>");
public static readonly GUIContent menuItemShowSelectedMarker = new GUIContent("Show Selected Marker");
public static readonly GUIContent menuItemShowThreads = new GUIContent("Show Filtered Threads");
// public static readonly GUIContent menuItemDetailedMode = new GUIContent("Detailed mode");
public static readonly GUIContent menuItemShowFrameLines = new GUIContent("Show Frame Lines");
public static readonly GUIContent menuItemShowFrameLineText = new GUIContent("Show Frame Line Text");
public static readonly GUIContent menuItemShowOrderedByFrameDuration = new GUIContent("Order by Frame Duration");
}
const int kXAxisWidth = 80;
const int kYAxisDetailThreshold = 40;
const int kOverrunHeight = 3;
static AxisMode s_YAxisMode;
static float m_YAxisMs;
bool m_IsOrderedByFrameDuration;
List<Data> m_Values = new List<Data> {};
List<int> m_LastSelectedFrameOffsets = new List<int> {};
int[] m_FrameOffsetToDataOffsetMapping = new int[] {};
SetRange m_SetRange;
SetActive m_SetActive;
List<int> m_CurrentSelection = new List<int>();
int m_CurrentSelectionFirstDataOffset;
int m_CurrentSelectionLastDataOffset;
int m_GraphId;
int m_ControlID;
static int s_LastSelectedGraphId = -1;
static int s_CurrentSelectedGraphId = -1;
bool m_Enabled;
struct BarData
{
public float x;
public float y;
public float w;
public float h;
public int startDataOffset;
public int endDataOffset;
public float yMin;
public float yMax;
public BarData(float _x, float _y, float _w, float _h, int _startDataOffset, int _endDataOffset, float _yMin, float _yMax)
{
x = _x;
y = _y;
w = _w;
h = _h;
startDataOffset = _startDataOffset;
endDataOffset = _endDataOffset;
yMin = _yMin;
yMax = _yMax;
}
}
List<BarData> m_Bars = new List<BarData>();
DisplayUnits m_Units;
Rect m_LastRect;
int m_MaxFrames;
string DisplayUnits()
{
return m_Units.Postfix();
}
string ToDisplayUnits(float ms, bool showUnits = false, int limitToNDigits = 5)
{
return m_Units.ToString(ms, showUnits, limitToNDigits);
}
public void SetUnits(Units units)
{
m_Units = new DisplayUnits(units);
}
public void Reset()
{
m_Zoomed = false;
m_Dragging = false;
ClearDragSelection();
m_Moving = false;
m_ClickCount = 0;
m_MouseReleased = false;
}
void Init()
{
Reset();
m_PairedWithFrameTimeGraph = null;
m_YAxisMs = 100f;
s_YAxisMode = AxisMode.Max;
m_IsOrderedByFrameDuration = false;
m_Enabled = true;
m_LastRect = new Rect(0, 0, 0, 0);
m_MaxFrames = -1;
}
public void MakeGraphActive(bool activate)
{
if (activate)
{
if (s_CurrentSelectedGraphId != m_GraphId)
{
s_LastSelectedGraphId = m_GraphId;
s_CurrentSelectedGraphId = m_GraphId;
// Make sure we are not still selecting another graph
GUIUtility.hotControl = 0;
m_SetActive(true);
}
if (GUI.GetNameOfFocusedControl() != "FrameTimeGraph")
{
// Take focus away from any other control
// Doesn't really matter what the name is here
GUI.FocusControl("FrameTimeGraph");
}
}
else
{
if (s_CurrentSelectedGraphId == m_GraphId)
{
s_CurrentSelectedGraphId = -1;
// Remember this was the active control
// Before this point one of the inner labels would have been active
// GUIUtility.hotControl = m_ControlID;
m_SetActive(false);
}
}
}
public bool IsGraphActive()
{
if (s_CurrentSelectedGraphId == m_GraphId)
return true;
if (s_LastSelectedGraphId == m_GraphId && GUIUtility.hotControl == m_ControlID)
return true;
return false;
}
public FrameTimeGraph(int graphID, Draw2D draw2D, Units units, Color background, Color backgroundSelected, Color barColor, Color barSelected, Color barMarker, Color barMarkerSelected, Color barThreads, Color barThreadsSelected, Color colorGridlines)
{
m_GraphId = graphID;
m_ControlID = 0;
m_2D = draw2D;
SetUnits(units);
Init();
float ratio = 0.75f;
m_ColorBarBackground = background;
m_ColorBarBackgroundSelected = backgroundSelected;
m_ColorBar = barColor;
m_ColorBarOutOfRange = new Color(barColor.r * ratio, barColor.g * ratio, barColor.b * ratio);
m_ColorBarSelected = barSelected;
m_ColorBarMarker = barMarker;
m_ColorBarMarkerOutOfRange = new Color(barMarker.r * ratio, barMarker.g * ratio, barMarker.b * ratio);
m_ColorBarMarkerSelected = barMarkerSelected;
m_ColorBarThreads = barThreads;
m_ColorBarThreadsOutOfRange = new Color(barThreads.r * ratio, barThreads.g * ratio, barThreads.b * ratio);
m_ColorBarThreadsSelected = barThreadsSelected;
m_ColorGridLine = colorGridlines;
}
int ClampToRange(int value, int min, int max)
{
if (value < min)
value = min;
if (value > max)
value = max;
return value;
}
int GetDataOffsetForXUnclamped(int xPosition, int width, int totalDataSize)
{
int visibleDataSize;
if (m_Zoomed)
visibleDataSize = (m_ZoomEndOffset - m_ZoomStartOffset) + 1;
else
visibleDataSize = totalDataSize;
int dataOffset = (int)(xPosition * visibleDataSize / width);
if (m_Zoomed)
dataOffset += m_ZoomStartOffset;
return dataOffset;
}
int GetDataOffsetForX(int xPosition, int width, int totalDataSize)
{
//xPosition = ClampToRange(xPosition, 0, width-1);
int dataOffset = GetDataOffsetForXUnclamped(xPosition, width, totalDataSize);
return ClampToRange(dataOffset, 0, totalDataSize - 1);
}
int GetXForDataOffset(int dataOffset, int width, int totalDataSize)
{
//frameOffset = ClampToRange(frameOffset, 0, frames-1);
int visibleDataSize;
if (m_Zoomed)
{
dataOffset = ClampToRange(dataOffset, m_ZoomStartOffset, m_ZoomEndOffset + 1);
dataOffset -= m_ZoomStartOffset;
visibleDataSize = (m_ZoomEndOffset - m_ZoomStartOffset) + 1;
}
else
visibleDataSize = totalDataSize;
int x = (int)(dataOffset * width / visibleDataSize);
x = ClampToRange(x, 0, width - 1);
return x;
}
void SetDragMovement(int startOffset, int endOffset, int currentSelectionFirstDataOffset, int currentSelectionLastDataOffset)
{
// Maintain length but clamp to range
int frames = m_Values.Count;
int currentSelectionRange = currentSelectionLastDataOffset - currentSelectionFirstDataOffset;
endOffset = startOffset + currentSelectionRange;
startOffset = ClampToRange(startOffset, 0, frames - (currentSelectionRange + 1));
endOffset = ClampToRange(endOffset, 0, frames - 1);
SetDragSelection(startOffset, endOffset);
if (m_PairedWithFrameTimeGraph != null && !m_SingleControlAction)
{
m_PairedWithFrameTimeGraph.SetDragSelection(m_DragFirstOffset, m_DragLastOffset);
}
}
void SetDragSelection(int startOffset, int endOffset, DragDirection dragDirection)
{
// No need to clamp these as input is clamped.
switch (dragDirection)
{
case DragDirection.Forward:
SetDragSelection(m_DragBeginFirstOffset, endOffset);
break;
case DragDirection.Backward:
SetDragSelection(startOffset, m_DragBeginLastOffset);
break;
case DragDirection.Start:
SetDragSelection(startOffset, endOffset);
// Record first selected bar range
m_DragBeginFirstOffset = m_DragFirstOffset;
m_DragBeginLastOffset = m_DragLastOffset;
break;
}
if (m_PairedWithFrameTimeGraph != null && !m_SingleControlAction)
{
m_PairedWithFrameTimeGraph.SetDragSelection(m_DragFirstOffset, m_DragLastOffset);
}
}
public void SetDragSelection(int startOffset, int endOffset)
{
m_DragFirstOffset = startOffset;
m_DragLastOffset = endOffset;
}
public void ClearDragSelection()
{
m_DragFirstOffset = -1;
m_DragLastOffset = -1;
}
public bool HasDragRegion()
{
return (m_DragFirstOffset != -1);
}
public void GetSelectedRange(List<int> frameOffsets, out int firstDataOffset, out int lastDataOffset, out int firstFrameOffset, out int lastFrameOffset)
{
int frames = m_Values != null ? m_Values.Count : 0;
firstDataOffset = 0;
lastDataOffset = frames - 1;
firstFrameOffset = 0;
lastFrameOffset = frames - 1;
if (m_FrameOffsetToDataOffsetMapping.Length > 0)
{
// By default data is ordered by index so first/last will be the selected visible range
if (frameOffsets.Count >= 1)
{
firstFrameOffset = frameOffsets[0];
lastFrameOffset = firstFrameOffset;
firstDataOffset = GetDataOffset(firstFrameOffset);
lastDataOffset = firstDataOffset;
}
if (frameOffsets.Count >= 2)
{
lastFrameOffset = frameOffsets[frameOffsets.Count - 1];
lastDataOffset = GetDataOffset(lastFrameOffset);
}
if (m_GlobalSettings.showOrderedByFrameDuration)
{
// Need to find the selected items with lowest and highest ms values
if (frameOffsets.Count > 0)
{
int dataOffset = GetDataOffset(firstFrameOffset);
firstDataOffset = dataOffset;
lastDataOffset = dataOffset;
float firstDataMS = m_Values[dataOffset].ms;
float lastDataMS = m_Values[dataOffset].ms;
foreach (int frameOffset in frameOffsets)
{
dataOffset = GetDataOffset(frameOffset);
float ms = m_Values[dataOffset].ms;
if (ms <= firstDataMS && dataOffset < firstDataOffset)
{
firstDataMS = ms;
firstDataOffset = dataOffset;
}
if (ms >= lastDataMS && dataOffset > lastDataOffset)
{
lastDataMS = ms;
lastDataOffset = dataOffset;
}
}
}
}
}
}
public bool IsMultiSelectControlHeld()
{
#if UNITY_EDITOR_OSX
return Event.current.command;
#else
return Event.current.control;
#endif
}
public State ProcessInput()
{
if (!IsEnabled())
return State.None;
if (m_Values == null)
return State.None;
if (m_LastRect.width == 0 || m_MaxFrames < 0)
return State.None;
Rect rect = m_LastRect;
int maxFrames = m_MaxFrames;
int dataLength = m_Values.Count;
if (dataLength <= 0)
return State.None;
if (m_IsOrderedByFrameDuration != m_GlobalSettings.showOrderedByFrameDuration)
{
// Reorder if necessary
SetData(m_Values);
}
int currentSelectionFirstDataOffset;
int currentSelectionLastDataOffset;
int currentSelectionFirstFrameOffset;
int currentSelectionLastFrameOffset;
GetSelectedRange(m_LastSelectedFrameOffsets, out currentSelectionFirstDataOffset, out currentSelectionLastDataOffset, out currentSelectionFirstFrameOffset, out currentSelectionLastFrameOffset);
m_CurrentSelection.Clear();
m_CurrentSelection.AddRange(m_LastSelectedFrameOffsets);
m_CurrentSelectionFirstDataOffset = currentSelectionFirstDataOffset;
m_CurrentSelectionLastDataOffset = currentSelectionLastDataOffset;
if (Event.current.isKey && Event.current.type == EventType.KeyDown && !m_Dragging && !m_MouseReleased)
{
if (IsGraphActive())
{
int step = Event.current.shift ? 10 : 1;
var eventUsed = false;
switch (Event.current.keyCode)
{
case KeyCode.LeftArrow:
SelectPrevious(step);
eventUsed = true;
break;
case KeyCode.RightArrow:
SelectNext(step);
eventUsed = true;
break;
case KeyCode.Less:
case KeyCode.Comma:
if (Event.current.alt)
SelectShrinkLeft(step);
else
SelectGrowLeft(step);
eventUsed = true;
break;
case KeyCode.Greater:
case KeyCode.Period:
if (Event.current.alt)
SelectShrinkRight(step);
else
SelectGrowRight(step);
eventUsed = true;
break;
case KeyCode.Plus:
case KeyCode.Equals:
case KeyCode.KeypadPlus:
if (Event.current.alt)
SelectShrink(step);
else
SelectGrow(step);
eventUsed = true;
break;
case KeyCode.Underscore:
case KeyCode.Minus:
case KeyCode.KeypadMinus:
if (Event.current.alt)
SelectGrow(step);
else
SelectShrink(step);
eventUsed = true;
break;
}
if (eventUsed)
Event.current.Use();
}
}
float doubleClickTimeout = 0.25f;
if (m_MouseReleased)
{
if ((EditorApplication.timeSinceStartup - m_LastClickTime) > doubleClickTimeout)
{
// By this point we will know if its a single or double click
bool append = IsMultiSelectControlHeld();
CallSetRange(m_DragFirstOffset, m_DragLastOffset, m_ClickCount, m_SingleControlAction, FrameTimeGraph.State.DragComplete, append);
ClearDragSelection();
if (m_PairedWithFrameTimeGraph != null && !m_SingleControlAction)
m_PairedWithFrameTimeGraph.ClearDragSelection();
m_MouseReleased = false;
}
}
int width = (int)rect.width;
int height = (int)rect.height;
float xStart = rect.xMin;
if (height > kYAxisDetailThreshold)
{
float h = GUI.skin.label.lineHeight;
xStart += kXAxisWidth;
width -= kXAxisWidth;
}
if (maxFrames > 0)
{
if (!m_Zoomed)
width = width * dataLength / maxFrames;
}
// Process input
Event e = Event.current;
if (e.isMouse)
{
if (m_Dragging)
{
if (e.type == EventType.MouseUp)
{
m_Dragging = false;
m_Moving = false;
// Delay the action as we are checking for double click
m_MouseReleased = true;
return State.Dragging;
}
}
int x = (int)(e.mousePosition.x - xStart);
int dataOffset = GetDataOffsetForXUnclamped(x, width, dataLength);
if (m_Moving)
dataOffset -= m_MoveHandleOffset;
dataOffset = ClampToRange(dataOffset, 0, dataLength - 1);
int frameOffsetBeforeNext = Math.Max(dataOffset, GetDataOffsetForX(x + 1, width, dataLength) - 1);
if (m_Dragging)
{
if (e.button == 0)
{
// Still dragging (doesn't have to be within the y bounds)
if (m_Moving)
{
// Forward drag from start point
SetDragMovement(dataOffset, frameOffsetBeforeNext, currentSelectionFirstDataOffset, currentSelectionLastDataOffset);
}
else
{
DragDirection dragDirection = (dataOffset < m_DragBeginFirstOffset) ? DragDirection.Backward : DragDirection.Forward;
SetDragSelection(dataOffset, frameOffsetBeforeNext, dragDirection);
}
CallSetRange(m_DragFirstOffset, m_DragLastOffset, m_ClickCount, m_SingleControlAction, FrameTimeGraph.State.Dragging);
return State.Dragging;
}
}
else
{
if (e.mousePosition.x >= rect.x && e.mousePosition.x <= rect.xMax &&
e.mousePosition.y >= rect.y && e.mousePosition.y <= rect.yMax)
{
if (e.mousePosition.x >= xStart && e.mousePosition.x <= (xStart + width) &&
e.mousePosition.y >= rect.y && e.mousePosition.y < rect.yMax)
{
MakeGraphActive(true);
if (e.type == EventType.MouseDown && e.button == 0)
{
// Drag start (must be within the bounds of the control)
// Might be single or double click
m_LastClickTime = EditorApplication.timeSinceStartup;
m_ClickCount = e.clickCount;
m_Dragging = true;
m_Moving = false;
if (currentSelectionFirstDataOffset != 0 || currentSelectionLastDataOffset != dataLength - 1)
{
// Selection is valid
if (e.shift && dataOffset >= currentSelectionFirstDataOffset && frameOffsetBeforeNext <= currentSelectionLastDataOffset)
{
// Moving if shift held and we are inside the current selection range
m_Moving = true;
}
}
if (m_PairedWithFrameTimeGraph != null)
m_SingleControlAction = e.alt; // Record if we are acting only on this control rather than the paired one too
else
m_SingleControlAction = true;
if (m_Moving)
{
m_MoveHandleOffset = dataOffset - currentSelectionFirstDataOffset;
SetDragMovement(currentSelectionFirstDataOffset, currentSelectionLastDataOffset, currentSelectionFirstDataOffset, currentSelectionLastDataOffset);
}
else
{
//SetDragSelection(dataOffset, frameOffsetBeforeNext, DragDirection.Start);
// Select just 1 frame
SetDragSelection(dataOffset, dataOffset, DragDirection.Start);
}
CallSetRange(m_DragFirstOffset, m_DragLastOffset, m_ClickCount, m_SingleControlAction, FrameTimeGraph.State.Dragging);
return State.Dragging;
}
}
}
else
{
// Left this graph area
MakeGraphActive(false);
}
}
}
if (m_MouseReleased)
{
// Not finished drag fully yet
CallSetRange(m_DragFirstOffset, m_DragLastOffset, m_ClickCount, m_SingleControlAction, FrameTimeGraph.State.Dragging);
return State.Dragging;
}
return State.None;
}
public float GetDataRange()
{
if (m_Values == null)
return 0f;
int frames = m_Values.Count;
float min = 0f;
float max = 0f;
for (int frameOffset = 0; frameOffset < frames; frameOffset++)
{
float ms = m_Values[frameOffset].ms;
if (ms > max)
max = ms;
}
float hRange = max - min;
return hRange;
}
public void PairWith(FrameTimeGraph otherFrameTimeGraph)
{
if (m_PairedWithFrameTimeGraph != null)
{
// Clear existing pairing
m_PairedWithFrameTimeGraph.m_PairedWithFrameTimeGraph = null;
}
m_PairedWithFrameTimeGraph = otherFrameTimeGraph;
if (otherFrameTimeGraph != null)
otherFrameTimeGraph.m_PairedWithFrameTimeGraph = this;
}
public FrameTimeGraph GetPairedWith()
{
return m_PairedWithFrameTimeGraph;
}
public float GetYAxisRange(float yMax)
{
switch (s_YAxisMode)
{
case AxisMode.One60HzFrame:
return 1000f / 60f;
case AxisMode.Two60HzFrames:
return 2000f / 60f;
case AxisMode.Four60HzFrames:
return 4000f / 60f;
case AxisMode.Max:
return yMax;
case AxisMode.Custom:
return m_YAxisMs;
}
return yMax;
}
public void SetData(List<Data> values)
{
if (values == null)
return;
m_Values = values;
if (m_GlobalSettings.showOrderedByFrameDuration)
m_Values.Sort((a, b) => { return a.ms.CompareTo(b.ms); });
else
m_Values.Sort((a, b) => { return a.frameOffset.CompareTo(b.frameOffset); });
m_FrameOffsetToDataOffsetMapping = new int[m_Values.Count];
for (int dataOffset = 0; dataOffset < m_Values.Count; dataOffset++)
m_FrameOffsetToDataOffsetMapping[m_Values[dataOffset].frameOffset] = dataOffset;
m_CurrentSelection.Clear();
for (int frameIndex = 0; frameIndex < m_Values.Count; frameIndex++)
{
m_CurrentSelection.Add(frameIndex);
}
m_CurrentSelectionFirstDataOffset = 0;
m_CurrentSelectionLastDataOffset = m_Values.Count - 1;
m_IsOrderedByFrameDuration = m_GlobalSettings.showOrderedByFrameDuration;
}
int GetDataOffset(int frameOffset)
{
if (frameOffset < 0 || frameOffset >= m_FrameOffsetToDataOffsetMapping.Length)
{
Debug.Log(string.Format("{0} out of range of frame offset to data offset mapping {1}", frameOffset, m_FrameOffsetToDataOffsetMapping.Length));
return 0;
}
return m_FrameOffsetToDataOffsetMapping[frameOffset];
}
public void ClearData()
{
m_Values = null;
}
public bool HasData()
{
if (m_Values == null)
return false;
if (m_Values.Count == 0)
return false;
return true;
}
public void SetActiveCallback(SetActive setActive)
{
m_SetActive = setActive;
}
public void SetRangeCallback(SetRange setRange)
{
m_SetRange = setRange;
}
void CallSetRange(int startDataOffset, int endDataOffset, int clickCount, bool singleControlAction, FrameTimeGraph.State inputStatus, bool append = false, bool effectPaired = true)
{
if (m_SetRange == null)
return;
if (startDataOffset < 0 && endDataOffset < 0)
{
// Clear
m_SetRange(new List<int>(), clickCount, inputStatus);
return;
}
startDataOffset = Math.Max(0, startDataOffset);
endDataOffset = Math.Min(endDataOffset, m_Values.Count - 1);
List<int> selected = new List<int>();
if (append && m_LastSelectedFrameOffsets.Count != m_Values.Count)
{
foreach (int frameOffset in m_LastSelectedFrameOffsets)
{
int dataOffset = m_FrameOffsetToDataOffsetMapping[frameOffset];
if (dataOffset >= 0 && dataOffset < m_Values.Count)
{
selected.Add(frameOffset);
}
}
}
for (int dataOffset = startDataOffset; dataOffset <= endDataOffset; dataOffset++)
{
if (dataOffset >= 0 && dataOffset < m_Values.Count)
{
int frameOffset = m_Values[dataOffset].frameOffset;
if (append == false || !selected.Contains(frameOffset))
{
selected.Add(frameOffset);
}
}
}
// Sort selection in frame index order so start is lowest and end is highest
selected.Sort();
if (selected.Count == 0)
return;
m_SetRange(selected, clickCount, inputStatus);
if (m_PairedWithFrameTimeGraph != null && m_PairedWithFrameTimeGraph.m_Values.Count > 1 && effectPaired && !singleControlAction)
{
// Update selection on the other frame time graph
int mainMaxFrame = m_Values.Count - 1;
int otherMaxFrame = m_PairedWithFrameTimeGraph.m_Values.Count - 1;
int startOffset = startDataOffset;
int endOffset = endDataOffset;
if (startOffset > otherMaxFrame)
{
if (append)
{
// Nothing more to do
return;
}
// Select all, if the main selection is outsize the range of the other
startOffset = 0;
endOffset = otherMaxFrame;
}
else
{
if (startOffset == 0 && endOffset == mainMaxFrame)
{
// If clearing main selection then clear the other section fully too
endOffset = otherMaxFrame;
}
startOffset = ClampToRange(startOffset, 0, otherMaxFrame);
endOffset = ClampToRange(endOffset, 0, otherMaxFrame);
}
m_PairedWithFrameTimeGraph.CallSetRange(startOffset, endOffset, clickCount, singleControlAction, inputStatus, append, false);
}
}
bool HasNoSelection()
{
if (m_Values == null)
return false;
int frames = m_Values.Count;
return ((m_CurrentSelectionFirstDataOffset < 0 || m_CurrentSelectionLastDataOffset >= frames) &&
(m_CurrentSelectionLastDataOffset < 0 || m_CurrentSelectionLastDataOffset >= frames));
}
bool HasSelectedAll()
{
if (m_Values == null)
return false;
int frames = m_Values.Count;
return (m_CurrentSelectionFirstDataOffset == 0 && m_CurrentSelectionLastDataOffset == (frames - 1));
}
bool HasSubsetSelected()
{
if (m_Values == null)
return false;
return !(HasSelectedAll() || HasNoSelection());
}
void RegenerateBars(float x, float y, float width, float height, float yRange)
{
int frames = m_Values.Count;
if (frames <= 0)
return;
m_Bars.Clear();
int nextDataOffset = GetDataOffsetForX(0, (int)width, frames);
for (int barX = 0; barX < width; barX++)
{
int startDataOffset = nextDataOffset;
nextDataOffset = GetDataOffsetForX(barX + 1, (int)width, frames);
int endDataOffset = Math.Max(startDataOffset, nextDataOffset - 1);
float min = m_Values[startDataOffset].ms;
float max = min;
for (int dataOffset = startDataOffset + 1; dataOffset <= endDataOffset; dataOffset++)
{
float ms = m_Values[dataOffset].ms;
if (ms < min)
min = ms;
if (ms > max)
max = ms;
}
float maxClamped = Math.Min(max, yRange);
float h = height * maxClamped / yRange;
m_Bars.Add(new BarData(x + barX, y, 1, h, startDataOffset, endDataOffset, min, max));
}
}
public void SetEnabled(bool enabled)
{
m_Enabled = enabled;
}
public bool IsEnabled()
{
return m_Enabled;
}
float GetTotalSelectionTime(List<int> selectedFrameOffsets)
{
float totalMs = 0;
for (int i = 0; i < selectedFrameOffsets.Count; i++)
{
int frameOffset = selectedFrameOffsets[i];
int dataOffset = m_FrameOffsetToDataOffsetMapping[frameOffset];
totalMs += m_Values[dataOffset].ms;
}
return totalMs;
}
float GetTotalSelectionTime(int firstOffset, int lastOffset)
{
float totalMs = 0;
for (int frameOffset = firstOffset; frameOffset <= lastOffset; frameOffset++)
{
if (frameOffset < m_FrameOffsetToDataOffsetMapping.Length)
{
int dataOffset = m_FrameOffsetToDataOffsetMapping[frameOffset];
totalMs += m_Values[dataOffset].ms;
}
}
return totalMs;
}
void ShowFrameLines(float x, float y, float yRange, float width, float height)
{
float msSegment = 1000f / 60f;
int lines = (int)(yRange / msSegment);
int step = 1;
for (int line = 1; line <= lines; line += step, step *= 2)
{
float ms = line * msSegment;
float h = height * ms / yRange;
m_2D.DrawLine(x, y + h, x + width - 1, y + h, m_ColorGridLine);
}
}
bool InSelectedRegion(int startDataOffset, int endDataOffset, int selectedFirstOffset, int selectedLastOffset, Dictionary<int, int> frameOffsetToSelectionIndex, bool subsetSelected)
{
bool inSelectionRegion = false;
bool showCurrentSelection = false;
if (HasDragRegion())
{
if (endDataOffset >= selectedFirstOffset && startDataOffset <= selectedLastOffset)
{
inSelectionRegion = true;
}
if (IsMultiSelectControlHeld() && m_LastSelectedFrameOffsets.Count != m_Values.Count)
{
// Show current selection too
showCurrentSelection = true;
}
}
else
{
showCurrentSelection = true;
}
if (showCurrentSelection)
{
//if (subsetSelected)
{
for (int dataOffset = startDataOffset; dataOffset <= endDataOffset; dataOffset++)
{
int frameOffset = m_Values[dataOffset].frameOffset;
if (frameOffsetToSelectionIndex.ContainsKey(frameOffset))
{
inSelectionRegion = true;
break;
}
}
}
}
return inSelectionRegion;
}
public void Draw(Rect rect, ProfileAnalysis analysis, List<int> selectedFrameOffsets, float yMax, int offsetToDisplayMapping, int offsetToIndexMapping, string selectedMarkerName, int maxFrames = 0, ProfileAnalysis fullAnalysis = null)
{
Profiler.BeginSample("FrameTimeGraph.Draw");
// Must be outside repaint to make sure next controls work correctly (specifically marker name filter)
int controlID = GUIUtility.GetControlID(FocusType.Keyboard);
if (Event.current.type == EventType.Repaint)
{
m_LastRect = rect;
// Control id would change during non repaint phase if tooltips displayed if we update this outside the repaint
m_ControlID = controlID;
}
m_MaxFrames = maxFrames;
if (m_Values == null)
return;
m_LastSelectedFrameOffsets = selectedFrameOffsets;
int totalDataSize = m_Values.Count;
if (totalDataSize <= 0)
return;
if (m_IsOrderedByFrameDuration != m_GlobalSettings.showOrderedByFrameDuration)
{
// Reorder if necessary
SetData(m_Values);
}
// Get start and end selection span
int currentSelectionFirstDataOffset;
int currentSelectionLastDataOffset;
int currentSelectionFirstFrameOffset;
int currentSelectionLastFrameOffset;
GetSelectedRange(selectedFrameOffsets, out currentSelectionFirstDataOffset, out currentSelectionLastDataOffset, out currentSelectionFirstFrameOffset, out currentSelectionLastFrameOffset);
// Create mapping from offset to selection for faster selection detection
Dictionary<int, int> frameOffsetToSelectionIndex = new Dictionary<int, int>();
for (int i = 0; i < selectedFrameOffsets.Count; i++)
{
int frameOffset = selectedFrameOffsets[i];
frameOffsetToSelectionIndex[frameOffset] = i;
}
Event current = Event.current;
int selectedFirstOffset;
int selectedLastOffset;
int selectedCount;
bool subsetSelected = false;
if (HasDragRegion())
{
selectedFirstOffset = m_DragFirstOffset;
selectedLastOffset = m_DragLastOffset;
if (selectedFirstOffset > m_Values.Count - 1)
{
// Selection off the end
selectedFirstOffset = m_Values.Count;
selectedLastOffset = m_Values.Count;
selectedCount = 0;
}
else
{
selectedFirstOffset = ClampToRange(selectedFirstOffset, 0, m_Values.Count - 1);
selectedLastOffset = ClampToRange(selectedLastOffset, 0, m_Values.Count - 1);
selectedCount = 1 + (selectedLastOffset - selectedFirstOffset);
subsetSelected = true;
}
}
else
{
selectedFirstOffset = currentSelectionFirstDataOffset;
selectedLastOffset = currentSelectionLastDataOffset;
selectedCount = selectedFrameOffsets.Count;
subsetSelected = (selectedCount > 0 && selectedCount != totalDataSize);
}
// Draw frames and selection
float width = rect.width;
float height = rect.height;
bool showAxis = false;
float xStart = 0f;
float yStart = 0f;
if (height > kYAxisDetailThreshold)
{
showAxis = true;
float h = GUI.skin.label.lineHeight;
xStart += kXAxisWidth;
width -= kXAxisWidth;
yStart += h;
height -= h;
}
if (maxFrames > 0)
{
if (!m_Zoomed)
width = width * totalDataSize / maxFrames;
}
// Start / End
int startOffset = m_Zoomed ? m_ZoomStartOffset : 0;
int endOffset = m_Zoomed ? m_ZoomEndOffset : totalDataSize - 1;
// Get try index values
int startIndex = offsetToDisplayMapping + startOffset;
int endIndex = offsetToDisplayMapping + endOffset;
int selectedFirstIndex = offsetToDisplayMapping + selectedFirstOffset;
int selectedLastIndex = offsetToDisplayMapping + selectedLastOffset;
string detailsString = "";
if (!showAxis)
{
string frameRangeString;
if (startIndex == endIndex)
frameRangeString = string.Format("Total Range {0}", startIndex);
else
frameRangeString = string.Format("Total Range {0} - {1} [{2}]", startIndex, endIndex, 1 + (endIndex - startIndex));
// Selection range
string selectedTooltip = "";
if (subsetSelected)
{
if (selectedFirstIndex == selectedLastIndex)
selectedTooltip = string.Format("\nSelected {0}\n", selectedFirstIndex);
else
selectedTooltip = string.Format("\nSelected {0} - {1} [{2}]", selectedFirstIndex, selectedLastIndex, selectedCount);
}
detailsString = string.Format("\n\n{0}{1}", frameRangeString, selectedTooltip);
}
float yRange = GetYAxisRange(yMax);
bool lastEnabled = GUI.enabled;
bool enabled = IsEnabled();
GUI.enabled = enabled;
if (m_2D.DrawStart(rect, Draw2D.Origin.BottomLeft))
{
float totalMs;
if (HasDragRegion())
{
totalMs = GetTotalSelectionTime(selectedFirstOffset, selectedLastOffset);
}
else
{
totalMs = GetTotalSelectionTime(selectedFrameOffsets);
}
string timeForSelectedFrames = ToDisplayUnits(totalMs, true, 0);
string timeForSelectedFramesClamped = ToDisplayUnits(totalMs, true, 1);
string selectionAreaString = string.Format("\n\nTotal time for {0} selected frames\n{1} ({2})", selectedCount, timeForSelectedFrames, timeForSelectedFramesClamped);
Color selectedControl = GUI.skin.settings.selectionColor;
if (IsGraphActive())
{
m_2D.DrawBox(xStart, yStart, width, height, selectedControl);
}
//xStart -= 1f;
yStart += 1f;
width -= 1f;
height -= 1f;
m_2D.DrawFilledBox(xStart, yStart, width, height, m_ColorBarBackground);
RegenerateBars(xStart, yStart, width, height, yRange);
foreach (BarData bar in m_Bars)
{
bool inSelectionRegion = InSelectedRegion(bar.startDataOffset, bar.endDataOffset, selectedFirstOffset, selectedLastOffset, frameOffsetToSelectionIndex, subsetSelected);
if (inSelectionRegion)
{
m_2D.DrawFilledBox(bar.x, bar.y, bar.w, height, m_ColorBarBackgroundSelected);
}
}
if (m_GlobalSettings.showFrameLines)
{
ShowFrameLines(xStart, yStart, yRange, width, height);
}
ProfileAnalysis analysisData = analysis;
bool full = false;
if (fullAnalysis != null)
{
analysisData = fullAnalysis;
full = true;
}
MarkerData selectedMarker = (m_GlobalSettings.showSelectedMarker && analysisData != null) ? analysisData.GetMarkerByName(selectedMarkerName) : null;
foreach (BarData bar in m_Bars)
{
bool inSelectionRegion = InSelectedRegion(bar.startDataOffset, bar.endDataOffset, selectedFirstOffset, selectedLastOffset, frameOffsetToSelectionIndex, subsetSelected);
if (inSelectionRegion)
{
m_2D.DrawFilledBox(bar.x, bar.y, bar.w, bar.h, m_ColorBarSelected);
}
else
{
m_2D.DrawFilledBox(bar.x, bar.y, bar.w, bar.h, m_ColorBar);
}
// Show where its been clamped
if (bar.yMax > yRange)
{
m_2D.DrawFilledBox(bar.x, bar.y + height, 1, kOverrunHeight, m_ColorBarOutOfRange);
}
if (analysisData != null && (full || !m_Dragging))
{
// Analysis is just on the subset
if (m_GlobalSettings.showThreads)
{
Profiler.BeginSample("FrameTimeGraph.ShowThreads");
ShowThreads(height, yRange, bar, full,
analysisData.GetThreads(), subsetSelected, selectedFirstOffset, selectedLastOffset,
offsetToIndexMapping, frameOffsetToSelectionIndex);
Profiler.EndSample();
}
if (m_GlobalSettings.showSelectedMarker)
{
// Analysis is just on the subset (unless we have full analysis data)
ShowSelectedMarker(height, yRange, bar, full, selectedMarker, subsetSelected, selectedFirstOffset, selectedLastOffset,
offsetToIndexMapping, frameOffsetToSelectionIndex);
}
}
}
m_2D.DrawEnd();
if (m_GlobalSettings.showFrameLines && m_GlobalSettings.showFrameLineText)
{
ShowFrameLinesText(rect, xStart, yStart, yRange, width, height, subsetSelected, selectedFirstOffset, selectedLastOffset);
}
foreach (BarData bar in m_Bars)
{
bool inSelectionRegion = InSelectedRegion(bar.startDataOffset, bar.endDataOffset, selectedFirstOffset, selectedLastOffset, frameOffsetToSelectionIndex, subsetSelected);
// Draw tooltip for bar (or 1 pixel segment of bar)
{
int barStartIndex = offsetToDisplayMapping + m_Values[bar.startDataOffset].frameOffset;
int barEndIndex = offsetToDisplayMapping + m_Values[bar.endDataOffset].frameOffset;
string tooltip;
if (barStartIndex == barEndIndex)
tooltip = string.Format("Frame {0}\n{1}{2}", barStartIndex, ToDisplayUnits(bar.yMax, true), detailsString);
else
tooltip = string.Format("Frame {0}-{1}\n{2} max\n{3} min{4}", barStartIndex, barEndIndex, ToDisplayUnits(bar.yMax, true), ToDisplayUnits(bar.yMin, true), detailsString);
if (inSelectionRegion)
tooltip += selectionAreaString;
GUI.Label(new Rect(rect.x + bar.x, rect.y + 5, bar.w, height), new GUIContent("", tooltip));
}
}
}
GUI.enabled = lastEnabled;
if (showAxis)
{
int zoomedSelectedFirstOffset = selectedFirstOffset;
int zoomedSelectedLastOffset = selectedLastOffset;
int zoomedSelectedCount = selectedCount;
if (m_Zoomed)
{
if (selectedFirstOffset > endOffset || selectedLastOffset < startOffset)
{
zoomedSelectedCount = 0;
}
else
{
// Clamp selection range to zoom range
zoomedSelectedFirstOffset = ClampToRange(selectedFirstOffset, startOffset, endOffset);
zoomedSelectedLastOffset = ClampToRange(selectedLastOffset, startOffset, endOffset);
if (HasDragRegion())
{
zoomedSelectedCount = 1 + (zoomedSelectedLastOffset - zoomedSelectedFirstOffset);
}
else
{
zoomedSelectedCount = 0;
foreach (var offset in selectedFrameOffsets)
{
if (offset >= startOffset && offset <= endOffset)
{
zoomedSelectedCount++;
}
}
}
}
}
ShowAxis(rect, xStart, width, startOffset, endOffset, zoomedSelectedFirstOffset, zoomedSelectedLastOffset, zoomedSelectedCount, selectedCount, yMax, totalDataSize, offsetToDisplayMapping);
}
GUI.enabled = enabled;
if (rect.Contains(current.mousePosition) && current.type == EventType.ContextClick)
{
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
ShowContextMenu(subsetSelected, selectedCount);
current.Use();
ProfileAnalyzerAnalytics.SendUIVisibilityEvent(ProfileAnalyzerAnalytics.UIVisibility.FrameTimeContextMenu, analytic.GetDurationInSeconds(), true);
}
GUI.enabled = lastEnabled;
Profiler.EndSample();
}
void ShowThreads(float height, float yRange, BarData bar, bool full,
List<ThreadData> threads, bool subsetSelected, int selectedFirstOffset, int selectedLastOffset,
int offsetToIndexMapping, Dictionary<int, int> frameOffsetToSelectionIndex)
{
float max = float.MinValue;
bool selected = false;
for (int dataOffset = bar.startDataOffset; dataOffset <= bar.endDataOffset; dataOffset++)
{
int frameOffset = m_Values[dataOffset].frameOffset;
if (!full && !frameOffsetToSelectionIndex.ContainsKey(frameOffset))
continue;
float threadMs = 0f;
foreach (var thread in threads)
{
int frameIndex = offsetToIndexMapping + frameOffset;
var frame = thread.GetFrame(frameIndex);
if (frame == null)
continue;
float ms = frame.Value.ms;
if (ms > threadMs)
threadMs = ms;
}
if (threadMs > max)
max = threadMs;
if (m_Dragging)
{
if (frameOffset >= selectedFirstOffset && frameOffset <= selectedLastOffset)
selected = true;
}
else if (subsetSelected)
{
if (frameOffsetToSelectionIndex.ContainsKey(frameOffset))
selected = true;
}
}
if (full || selected)
{
// Clamp to frame time (these values can be time summed over multiple threads)
if (max > bar.yMax)
max = bar.yMax;
float maxClamped = Math.Min(max, yRange);
float h = height * maxClamped / yRange;
m_2D.DrawFilledBox(bar.x, bar.y, bar.w, h, selected ? m_ColorBarThreadsSelected : m_ColorBarThreads);
// Show where its been clamped
if (max > yRange)
{
m_2D.DrawFilledBox(bar.x, bar.y + height, bar.w, kOverrunHeight, m_ColorBarThreadsOutOfRange);
}
}
}
void ShowSelectedMarker(float height, float yRange, BarData bar, bool full,
MarkerData selectedMarker, bool subsetSelected, int selectedFirstOffset, int selectedLastOffset,
int offsetToIndexMapping, Dictionary<int, int> frameOffsetToSelectionIndex)
{
float max = 0f;
bool selected = false;
if (selectedMarker != null)
{
for (int dataOffset = bar.startDataOffset; dataOffset <= bar.endDataOffset; dataOffset++)
{
int frameOffset = m_Values[dataOffset].frameOffset;
if (!full && !frameOffsetToSelectionIndex.ContainsKey(frameOffset))
continue;
float ms = selectedMarker.GetFrameMs(offsetToIndexMapping + frameOffset);
if (ms > max)
max = ms;
if (m_Dragging)
{
if (frameOffset >= selectedFirstOffset && frameOffset <= selectedLastOffset)
selected = true;
}
else if (subsetSelected)
{
if (frameOffsetToSelectionIndex.ContainsKey(frameOffset))
selected = true;
}
}
}
if (full || selected)
{
// Clamp to frame time (these values can be tiem summed over multiple threads)
if (max > bar.yMax)
max = bar.yMax;
float maxClamped = Math.Min(max, yRange);
float h = height * maxClamped / yRange;
m_2D.DrawFilledBox(bar.x, bar.y, bar.w, h, selected ? m_ColorBarMarkerSelected : m_ColorBarMarker);
if (max > 0f)
{
// we start the bar lower so that very small markers still show up.
m_2D.DrawFilledBox(bar.x, bar.y - kOverrunHeight, bar.w, kOverrunHeight, m_ColorBarMarkerOutOfRange);
}
// Show where its been clamped
if (max > yRange)
{
m_2D.DrawFilledBox(bar.x, bar.y + height, bar.w, kOverrunHeight, m_ColorBarMarkerOutOfRange);
}
}
}
void ShowFrameLinesText(Rect rect, float xStart, float yStart, float yRange, float width, float height, bool subsetSelected, int selectedFirstOffset, int selectedLastOffset)
{
int totalDataSize = m_Values.Count;
float y = yStart;
float msSegment = 1000f / 60f;
int lines = (int)(yRange / msSegment);
int step = 1;
for (int line = 1; line <= lines; line += step, step *= 2)
{
float ms = line * msSegment;
float h = height * ms / yRange;
int edgePad = 3;
if (h >= (height / 4) && h < (height - GUI.skin.label.lineHeight))
{
GUIContent content = new GUIContent(ToDisplayUnits((float)Math.Floor(ms), true, 0));
Vector2 size = EditorStyles.miniTextField.CalcSize(content);
bool left = true;
if (subsetSelected)
{
float x = GetXForDataOffset(selectedFirstOffset, (int)width, totalDataSize);
float x2 = GetXForDataOffset(selectedLastOffset + 1, (int)width, totalDataSize);
// text would overlap selection so move it if that prevents overlap
if (left)
{
if (x < (size.x + edgePad) && x2 < (width - (size.x + edgePad)))
left = false;
}
else
{
if (x > (size.x + edgePad) && x2 > (width - (size.x + edgePad)))
left = true;
}
}
Rect r;
if (left)
r = new Rect(rect.x + (xStart + edgePad), (rect.y - y) + (height - h), size.x, EditorStyles.miniTextField.lineHeight);
else
r = new Rect(rect.x + (xStart + width) - (size.x + edgePad), (rect.y - y) + (height - h), size.x, EditorStyles.miniTextField.lineHeight);
GUI.Label(r, content, EditorStyles.miniTextField);
}
}
}
void ShowSelectionMenuItem(bool subsetSelected, GenericMenu menu, GUIContent style, bool state, GenericMenu.MenuFunction func)
{
if (subsetSelected)
menu.AddItem(style, state, func);
else
menu.AddDisabledItem(style);
}
void ShowContextMenu(bool subsetSelected, int selectionCount)
{
GenericMenu menu = new GenericMenu();
bool showselectionOptions = subsetSelected || ((m_PairedWithFrameTimeGraph != null) && m_PairedWithFrameTimeGraph.HasSubsetSelected());
ShowSelectionMenuItem(showselectionOptions || selectionCount == 0, menu, Styles.menuItemSelectAll, false, () => SelectAll());
ShowSelectionMenuItem(showselectionOptions || selectionCount == m_Values.Count, menu, Styles.menuItemClearSelection, false, () => ClearSelection());
menu.AddItem(Styles.menuItemInvertSelection, false, () => InvertSelection());
menu.AddItem(Styles.menuItemSelectMin, false, () => SelectMin());
menu.AddItem(Styles.menuItemSelectMax, false, () => SelectMax());
menu.AddItem(Styles.menuItemSelectMedian, false, () => SelectMedian());
menu.AddSeparator("");
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectPrevious, false, () => SelectPrevious(1));
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectNext, false, () => SelectNext(1));
menu.AddSeparator("");
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectGrow, false, () => SelectGrow(1));
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectShrink, false, () => SelectShrink(1));
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectGrowLeft, false, () => SelectGrowLeft(1));
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectGrowRight, false, () => SelectGrowRight(1));
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectShrinkLeft, false, () => SelectShrinkLeft(1));
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectShrinkRight, false, () => SelectShrinkRight(1));
menu.AddSeparator("");
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectGrowFast, false, () => SelectGrow(10));
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectShrinkFast, false, () => SelectShrink(10));
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectGrowLeftFast, false, () => SelectGrowLeft(10));
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectGrowRightFast, false, () => SelectGrowRight(10));
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectShrinkLeftFast, false, () => SelectShrinkLeft(10));
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectShrinkRightFast, false, () => SelectShrinkRight(10));
menu.AddSeparator("");
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemZoomSelection, false, () => ZoomSelection());
ShowSelectionMenuItem(m_Zoomed, menu, Styles.menuItemZoomAll, false, () => ZoomAll());
menu.AddSeparator("");
menu.AddItem(Styles.menuItemShowSelectedMarker, m_GlobalSettings.showSelectedMarker, () => ToggleShowSelectedMarker());
menu.AddItem(Styles.menuItemShowThreads, m_GlobalSettings.showThreads, () => ToggleShowThreads());
menu.AddItem(Styles.menuItemShowFrameLines, m_GlobalSettings.showFrameLines, () => ToggleShowFrameLines());
//menu.AddItem(Styles.menuItemShowFrameLineText, m_GlobalSettings.showFrameLineText, () => ToggleShowFrameLinesText());
menu.AddSeparator("");
menu.AddItem(Styles.menuItemShowOrderedByFrameDuration, m_GlobalSettings.showOrderedByFrameDuration, () => ToggleShowOrderedByFrameDuration());
menu.ShowAsContext();
}
string GetYMaxText(float value)
{
return ToDisplayUnits(value, true, 0);
}
void DrawYAxisRangeSelector(Rect rect, float yMax)
{
string yMaxText = GetYMaxText(yMax);
List<GUIContent> yAxisOptions = new List<GUIContent>();
var graphScaleUnits = ToDisplayUnits(1000f / 60f, true, 0);
yAxisOptions.Add(new GUIContent(graphScaleUnits, string.Format("Graph Scale : {0} is equivalent to 60Hz or 60FPS.", graphScaleUnits)));
graphScaleUnits = ToDisplayUnits(1000f / 30f, true, 0);
yAxisOptions.Add(new GUIContent(graphScaleUnits, string.Format("Graph Scale : {0} is equivalent to 30Hz or 30FPS.", graphScaleUnits)));
graphScaleUnits = ToDisplayUnits(1000f / 15f, true, 0);
yAxisOptions.Add(new GUIContent(graphScaleUnits, string.Format("Graph Scale : {0} is equivalent to 15Hz or 15FPS.", graphScaleUnits)));
yAxisOptions.Add(new GUIContent(yMaxText, "Graph Scale : Max frame time from data"));
float width = 0;
foreach (var content in yAxisOptions)
{
Vector2 size = EditorStyles.popup.CalcSize(content);
if (size.x > width)
width = size.x;
}
// Use smaller width if text is shorter
int margin = 2;
width = Math.Min(width + margin, rect.width);
// Shift right to right align
rect.x += (rect.width - width);
rect.x -= margin;
rect.width = width;
s_YAxisMode = (AxisMode)EditorGUI.Popup(rect, (int)s_YAxisMode, yAxisOptions.ToArray());
}
void ShowAxis(Rect rect, float xStart, float width, int startOffset, int endOffset, int selectedFirstOffset, int selectedLastOffset, int selectedCount, int totalSelectedCount, float yMax, int totalDataSize, int offsetToDisplayMapping)
{
GUIStyle leftAlignStyle = new GUIStyle(GUI.skin.label);
leftAlignStyle.padding = new RectOffset(leftAlignStyle.padding.left, leftAlignStyle.padding.right, 0, 0);
leftAlignStyle.alignment = TextAnchor.MiddleLeft;
GUIStyle rightAlignStyle = new GUIStyle(GUI.skin.label);
rightAlignStyle.padding = new RectOffset(rightAlignStyle.padding.left, rightAlignStyle.padding.right, 0, 0);
rightAlignStyle.alignment = TextAnchor.MiddleRight;
// y axis
float h = GUI.skin.label.lineHeight;
float y = rect.y + ((rect.height - 1) - h);
DrawYAxisRangeSelector(new Rect(rect.x, rect.y, kXAxisWidth, h), yMax);
string yMinText = ToDisplayUnits(0, true);
GUI.Label(new Rect(rect.x, y - h, kXAxisWidth, h), yMinText, rightAlignStyle);
// x axis
rect.x += xStart;
leftAlignStyle.padding = new RectOffset(0, 0, leftAlignStyle.padding.top, leftAlignStyle.padding.bottom);
rightAlignStyle.padding = new RectOffset(0, 0, rightAlignStyle.padding.top, rightAlignStyle.padding.bottom);
int startIndex = offsetToDisplayMapping + startOffset;
string startIndexText = string.Format("{0}", startIndex);
GUIContent startIndexContent = new GUIContent(startIndexText);
Vector2 startIndexSize = GUI.skin.label.CalcSize(startIndexContent);
bool drawStart = !m_GlobalSettings.showOrderedByFrameDuration;
int endIndex = offsetToDisplayMapping + endOffset;
string endIndexText = string.Format("{0}", endIndex);
GUIContent endIndexContent = new GUIContent(endIndexText);
Vector2 endIndexSize = GUI.skin.label.CalcSize(endIndexContent);
bool drawEnd = !m_GlobalSettings.showOrderedByFrameDuration;
// Show selection frame values (if space for them)
if (totalSelectedCount > 0)
{
if (selectedCount == 0)
{
// If we have no selection then adjust 'selection start/end to span whole view so the count display is centred)
selectedFirstOffset = startOffset;
selectedLastOffset = endOffset;
}
int selectedFirstX = GetXForDataOffset(selectedFirstOffset, (int)width, totalDataSize);
int selectedLastX = GetXForDataOffset(selectedLastOffset + 1, (int)width, totalDataSize); // last + 1 so right hand side of the bbar
int selectedRangeWidth = 1 + (selectedLastX - selectedFirstX);
int selectedFirstIndex = offsetToDisplayMapping + selectedFirstOffset;
int selectedLastIndex = offsetToDisplayMapping + selectedLastOffset;
string selectionCountText;
if (totalSelectedCount != selectedCount)
selectionCountText = string.Format("[{0} of {1}]", selectedCount, totalSelectedCount);
else
selectionCountText = string.Format("[{0}]", selectedCount);
string selectionRangeText;
if (selectedCount > 1)
{
if (m_GlobalSettings.showOrderedByFrameDuration)
selectionRangeText = selectionCountText;
else
selectionRangeText = string.Format("{0} {1} {2}", selectedFirstIndex, selectionCountText, selectedLastIndex);
}
else
selectionRangeText = string.Format("{0} {1}", selectedFirstIndex, selectionCountText);
string tooltip = string.Format("{0} frames in selection", selectedCount);
if (totalSelectedCount != selectedCount)
{
tooltip = string.Format("{0} frames in zoomed selection\n{1} frames in overall selection", selectedCount, totalSelectedCount);
}
GUIContent selectionRangeTextContent = new GUIContent(selectionRangeText, tooltip);
Vector2 selectionRangeTextSize = GUI.skin.label.CalcSize(selectionRangeTextContent);
if ((selectedRangeWidth > selectionRangeTextSize.x && selectedCount > 1) || selectedCount == 0)
{
// Selection width is larger than the text so we can split the text
string selectedFirstIndexText = string.Format("{0}", selectedFirstIndex);
GUIContent selectedFirstIndexContent = new GUIContent(selectedFirstIndexText);
Vector2 selectedFirstIndexSize = GUI.skin.label.CalcSize(selectedFirstIndexContent);
if (m_GlobalSettings.showOrderedByFrameDuration)
selectedFirstIndexSize.x = 0;
string selectedLastIndexText = string.Format("{0}", selectedLastIndex);
GUIContent selectedLastIndexContent = new GUIContent(selectedLastIndexText);
Vector2 selectedLastIndexSize = GUI.skin.label.CalcSize(selectedLastIndexContent);
if (m_GlobalSettings.showOrderedByFrameDuration)
selectedLastIndexSize.x = 0;
GUIContent selectedCountContent = new GUIContent(selectionCountText, tooltip);
Vector2 selectedCountSize = GUI.skin.label.CalcSize(selectedCountContent);
Rect rFirst = new Rect(rect.x + selectedFirstX, y, selectedFirstIndexSize.x, selectedFirstIndexSize.y);
GUI.Label(rFirst, selectedFirstIndexContent);
Rect rLast = new Rect(rect.x + selectedLastX - selectedLastIndexSize.x, y, selectedLastIndexSize.x, selectedLastIndexSize.y);
GUI.Label(rLast, selectedLastIndexContent);
float mid = selectedFirstX + ((selectedLastX - selectedFirstX) / 2);
Rect rCount = new Rect(rect.x + mid - (selectedCountSize.x / 2), y, selectedCountSize.x, selectedCountSize.y);
GUI.Label(rCount, selectedCountContent);
if (selectedFirstX < startIndexSize.x)
{
// would overlap with start text
drawStart = false;
}
if (selectedLastX > ((width - 1) - endIndexSize.x))
{
// would overlap with end text
drawEnd = false;
}
}
else
{
int mid = (selectedFirstX + (selectedRangeWidth / 2));
int selectionTextX = mid - (int)(selectionRangeTextSize.x / 2);
selectionTextX = ClampToRange(selectionTextX, 0, (int)((width - 1) - selectionRangeTextSize.x));
Rect rangeRect = new Rect(rect.x + selectionTextX, y, selectionRangeTextSize.x, selectionRangeTextSize.y);
GUI.Label(rangeRect, selectionRangeTextContent);
if (selectionTextX < startIndexSize.x)
{
// would overlap with start text
drawStart = false;
}
if ((selectionTextX + selectionRangeTextSize.x) > ((width - 1) - endIndexSize.x))
{
// would overlap with end text
drawEnd = false;
}
}
}
// Show start and end values
if (drawStart)
{
Rect leftRect = new Rect(rect.x, y, startIndexSize.x, startIndexSize.y);
GUI.Label(leftRect, startIndexContent, leftAlignStyle);
}
if (drawEnd)
{
Rect rightRect = new Rect(rect.x + ((width - 1) - endIndexSize.x), y, endIndexSize.x, endIndexSize.y);
GUI.Label(rightRect, endIndexContent, rightAlignStyle);
}
}
void ClearSelection(bool effectPaired = true)
{
int dataLength = m_Values.Count;
bool singleControlAction = true; // As we need the frame range to be unique to each data set
CallSetRange(-1, -1, 0, singleControlAction, FrameTimeGraph.State.DragComplete);
// Disable zoom
m_Zoomed = false;
if (m_PairedWithFrameTimeGraph != null && effectPaired)
m_PairedWithFrameTimeGraph.ClearSelection(false);
}
void SelectAll(bool effectPaired = true)
{
int dataLength = m_Values.Count;
bool singleControlAction = true; // As we need the frame range to be unique to each data set
CallSetRange(0, dataLength - 1, 0, singleControlAction, FrameTimeGraph.State.DragComplete);
// Disable zoom
m_Zoomed = false;
if (m_PairedWithFrameTimeGraph != null && effectPaired)
m_PairedWithFrameTimeGraph.SelectAll(false);
}
void InvertSelection(bool effectPaired = true)
{
int dataLength = m_Values.Count;
Dictionary<int, int> frameOffsetToSelectionIndex = new Dictionary<int, int>();
for (int i = 0; i < m_CurrentSelection.Count; i++)
{
int frameOffset = m_CurrentSelection[i];
frameOffsetToSelectionIndex[frameOffset] = i;
}
m_CurrentSelection.Clear();
for (int frameIndex = 0; frameIndex < dataLength; frameIndex++)
{
if (!frameOffsetToSelectionIndex.ContainsKey(frameIndex))
m_CurrentSelection.Add(frameIndex);
}
m_SetRange(m_CurrentSelection, 1, FrameTimeGraph.State.DragComplete);
// Disable zoom
m_Zoomed = false;
if (m_PairedWithFrameTimeGraph != null && effectPaired)
m_PairedWithFrameTimeGraph.InvertSelection(false);
}
void ZoomSelection(bool effectPaired = true)
{
m_Zoomed = true;
m_ZoomStartOffset = m_CurrentSelectionFirstDataOffset;
m_ZoomEndOffset = m_CurrentSelectionLastDataOffset;
if (m_PairedWithFrameTimeGraph != null && effectPaired)
m_PairedWithFrameTimeGraph.ZoomSelection(false);
}
void ZoomAll(bool effectPaired = true)
{
m_Zoomed = false;
int frames = m_Values.Count;
m_ZoomStartOffset = 0;
m_ZoomEndOffset = frames - 1;
if (m_PairedWithFrameTimeGraph != null && effectPaired)
m_PairedWithFrameTimeGraph.ZoomAll(false);
}
void SelectMin(bool effectPaired = true)
{
int dataLength = m_Values.Count;
if (dataLength <= 0)
return;
int minDataOffset = 0;
float msMin = m_Values[0].ms;
for (int dataOffset = 0; dataOffset < dataLength; dataOffset++)
{
float ms = m_Values[dataOffset].ms;
if (ms < msMin)
{
msMin = ms;
minDataOffset = dataOffset;
}
}
bool singleControlAction = true;
CallSetRange(minDataOffset, minDataOffset, 1, singleControlAction, State.DragComplete); // act like single click, so we select frame
//CallSetRange(minDataOffset, minDataOffset, 2, singleControlAction, State.DragComplete); // act like double click, so we jump to the frame
m_Zoomed = false;
if (m_PairedWithFrameTimeGraph != null && effectPaired)
m_PairedWithFrameTimeGraph.SelectMin(false);
}
void SelectMax(bool effectPaired = true)
{
int dataLength = m_Values.Count;
if (dataLength <= 0)
return;
int maxDataOffset = 0;
float msMax = m_Values[0].ms;
for (int dataOffset = 0; dataOffset < dataLength; dataOffset++)
{
float ms = m_Values[dataOffset].ms;
if (ms > msMax)
{
msMax = ms;
maxDataOffset = dataOffset;
}
}
bool singleControlAction = true;
CallSetRange(maxDataOffset, maxDataOffset, 1, singleControlAction, State.DragComplete); // act like single click, so we select frame
//CallSetRange(maxDataOffset, maxDataOffset, 2, singleControlAction, State.DragComplete); // act like double click, so we jump to the frame
m_Zoomed = false;
if (m_PairedWithFrameTimeGraph != null && effectPaired)
m_PairedWithFrameTimeGraph.SelectMax(false);
}
float GetPercentageOffset(List<Data> data, float percent, out int outputFrameOffset)
{
int index = (int)((data.Count - 1) * percent / 100);
outputFrameOffset = data[index].frameOffset;
// 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 data[index].ms;
}
void SelectMedian(bool effectPaired = true)
{
int dataLength = m_Values.Count;
if (dataLength <= 0)
return;
List<Data> sortedValues = new List<Data>();
foreach (var value in m_Values)
{
Data data = new Data(value.ms, value.frameOffset);
sortedValues.Add(data);
}
// If ms value is the same then order by frame offset
sortedValues.Sort((a, b) => { int compare = a.ms.CompareTo(b.ms); return compare == 0 ? a.frameOffset.CompareTo(b.frameOffset) : compare; });
int medianFrameOffset = 0;
GetPercentageOffset(sortedValues, 50, out medianFrameOffset);
int medianDataOffset = m_FrameOffsetToDataOffsetMapping[medianFrameOffset];
bool singleControlAction = true;
CallSetRange(medianDataOffset, medianDataOffset, 1, singleControlAction, State.DragComplete); // act like single click, so we select frame
//CallSetRange(medianDataOffset, medianDataOffset, 2, singleControlAction, State.DragComplete); // act like double click, so we jump to the frame
m_Zoomed = false;
if (m_PairedWithFrameTimeGraph != null && effectPaired)
m_PairedWithFrameTimeGraph.SelectMedian(false);
}
public void SelectPrevious(int step, bool effectPaired = true)
{
int clicks = 1;
bool singleClickAction = true;
MoveSelectedRange(-step, clicks, singleClickAction, State.DragComplete);
if (m_PairedWithFrameTimeGraph != null && effectPaired)
m_PairedWithFrameTimeGraph.SelectPrevious(step, false);
}
public void SelectNext(int step, bool effectPaired = true)
{
int clicks = 1;
bool singleClickAction = true;
MoveSelectedRange(step, clicks, singleClickAction, State.DragComplete);
if (m_PairedWithFrameTimeGraph != null && effectPaired)
m_PairedWithFrameTimeGraph.SelectNext(step, false);
}
public void SelectGrow(int step, bool effectPaired = true)
{
int clicks = 1;
bool singleClickAction = true;
ResizeSelectedRange(-step, step, clicks, singleClickAction, State.DragComplete);
if (m_PairedWithFrameTimeGraph != null && effectPaired)
m_PairedWithFrameTimeGraph.SelectGrow(step, false);
}
public void SelectGrowLeft(int step, bool effectPaired = true)
{
int clicks = 1;
bool singleClickAction = true;
ResizeSelectedRange(-step, 0, clicks, singleClickAction, State.DragComplete);
if (m_PairedWithFrameTimeGraph != null && effectPaired)
m_PairedWithFrameTimeGraph.SelectGrowLeft(step, false);
}
public void SelectGrowRight(int step, bool effectPaired = true)
{
int clicks = 1;
bool singleClickAction = true;
ResizeSelectedRange(0, step, clicks, singleClickAction, State.DragComplete);
if (m_PairedWithFrameTimeGraph != null && effectPaired)
m_PairedWithFrameTimeGraph.SelectGrowRight(step, false);
}
public void SelectShrink(int step, bool effectPaired = true)
{
int clicks = 1;
bool singleClickAction = true;
ResizeSelectedRange(step, -step, clicks, singleClickAction, State.DragComplete);
if (m_PairedWithFrameTimeGraph != null && effectPaired)
m_PairedWithFrameTimeGraph.SelectShrink(step, false);
}
public void SelectShrinkLeft(int step, bool effectPaired = true)
{
int clicks = 1;
bool singleClickAction = true;
ResizeSelectedRange(step, 0, clicks, singleClickAction, State.DragComplete);
if (m_PairedWithFrameTimeGraph != null && effectPaired)
m_PairedWithFrameTimeGraph.SelectShrinkLeft(step, false);
}
public void SelectShrinkRight(int step, bool effectPaired = true)
{
int clicks = 1;
bool singleClickAction = true;
ResizeSelectedRange(0, -step, clicks, singleClickAction, State.DragComplete);
if (m_PairedWithFrameTimeGraph != null && effectPaired)
m_PairedWithFrameTimeGraph.SelectShrinkRight(step, false);
}
public void ToggleShowThreads()
{
m_GlobalSettings.showThreads ^= true;
}
public void ToggleShowSelectedMarker()
{
m_GlobalSettings.showSelectedMarker ^= true;
}
public void ToggleShowFrameLines()
{
m_GlobalSettings.showFrameLines ^= true;
}
public void ToggleShowFrameLinesText()
{
m_GlobalSettings.showFrameLineText ^= true;
}
public void ToggleShowOrderedByFrameDuration()
{
m_GlobalSettings.showOrderedByFrameDuration ^= true;
SetData(m_Values); // Update order
if (m_PairedWithFrameTimeGraph != null)
{
m_PairedWithFrameTimeGraph.SetData(m_PairedWithFrameTimeGraph.m_Values); // Update order
}
}
internal struct SelectedRangeState
{
public int currentSelectionFirstDataOffset;
public int currentSelectionLastDataOffset;
public List<int> lastSelectedFrameOffsets;
}
void MoveSelectedRange(int offset, int clickCount, bool singleControlAction, State inputStatus)
{
MoveSelectedRange(offset, clickCount, singleControlAction, inputStatus, new SelectedRangeState()
{
currentSelectionFirstDataOffset = m_CurrentSelectionFirstDataOffset,
currentSelectionLastDataOffset = m_CurrentSelectionLastDataOffset,
lastSelectedFrameOffsets = m_LastSelectedFrameOffsets,
});
}
internal void MoveSelectedRange(int offset, int clickCount, bool singleControlAction, State inputStatus, SelectedRangeState selectedRange)
{
var currentSelectionFirstDataOffset = selectedRange.currentSelectionFirstDataOffset;
var currentSelectionLastDataOffset = selectedRange.currentSelectionLastDataOffset;
var lastSelectedFrameOffsets = selectedRange.lastSelectedFrameOffsets;
if (offset < 0)
{
// Clamp offset to graph lower bound.
if (currentSelectionFirstDataOffset + offset < 0)
{
offset = -currentSelectionFirstDataOffset;
}
}
else
{
// Clamp offset to graph upper bound.
if (currentSelectionLastDataOffset + offset >= m_Values.Count)
{
offset = (m_Values.Count - 1) - currentSelectionLastDataOffset;
}
}
// Offset selection.
List<int> selected = new List<int>();
foreach (int selectedFrameOffset in lastSelectedFrameOffsets)
{
var selectedDataOffset = m_FrameOffsetToDataOffsetMapping[selectedFrameOffset];
var newDataOffset = selectedDataOffset + offset;
if (newDataOffset >= 0 && newDataOffset < m_Values.Count)
{
var newFrameOffset = m_Values[newDataOffset].frameOffset;
if (!selected.Contains(newFrameOffset))
{
selected.Add(newFrameOffset);
}
}
}
// Sort selection in frame index order.
selected.Sort();
m_SetRange(selected, clickCount, inputStatus);
}
void ResizeSelectedRange(int leftOffset, int rightOffset, int clickCount, bool singleControlAction, State inputState)
{
ResizeSelectedRange(leftOffset, rightOffset, clickCount, singleControlAction, inputState, new SelectedRangeState()
{
currentSelectionFirstDataOffset = m_CurrentSelectionFirstDataOffset,
currentSelectionLastDataOffset = m_CurrentSelectionLastDataOffset,
lastSelectedFrameOffsets = m_LastSelectedFrameOffsets,
});
}
internal void ResizeSelectedRange(int leftOffset, int rightOffset, int clickCount, bool singleControlAction, State inputState, SelectedRangeState selectedRange)
{
const int k_InvalidDataOffset = -1;
var currentSelectionFirstDataOffset = selectedRange.currentSelectionFirstDataOffset;
var currentSelectionLastDataOffset = selectedRange.currentSelectionLastDataOffset;
var lastSelectedFrameOffsets = selectedRange.lastSelectedFrameOffsets;
// Clamp left offset to lower graph bound.
bool isGrowingLeft = leftOffset < 0;
if (isGrowingLeft && currentSelectionFirstDataOffset + leftOffset < 0)
{
leftOffset = -currentSelectionFirstDataOffset;
}
// Clamp right offset to upper graph bound.
bool isGrowingRight = rightOffset > 0;
if (isGrowingRight && currentSelectionLastDataOffset + rightOffset > (m_Values.Count - 1))
{
rightOffset = (m_Values.Count - 1) - currentSelectionLastDataOffset;
}
int selectionStartDataOffset = k_InvalidDataOffset;
List<int> selected = new List<int>(lastSelectedFrameOffsets);
for (int dataOffset = 0; dataOffset < m_Values.Count; ++dataOffset)
{
var frameOffset = m_Values[dataOffset].frameOffset;
if (selectionStartDataOffset == k_InvalidDataOffset)
{
// Find selection start.
if (lastSelectedFrameOffsets.Contains(frameOffset))
{
selectionStartDataOffset = dataOffset;
}
}
else
{
// Find selection end.
bool isSelected = lastSelectedFrameOffsets.Contains(frameOffset);
if (!isSelected || dataOffset == (m_Values.Count - 1))
{
int selectionEndDataOffset;
// If we reached the last index and it is selected, this index is the selection end. Otherwise, the previous index is the last selected.
bool isLastIndex = dataOffset == (m_Values.Count - 1);
if (isLastIndex && isSelected)
{
selectionEndDataOffset = dataOffset;
}
else
{
selectionEndDataOffset = dataOffset - 1;
}
int newSelectionStartDataOffset = Mathf.Clamp(selectionStartDataOffset + leftOffset, 0, m_Values.Count - 1);
int newSelectionEndDataOffset = Mathf.Clamp(selectionEndDataOffset + rightOffset, 0, m_Values.Count - 1);
// Enforce a minimum selection width.
if (newSelectionEndDataOffset < newSelectionStartDataOffset)
{
var maximumOffset = (selectionEndDataOffset - selectionStartDataOffset) * 0.5f;
var adjustedLeftOffset = Mathf.CeilToInt(maximumOffset);
newSelectionStartDataOffset = Mathf.Clamp(selectionStartDataOffset + adjustedLeftOffset, 0, m_Values.Count - 1);
var adjustedRightOffset = -Mathf.FloorToInt(maximumOffset);
newSelectionEndDataOffset = Mathf.Clamp(selectionEndDataOffset + adjustedRightOffset, 0, m_Values.Count - 1);
}
if (selectionStartDataOffset != newSelectionStartDataOffset)
{
// Resize from left edge.
int startDataOffset = Mathf.Min(selectionStartDataOffset, newSelectionStartDataOffset);
int endDataOffset = Mathf.Max(selectionStartDataOffset, newSelectionStartDataOffset);
MoveSelectionEdge(startDataOffset, endDataOffset, isGrowingLeft, ref selected);
}
if (selectionEndDataOffset != newSelectionEndDataOffset)
{
// Resize from right edge (iterate backwards).
int startDataOffset = Mathf.Max(selectionEndDataOffset, newSelectionEndDataOffset);
int endDataOffset = Mathf.Min(selectionEndDataOffset, newSelectionEndDataOffset);
MoveSelectionEdge(startDataOffset, endDataOffset, isGrowingRight, ref selected);
}
// Reset to find next selection.
selectionStartDataOffset = k_InvalidDataOffset;
}
}
}
// Sort selection in frame index order.
selected.Sort();
m_SetRange(selected, clickCount, inputState);
}
void MoveSelectionEdge(int startDataOffset, int endDataOffset, bool isGrowingFromEdge, ref List<int> selection)
{
var direction = (startDataOffset >= endDataOffset) ? -1 : 1;
for (int dataOffset = startDataOffset; dataOffset != endDataOffset; dataOffset += direction)
{
var frameOffset = m_Values[dataOffset].frameOffset;
var indexInSelection = selection.IndexOf(frameOffset);
if (isGrowingFromEdge)
{
if (indexInSelection == -1)
{
selection.Add(frameOffset);
}
}
else
{
if (indexInSelection != -1)
{
selection.RemoveAt(indexInSelection);
}
}
}
}
}
}

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,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,343 @@
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 float countStandardDeviation;
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 float msStandardDeviation;
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 double timeRemoved;
public double timeIgnored;
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;
countStandardDeviation = 0f;
lastFrame = -1;
presentOnFrameCount = 0;
firstFrameIndex = -1;
msMean = 0f;
msMedian = 0f;
msLowerQuartile = 0f;
msUpperQuartile = 0f;
msMin = float.MaxValue;
msMax = float.MinValue;
msStandardDeviation = 0f;
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;
}
timeRemoved = 0.0;
timeIgnored = 0.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;
// using a for loop instead of foreach is surprisingly faster on Mono
for (int i = 0, n = frames.Count; i < n; i++)
{
var frameTime = frames[i];
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;
// using a for loop instead of foreach is surprisingly faster on Mono
for (int i = 0, n = frames.Count; i < n; i++)
{
var frameTime = frames[i];
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;
}
public static double GetTimeRemoved(MarkerData marker)
{
return marker != null ? marker.timeRemoved : 0.0;
}
public static double GetTimeIgnored(MarkerData marker)
{
return marker != null ? marker.timeIgnored : 0.0;
}
public bool IsFullyIgnored()
{
if (timeIgnored > 0.0)
{
if (msTotal == 0.0)
return true;
}
return false;
}
}
}

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,405 @@
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;
}
void CalculateStandardDeviations(MarkerData marker)
{
if (marker.frames.Count <= 1)
{
marker.msStandardDeviation = 0;
marker.countStandardDeviation = 0;
return;
}
int frameCount = marker.frames.Count;
float msMean = marker.msMean;
float countMean = marker.countMean;
double msSum = 0.0;
double countSum = 0.0;
for (int i = 0; i < frameCount; ++i)
{
float delta = (marker.frames[i].ms - msMean);
msSum += (delta * delta);
delta = (marker.frames[i].count - countMean);
countSum += (delta * delta);
}
double variance = msSum / (frameCount - 1);
marker.msStandardDeviation = (float)Math.Sqrt(variance);
variance = countSum / (frameCount - 1);
marker.countStandardDeviation = (float)Math.Sqrt(variance);
}
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;
CalculateStandardDeviations(marker);
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()
{
// using a for loop instead of foreach is surprisingly faster on Mono
for (int i = 0, n = m_Markers.Count; i < n; i++)
{
var marker = m_Markers[i];
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;
// using a for loop instead of foreach is surprisingly faster on Mono
for (int i = 0, n = m_FrameSummary.frames.Count; i < n; i++)
{
var frameData = m_FrameSummary.frames[i];
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,553 @@
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 void RemoveMarkerTimeFromParents(MarkerData[] markers, ProfileData profileData, ProfileThread threadData, int markerAt)
{
// Get the info for the marker we plan to remove (assume thats what we are at)
ProfileMarker profileMarker = threadData.markers[markerAt];
float markerTime = profileMarker.msMarkerTotal;
// Traverse parents and remove time from them
int currentDepth = profileMarker.depth;
for (int parentMarkerAt = markerAt - 1; parentMarkerAt >= 0; parentMarkerAt--)
{
ProfileMarker parentMarkerData = threadData.markers[parentMarkerAt];
if (parentMarkerData.depth == currentDepth - 1)
{
currentDepth--;
if (parentMarkerData.nameIndex < markers.Length) // Had an issue where marker not yet processed(marker from another thread)
{
MarkerData parentMarker = markers[parentMarkerData.nameIndex];
// If a depth slice is applied we may not have a parent marker stored
if (parentMarker != null)
{
// Revise the duration of parent to remove time from there too
// Note if the marker to remove is nested (i.e. parent of the same name, this could reduce the msTotal, more than we add to the timeIgnored)
parentMarker.msTotal -= markerTime;
// Reduce from the max marker time too
// This could be incorrect when there are many instances that contribute the the total time
if (parentMarker.msMaxIndividual > markerTime)
{
parentMarker.msMaxIndividual -= markerTime;
}
if (parentMarker.msMinIndividual > markerTime)
{
parentMarker.msMinIndividual -= markerTime;
}
// Revise stored frame time
FrameTime frameTime = parentMarker.frames[parentMarker.frames.Count - 1];
frameTime = new FrameTime(frameTime.frameIndex, frameTime.ms - markerTime, frameTime.count);
parentMarker.frames[parentMarker.frames.Count - 1] = frameTime;
// Note that we have modified the time
parentMarker.timeRemoved += markerTime;
// Note markerTime can be 0 in some cases.
// Make sure timeRemoved is never left at 0.0
// This makes sure we can test for non zero to indicate the marker has been removed
if (parentMarker.timeRemoved == 0.0)
parentMarker.timeRemoved = double.Epsilon;
}
}
}
}
}
public int RemoveMarker(ProfileThread threadData, int markerAt)
{
ProfileMarker profileMarker = threadData.markers[markerAt];
int at = markerAt;
// skip marker
at++;
// Skip children
int currentDepth = profileMarker.depth;
while (at < threadData.markers.Count)
{
profileMarker = threadData.markers[at];
if (profileMarker.depth <= currentDepth)
break;
at++;
}
// Mark the following number to be ignored
int markerAndChildCount = at - markerAt;
return markerAndChildCount;
}
public ProfileAnalysis Analyze(ProfileData profileData, List<int> selectionIndices, List<string> threadFilters, int depthFilter, bool selfTimes = false, string parentMarker = null, float timeScaleMax = 0, string removeMarker = null)
{
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 MarkerData[profileData.MarkerNameCount];
var removedMarkers = new Dictionary<string, double>();
var mainThreadIdentifier = new ThreadIdentifier("Main Thread", 1);
int markerCount = 0;
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;
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");
}
}
int markerAndChildCount = 0;
for (int markerAt = 0, n = threadData.markers.Count; markerAt < n; markerAt++)
{
var markerData = threadData.markers[markerAt];
if (markerAndChildCount > 0)
markerAndChildCount--;
string markerName = null;
float ms = markerData.msMarkerTotal - (selfTimes ? markerData.msChildren : 0);
var markerDepth = markerData.depth;
if (markerDepth > maxMarkerDepthFound)
maxMarkerDepthFound = markerDepth;
if (markerDepth == 1)
{
markerName = profileData.GetMarkerName(markerData);
if (markerName.Equals("Idle", StringComparison.Ordinal))
msIdleTimeOfMinDepthMarkers += ms;
else
msTimeOfMinDepthMarkers += ms;
}
if (removeMarker != null)
{
if (markerAndChildCount <= 0) // If we are already removing markers - don't focus on other occurances in the children
{
if (markerName == null)
markerName = profileData.GetMarkerName(markerData);
if (markerName == removeMarker)
{
float removeMarkerTime = markerData.msMarkerTotal;
// Remove this markers time from frame time (if its on the main thread)
if (thread.threadNameWithIndex == mainThreadIdentifier.threadNameWithIndex)
{
msFrame -= removeMarkerTime;
}
if (selfTimes == false) // (Self times would not need thread or parent adjustments)
{
// And from thread time
if (markerName == "Idle")
msIdleTimeOfMinDepthMarkers -= removeMarkerTime;
else
msTimeOfMinDepthMarkers -= removeMarkerTime;
// And from parents
RemoveMarkerTimeFromParents(markers, profileData, threadData, markerAt);
}
markerAndChildCount = RemoveMarker(threadData, markerAt);
}
}
}
if (!include)
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;
}
if (depthFilter != kDepthAll && markerDepth != depthFilter)
continue;
MarkerData marker = markers[markerData.nameIndex];
if (marker != null)
{
if (!marker.threads.Contains(threadNameWithIndex))
marker.threads.Add(threadNameWithIndex);
}
else
{
if (markerName == null)
markerName = profileData.GetMarkerName(markerData);
marker = new MarkerData(markerName);
marker.firstFrameIndex = frameIndex;
marker.minDepth = markerDepth;
marker.maxDepth = markerDepth;
marker.threads.Add(threadNameWithIndex);
analysis.AddMarker(marker);
markers[markerData.nameIndex] = marker;
markerCount += 1;
}
marker.count += 1;
if (markerAndChildCount > 0)
{
marker.timeIgnored += ms;
// Note ms can be 0 in some cases.
// Make sure timeIgnored is never left at 0.0
// This makes sure we can test for non zero to indicate the marker has been ignored
if (marker.timeIgnored == 0.0)
marker.timeIgnored = double.Epsilon;
// zero out removed marker time
// so we don't record in the individual marker times, marker frame times or min/max times
// ('min/max times' is calculated later from marker frame times)
ms = 0f;
}
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));
}
}
analysis.UpdateSummary(frameIndex, msFrame);
at++;
m_Progress = (100 * at) / frameCount;
}
analysis.GetFrameSummary().totalMarkers = profileData.MarkerNameCount;
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,281 @@
using UnityEngine;
using System.IO;
using System;
using System.Collections.Generic;
using System.Globalization;
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 comparisonTables = new GUIContent("Comparison table", "Export data from the comparsion view marker table");
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, 180);
window.position = new Rect(screenX, screenY, 200, 180);
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_ProfileAnalyzerWindow.CanExportComparisonTable())
GUI.enabled = false;
if (GUILayout.Button(Styles.comparisonTables))
SaveComparisonTableCSV();
GUI.enabled = enabled;
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(string.Format(CultureInfo.InvariantCulture,"{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(string.Format(CultureInfo.InvariantCulture, "{0};",
marker.msTotal));
file.Write(string.Format(CultureInfo.InvariantCulture, "{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(string.Format(CultureInfo.InvariantCulture, "{0};{1};",
marker.msMinIndividual, marker.msMaxIndividual));
file.Write("{0};{1};",
minIndividualFrameIndex, maxIndividualFrameIndex);
file.WriteLine(string.Format(CultureInfo.InvariantCulture, "{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(string.Format(CultureInfo.InvariantCulture, "{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(string.Format(CultureInfo.InvariantCulture, "{0};{1};", msFrameLeft, msTimePassedLeft));
file.Write(string.Format(CultureInfo.InvariantCulture, "{0};{1};", msFrameRight, msTimePassedRight));
file.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0}", msFrameDiff));
msTimePassedLeft += msFrameLeft;
msTimePassedRight += msFrameRight;
}
}
ProfileAnalyzerAnalytics.SendUIButtonEvent(ProfileAnalyzerAnalytics.UIButton.ExportComparisonFrames, analytic);
}
}
void SaveComparisonTableCSV()
{
if (m_LeftDataView == null || m_RightDataView == null)
return;
if (!m_LeftDataView.IsDataValid() || !m_RightDataView.IsDataValid())
return;
string path = EditorUtility.SaveFilePanel("Save comparison table CSV data", "", "tableComparison.csv", "csv");
if (path.Length != 0)
{
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
using (StreamWriter file = new StreamWriter(path))
{
m_ProfileAnalyzerWindow.TryExportComparisonTable(file);
}
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,5916 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using UnityEditor.IMGUI.Controls;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.Profiling;
using Debug = UnityEngine.Debug;
using ProfilerCommon = UnityEditorInternal.ProfilerDriver;
using ProfilerMarkerAbstracted = Unity.Profiling.ProfilerMarker;
namespace UnityEditor.Performance.ProfileAnalyzer
{
enum ThreadRange
{
Median,
UpperQuartile,
Max
};
enum ActiveTab
{
Summary,
Compare,
};
enum ActiveView
{
Single,
Left,
Right
}
enum ThreadActivity
{
None,
Analyze,
AnalyzeDone,
Compare,
CompareDone,
Load,
LoadDone
};
enum TopTenDisplay
{
Normalized,
LongestTime,
};
enum NameFilterOperation
{
All, // AND
Any, // OR
};
enum RemoveMarkerOperation
{
ShowAll,
HideWaitForFPS,
HideWaitForPresent,
Custom,
};
/// <summary>
/// Main profile Analyzer UI window
/// </summary>
public class ProfileAnalyzerWindow : EditorWindow
{
internal static class Styles
{
public static readonly GUIContent emptyString = new GUIContent("", "");
public static readonly GUIContent dash = new GUIContent("-", "");
public static readonly GUIContent thread = new GUIContent("Thread", "");
public static readonly GUIContent noThread = new GUIContent("", "Thread not present on this data set");
public static readonly GUIContent max = new GUIContent("Max", "The peak value in the data set");
public static readonly GUIContent upperQuartile = new GUIContent("Upper Quartile", "The middle value between the median and the highest value of the data set. I.e. at 75% of the ordered data.");
public static readonly GUIContent mean = new GUIContent("Mean", "The average value in the data set");
public static readonly GUIContent median = new GUIContent("Median", "The central value in the data set");
public static readonly GUIContent lowerQuartile = new GUIContent("Lower Quartile", "The middle number between the smallest number and the median of the data set. I.e. at 25% of the ordered data.");
public static readonly GUIContent min = new GUIContent("Min", "The minimum value in the data set");
public static readonly GUIContent individualMin = new GUIContent("Individual Min", "The minimum value in the data set for an individual marker instance (not the total in the frame)");
public static readonly GUIContent individualMax = new GUIContent("Individual Max", "The maximum value in the data set for an individual marker instance (not the total in the frame)");
public static readonly GUIContent export = new GUIContent("Export", "Export profiler data as CSV files");
public static readonly GUIContent pullOpen = new GUIContent("Pull Data", "Pull data from Unity profiler.\nFirst you must open Unity profiler to pull data from it");
public static readonly GUIContent pullRange = new GUIContent("Pull Data", "Pull data from Unity profiler.\nFirst you must use the Unity profiler to capture data from application");
public static readonly GUIContent pullRecording = new GUIContent("Pull Data", "Pull data from Unity profiler.\nStop Unity profiler recording to enable pulling data");
public static readonly GUIContent pull = new GUIContent("Pull Data", "Pull data from Unity profiler");
public static readonly GUIContent nameFilter = new GUIContent("Name Filter : ", "Only show markers containing the strings\n\n(Effects the marker table below)");
public static readonly GUIContent nameExclude = new GUIContent("Exclude Names : ", "Excludes markers containing the strings\n\n(Effects the marker table below)");
public static readonly GUIContent threadFilter = new GUIContent("Thread : ", "Select threads to focus on\n\n(Effects the marker table below)");
public static readonly GUIContent threadFilterSelect = new GUIContent("Select", "Select threads to focus on\n\n(Effects the marker table below)");
public static readonly GUIContent unitFilter = new GUIContent("Units : ", "Units to show in UI");
public static readonly GUIContent timingFilter = new GUIContent("Analysis Type : ", TimingOptions.Tooltip);
public static readonly GUIContent markerColumns = new GUIContent("Marker Columns : ", "Set of Columns to show in the table");
public static readonly GUIContent graphPairing = new GUIContent("Pair Graph Selection", "Selections on one graph will affect the other");
public static readonly GUIContent removeMarker = new GUIContent("Remove : ", "Remove a specific marker from time analysis\n\n(Effects all views)");
public static readonly GUIContent hideRemoveMarkers = new GUIContent("Hide Removed Markers", "Hide removed markers from the marker table");
public static readonly GUIContent frameSummary = new GUIContent("Frame Summary", "");
public static readonly GUIContent frameCount = new GUIContent("Frame Count", "Frame Count");
public static readonly GUIContent frameStart = new GUIContent("Start", "Frame Start");
public static readonly GUIContent frameEnd = new GUIContent("End", "Frame End");
public static readonly GUIContent threadSummary = new GUIContent("Thread Summary", "");
public static readonly GUIContent threadGraphScale = new GUIContent("Graph Scale : ", "");
public static readonly GUIContent[] threadRanges =
{
new GUIContent("Median", "Median frame time"),
new GUIContent("Upper quartile", "Upper quartile of frame time"),
new GUIContent("Max", "Max frame time")
};
public static readonly GUIContent markerSummary = new GUIContent("Marker Summary", "");
public static readonly GUIContent filters = new GUIContent("Filters", "");
public static readonly GUIContent profileTable = new GUIContent("Marker Details for currently selected range", "");
public static readonly GUIContent comparisonTable = new GUIContent("Marker Comparison for currently selected range", "");
public static readonly GUIContent depthTitle = new GUIContent("Depth Slice : ", "Marker callstack depth to analyze");
public static readonly GUIContent leftDepthTitle = new GUIContent("Left : ", "Marker callstack depth to analyze");
public static readonly GUIContent rightDepthTitle = new GUIContent("Right : ", "Marker callstack depth to analyze");
public static readonly string autoDepthTitleText = "Auto Depth (Diff: {0:+##;-##;None})";
public static readonly GUIContent autoDepthTitle = new GUIContent("Auto Depth", "Match up the depth levels based on the most common difference between markers present in both data sets. If the selected depth is at a depth not present in the other data set, after applying this difference, it will use the deepest level.");
public static readonly GUIContent parentMarker = new GUIContent("Parent Marker : ", "Marker to start analysis from.\nParent of the hierarchy to analyze.");
public static readonly GUIContent selectParentMarker = new GUIContent("None", "Select using right click context menu on marker names in marker table");
public static readonly GUIContent topMarkerRatio = new GUIContent("Ratio : ", "Normalize\tNormalized to time of the individual set\nLongest\tRatio based on longest time of the two");
public static readonly GUIContent firstFrame = new GUIContent("First frame", "");
public static readonly GUIContent[] topTenDisplayOptions =
{
new GUIContent("Normalized", "Ratio normalized to time of the individual data set"),
new GUIContent("Longest", "Ratio based on longest time of the two data sets")
};
public static readonly GUIContent[] nameFilterOperation =
{
new GUIContent("All", "Marker name contains all strings"),
new GUIContent("Any", "Marker name contains any of the strings")
};
public static readonly GUIContent[] removeMarkerOperation =
{
new GUIContent("None", "All markers shown. None removed."),
new GUIContent("FPS Wait", "Remove the WaitForTargetFPS marker which represents targeted FPS (*) time\n\n(*) Via Application.targetFrameRate API usage (common on Mobile)"),
new GUIContent("Present Wait", "Remove the Gfx.WaitForPresentOnGfxThread marker which represents time waiting for GPU to complete (common on consoles)"),
new GUIContent("Custom", "Right click on a marker in the table and select 'Remove Marker' to remove a specific marker (and all children too)")
};
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 menuItemSelectFramesAll = new GUIContent("Clear Selection", "");
public static readonly GUIContent frameCosts = new GUIContent(" by frame costs", "Contains accumulated marker cost within the frame");
public static readonly GUIContent dataMissing = new GUIContent("Pull or load a data set for analysis", "Pull data from Unity Profiler or load a previously saved analysis data set");
public static readonly GUIContent comparisonDataMissing = new GUIContent("Pull or load a data set for comparison", "Pull data from Unity Profiler or load previously saved analysis data sets");
public static readonly string topMarkersTooltip = "Top markers for the median frame.\nThe length of this frame is the median of those in the data set.\nIt is likely to be the most representative frame.";
public static readonly string medianFrameTooltip = "The length of this frame is the median of those in the data set.\nIt is likely to be the most representative frame.";
public static readonly string helpText =
@"This tool can analyze Unity Profiler data, to find representative frames and perform comparisons of data sets.
To gather data to analyze:
* Open the Unity Profiler. Either via the Unity menu under 'Windows', 'Analysis' or via the 'Open Profile Window' in the tool bar.
* Capture some profiling data in the Unity Profiler by selecting a target application and click the 'Record' button.
* Stop the capture by clicking again on the 'Record' button.
To analyze the data:
* Pull the Unity Profiler data into this tool by clicking the 'Pull Data' button in the single or compare views.
* The analysis will be automatically triggered (in the compare view two data sets are required before analysis is performed).
* Select a marker to see more detailed information about its time utilization over the frame time range.
* Save off a data file from here to keep for future use. (Recommend saving the profile .data file in the same folder).
To compare two data sets:
* Click the compare tab. The data in the single tab will be used by default. You can also load previously saved analysis data.
* Drag select a region in the frame time graph (above) to choose 1 or more frames for each of the two data sets.
* The comparison will be automatically triggered as the selection is made.";
}
const float k_ProgressBarHeight = 2f;
ProgressBarDisplay m_ProgressBar;
ProfileAnalyzer m_ProfileAnalyzer;
ProfilerWindowInterface m_ProfilerWindowInterface;
string m_LastProfilerSelectedMarker;
string m_LastMarkerSuccesfullySyncedWithProfilerWindow = null;
[NonSerialized] bool m_SelectionEventFromProfilerWindowInProgress = false;
int m_TopNumber;
string[] m_TopStrings;
int[] m_TopValues;
[SerializeField] DepthSliceUI m_DepthSliceUI;
[SerializeField]
TimingOptions.TimingOption m_TimingOption = TimingOptions.TimingOption.Time;
[SerializeField]
string m_ParentMarker = null;
List<string> m_ThreadUINames = new List<string>();
List<string> m_ThreadNames = new List<string>();
Dictionary<string, string> m_ThreadNameToUIName;
GUIContent[] m_removeMarkerDisplay = null;
int[] m_removeMarkerValues = null;
bool m_removeMarkerSomeMissing = false;
struct MarkerFilter
{
public Dictionary<string, bool> MarkerCache;
public bool NeedsRebuild;
public List<string> IncludeFilter;
public List<string> ExcludeFilter;
public NameFilterOperation IncludeOperation;
public NameFilterOperation ExcludeOperation;
public void Clear()
{
MarkerCache.Clear();
NeedsRebuild = true;
}
private void Rebuild(ProfileAnalyzerWindow window)
{
NeedsRebuild = false;
IncludeFilter = window.GetNameFilters();
ExcludeFilter = window.GetNameExcludes();
IncludeOperation = window.m_NameFilterOperation;
ExcludeOperation = window.m_NameExcludeOperation;
}
private bool ComputeFilter(string marker)
{
if (IncludeFilter.Count > 0)
{
if (!NameInFilterList(marker, IncludeFilter, IncludeOperation))
return false;
}
if (ExcludeFilter.Count > 0)
{
if (NameInFilterList(marker, ExcludeFilter, ExcludeOperation))
return false;
}
return true;
}
public bool DoesMarkerPassFilter(ProfileAnalyzerWindow window, string marker)
{
if (NeedsRebuild)
Rebuild(window);
if (MarkerCache.TryGetValue(marker, out var value))
return value;
bool passesFilter = ComputeFilter(marker);
MarkerCache[marker] = passesFilter;
return passesFilter;
}
}
MarkerFilter m_MarkerFilter;
[SerializeField]
ThreadSelection m_ThreadSelection = new ThreadSelection();
ThreadSelection m_ThreadSelectionNew;
string m_ThreadSelectionSummary;
[SerializeField]
DisplayUnits m_DisplayUnits = new DisplayUnits(Units.Milliseconds);
string[] m_UnitNames;
[SerializeField]
string m_NameFilter = "";
[SerializeField]
string m_NameExclude = "";
[SerializeField]
MarkerColumnFilter m_SingleModeFilter = new MarkerColumnFilter(MarkerColumnFilter.Mode.TimeAndCount);
[SerializeField]
MarkerColumnFilter m_CompareModeFilter = new MarkerColumnFilter(MarkerColumnFilter.Mode.TimeAndCount);
[SerializeField]
TopTenDisplay m_TopTenDisplay = TopTenDisplay.Normalized;
[SerializeField]
NameFilterOperation m_NameFilterOperation = NameFilterOperation.All;
[SerializeField]
NameFilterOperation m_NameExcludeOperation = NameFilterOperation.Any;
[SerializeField]
RemoveMarkerOperation m_removeMarkerOperation = RemoveMarkerOperation.ShowAll;
[SerializeField]
bool m_hideRemovedMarkers = true;
int m_ProfilerFirstFrameIndex = 0;
int m_ProfilerLastFrameIndex = 0;
const int k_ProfileDataDefaultDisplayOffset = 1;
ActiveTab m_NextActiveTab = ActiveTab.Summary;
ActiveTab m_ActiveTab = ActiveTab.Summary;
bool m_OtherTabDirty = false;
bool m_OtherTableDirty = false;
[SerializeField]
string m_removeMarkerCustomRemoveMarker = null;
[SerializeField]
bool m_ShowFilters = true;
[SerializeField]
bool m_ShowTopNMarkers = true;
[SerializeField]
bool m_ShowFrameSummary = true;
[SerializeField]
bool m_ShowThreadSummary = false;
[SerializeField]
bool m_ShowMarkerSummary = true;
[SerializeField]
bool m_ShowMarkerTable = true;
internal static class UIColor
{
static internal Color Color256(int r, int g, int b, int a)
{
return new Color((float)r / 255.0f, (float)g / 255.0f, (float)b / 255.0f, (float)a / 255.0f);
}
public static readonly Color white = new UnityEngine.Color(1.0f, 1.0f, 1.0f);
public static readonly Color barBackground = new Color(0.5f, 0.5f, 0.5f);
public static readonly Color barBackgroundSelected = new Color(0.6f, 0.6f, 0.6f);
public static readonly Color boxAndWhiskerBoxColor = Color256(112, 112, 112, 255);
public static readonly Color boxAndWhiskerLineColorLeft = Color256(206, 219, 238, 255);
public static readonly Color boxAndWhiskerBoxColorLeft = Color256(59, 104, 144, 255);
public static readonly Color boxAndWhiskerLineColorRight = Color256(247, 212, 201, 255);
public static readonly Color boxAndWhiskerBoxColorRight = Color256(161, 83, 30, 255);
public static readonly Color bar = new Color(0.95f, 0.95f, 0.95f);
public static readonly Color barSelected = new Color(0.5f, 1.0f, 0.5f);
public static readonly Color standardLine = new Color(1.0f, 1.0f, 1.0f);
public static readonly Color gridLines = new Color(0.4f, 0.4f, 0.4f);
public static readonly Color left = Color256(111, 163, 216, 255);
public static readonly Color leftSelected = Color256(06, 219, 238, 255);
public static readonly Color right = Color256(238, 134, 84, 255);
public static readonly Color rightSelected = Color256(247, 212, 201, 255);
public static readonly Color both = Color256(175, 150, 150, 255);
public static readonly Color textTopMarkers = Color256(0, 0, 0, 255);
public static readonly Color marker = new Color(0.0f, 0.5f, 0.5f);
public static readonly Color markerSelected = new Color(0.0f, 0.6f, 0.6f);
public static readonly Color thread = new Color(0.5f, 0.0f, 0.5f);
public static readonly Color threadSelected = new Color(0.6f, 0.0f, 0.6f);
}
[SerializeField]
ProfileDataView m_ProfileSingleView;
[SerializeField]
ProfileDataView m_ProfileLeftView;
[SerializeField]
ProfileDataView m_ProfileRightView;
[SerializeField] ThreadMarkerInfo m_SelectedMarker = new ThreadMarkerInfo();
[Serializable]
struct ThreadMarkerInfo
{
[SerializeField]
public int id;
[SerializeField]
public string threadName;
[SerializeField]
public string threadGroupName;
[SerializeField]
public string name;
}
FrameTimeGraphGlobalSettings m_FrameTimeGraphGlobalSettings;
FrameTimeGraph m_FrameTimeGraph;
FrameTimeGraph m_LeftFrameTimeGraph;
FrameTimeGraph m_RightFrameTimeGraph;
bool m_FrameTimeGraphsPaired = true;
TopMarkers m_TopMarkers;
TopMarkers m_TopMarkersLeft;
TopMarkers m_TopMarkersRight;
List<MarkerPairing> m_PairingsNew;
int m_TotalCombinedMarkerCountNew;
[SerializeField]
List<MarkerPairing> m_Pairings = new List<MarkerPairing>();
int m_TotalCombinedMarkerCount = 0;
[SerializeField]
int m_SelectedPairing = 0;
[SerializeField]
TreeViewState m_ProfileTreeViewState;
[SerializeField]
MultiColumnHeaderState m_ProfileMulticolumnHeaderState;
ProfileTable m_ProfileTable;
[SerializeField]
TreeViewState m_ComparisonTreeViewState;
[SerializeField]
MultiColumnHeaderState m_ComparisonMulticolumnHeaderState;
ComparisonTable m_ComparisonTable;
internal static class LayoutSize
{
public static readonly int WidthColumn0 = 100;
public static readonly int WidthColumn1 = 52; // +2 to prevent some
public static readonly int WidthColumn2 = 52;
public static readonly int WidthColumn3 = 52;
public static readonly int WidthRHS = 290; // Column widths + label padding between (276) + scrollbar width
public static readonly int FilterOptionsLeftLabelWidth = 100;
public static readonly int FilterOptionsEnumWidth = 50;
public static readonly int RemoveMarkerOptionsEnumWidth = 100;
public static readonly int RemoveMarkerMissingOptionsEnumWidth = 200;
public static readonly int FilterOptionsLockedEnumWidth = 120;
public static readonly int FilterOptionsRightLabelWidth = 110;
public static readonly int FilterOptionsRightEnumWidth = 150;
public static readonly int HistogramWidth = 153;
public static readonly int MinWindowWidth = 800 + WidthRHS;
public static readonly int MinWindowHeight = 480;
public static readonly int WindowWidth = MinWindowWidth;
public static readonly int WindowHeight = 840;
public static readonly int ScrollBarPadding = 6; // this is legacy and we might be able to kill it but it will slightly change the layout of the window.
}
Columns m_Columns = new Columns(LayoutSize.WidthColumn0, LayoutSize.WidthColumn1, LayoutSize.WidthColumn2, LayoutSize.WidthColumn3);
[SerializeField]
ThreadRange m_ThreadRange = ThreadRange.UpperQuartile;
internal Draw2D m_2D;
bool m_Async = true;
Thread m_BackgroundThread;
ThreadActivity m_ThreadActivity;
ProfileData m_ProfilerData;
string m_Path;
int m_ThreadPhase;
int m_ThreadPhases;
int m_ThreadProgress;
bool m_RequestRepaint;
bool m_RequestAnalysis;
bool m_RequestCompare;
bool m_FullAnalysisRequired;
bool m_FullCompareRequired;
[SerializeField]
int m_TopNBars = 10;
bool m_EnableAnalysisProfiling = false;
int m_AnalyzeInUpdatePhase = 0;
string m_LastAnalysisTime = "";
string m_LastCompareTime = "";
float m_LastAnalysisTimeMilliseconds;
float m_LastCompareTimeMilliseconds;
bool m_NewDataLoaded = false;
bool m_NewComparisonDataLoaded = false;
Vector2 m_HelpScroll = new Vector2(0, 0);
Vector2 m_ThreadScroll = new Vector2(0, 0);
Vector2 m_MarkerSummaryScroll = new Vector2(0, 0);
Rect m_ThreadsAreaRect = new Rect();
Rect m_ComparisonThreadsAreaRect = new Rect();
Vector2 m_LastScreenSize = new Vector2(0, 0);
bool m_ScreenSizeChanged;
double m_ScreenSizeChangedTimeStarted;
double m_ScreenSizeChangedTimeFinished;
ActiveTab m_ScreenSizeChangedTab;
GUIStyle m_StyleMiddleRight;
GUIStyle m_StyleUpperLeft;
bool m_StylesSetup = false;
static Regex quotedStringWithoutQuotes = new Regex("\"([^\"]*)\"");
static Regex quotedString = new Regex("(\"[^\"]*\")");
static Regex stringWithoutWhiteSpace = new Regex("([^ \t]+)");
/*
static Regex lastSpace = new Regex("(.+)[ ]([^ ]*)");
*/
[MenuItem("Window/Analysis/Profile Analyzer")]
static void Init()
{
var window = GetWindow<ProfileAnalyzerWindow>("Profile Analyzer");
window.minSize = new Vector2(LayoutSize.MinWindowWidth, LayoutSize.MinWindowHeight);
window.position.size.Set(LayoutSize.WindowWidth, LayoutSize.WindowHeight);
window.Show();
window.m_LastScreenSize = window.position.size;
}
/// <summary>
/// Open profile analyzer window
/// </summary>
public static void OpenProfileAnalyzer()
{
Init();
}
void Awake()
{
m_ScreenSizeChanged = false;
m_ScreenSizeChangedTimeStarted = 0.0;
m_ScreenSizeChangedTimeFinished = 0.0;
m_ScreenSizeChangedTab = ActiveTab.Summary;
m_ProfileSingleView = new ProfileDataView();
m_ProfileLeftView = new ProfileDataView();
m_ProfileRightView = new ProfileDataView();
m_RequestRepaint = false;
m_RequestAnalysis = false;
m_RequestCompare = false;
m_FrameTimeGraphGlobalSettings = new FrameTimeGraphGlobalSettings();
}
static int s_TmpCount = 0;
static string s_TmpDir = "";
ActiveView m_ActiveLoadingView;
static string s_ApplicationDataPath;
internal static string TmpDir
{
get
{
if (string.IsNullOrEmpty(s_ApplicationDataPath))
s_ApplicationDataPath = Application.dataPath;
if (string.IsNullOrEmpty(s_TmpDir))
s_TmpDir = string.Format("{0}{1}ProfileAnalyzer{1}", Directory.GetParent(s_ApplicationDataPath).FullName, Path.DirectorySeparatorChar);
return s_TmpDir;
}
}
internal static string TmpPath
{
get
{
if (!Directory.Exists(TmpDir))
Directory.CreateDirectory(TmpDir);
while (File.Exists(string.Format("{0}tmp{1}.pdata", TmpDir, s_TmpCount)))
{
s_TmpCount++;
}
return string.Format("{0}tmp{1}.pdata", TmpDir, s_TmpCount);
}
}
void OnEnable()
{
//do this here to safeguard against Application.dataPAth being accessed off the main thread
s_ApplicationDataPath = Application.dataPath;
// Update styles so we get the theme changes
m_StylesSetup = false;
ProfileAnalyzerAnalytics.EnableAnalytics();
m_ProgressBar = new ProgressBarDisplay();
if (m_DepthSliceUI == null)
m_DepthSliceUI = new DepthSliceUI(b => UpdateActiveTab(b));
else
m_DepthSliceUI.OnEnable(b => UpdateActiveTab(b));
m_ProfilerWindowInterface = new ProfilerWindowInterface(m_ProgressBar);
if (!m_ProfilerWindowInterface.IsReady())
{
m_ProfilerWindowInterface.GetProfilerWindowHandle();
}
if (IsSelectedMarkerNameValid())
{
var oldSelectedMarkerName = m_SelectedMarker;
m_SelectedMarker.name = null;
// wait a frame for the ProfilerWindow to get Enabled before re-setting the selection
EditorApplication.delayCall += () => SelectMarkerByName(oldSelectedMarkerName.name, oldSelectedMarkerName.threadGroupName, oldSelectedMarkerName.threadName);
}
m_ProfilerWindowInterface.selectedMarkerChanged -= OnProfilerWindowCpuModuleSelectionChanged;
m_ProfilerWindowInterface.selectedMarkerChanged += OnProfilerWindowCpuModuleSelectionChanged;
m_ProfilerWindowInterface.selectedFrameChanged -= OnProfilerWindowSelectedFrameChanged;
m_ProfilerWindowInterface.selectedFrameChanged += OnProfilerWindowSelectedFrameChanged;
m_MarkerFilter = new MarkerFilter
{
MarkerCache = new Dictionary<string, bool>(),
NeedsRebuild = true,
};
m_ProfileAnalyzer = new ProfileAnalyzer();
if (m_ThreadSelection == null || m_ThreadSelection.empty)
{
ThreadIdentifier mainThreadSelection = new ThreadIdentifier("Main Thread", 1);
m_ThreadSelection.Set(mainThreadSelection.threadNameWithIndex);
}
if (m_ThreadSelectionNew != null && m_ThreadSelectionNew.empty)
m_ThreadSelectionNew = null;
m_2D = new Draw2D("Unlit/ProfileAnalyzerShader");
FrameTimeGraph.SetGlobalSettings(m_FrameTimeGraphGlobalSettings);
m_FrameTimeGraph = new FrameTimeGraph(0, m_2D, m_DisplayUnits.Units, UIColor.barBackground, UIColor.barBackgroundSelected, UIColor.bar, UIColor.barSelected, UIColor.marker, UIColor.markerSelected, UIColor.thread, UIColor.threadSelected, UIColor.gridLines);
m_FrameTimeGraph.SetRangeCallback(SetRange);
m_FrameTimeGraph.SetActiveCallback(GraphActive);
m_LeftFrameTimeGraph = new FrameTimeGraph(1, m_2D, m_DisplayUnits.Units, UIColor.barBackground, UIColor.barBackgroundSelected, UIColor.left, UIColor.leftSelected, UIColor.marker, UIColor.markerSelected, UIColor.thread, UIColor.threadSelected, UIColor.gridLines);
m_LeftFrameTimeGraph.SetRangeCallback(SetLeftRange);
m_LeftFrameTimeGraph.SetActiveCallback(GraphActive);
m_RightFrameTimeGraph = new FrameTimeGraph(2, m_2D, m_DisplayUnits.Units, UIColor.barBackground, UIColor.barBackgroundSelected, UIColor.right, UIColor.rightSelected, UIColor.marker, UIColor.markerSelected, UIColor.thread, UIColor.threadSelected, UIColor.gridLines);
m_RightFrameTimeGraph.SetRangeCallback(SetRightRange);
m_RightFrameTimeGraph.SetActiveCallback(GraphActive);
m_LeftFrameTimeGraph.PairWith(m_FrameTimeGraphsPaired ? m_RightFrameTimeGraph : null);
m_TopMarkers = new TopMarkers(this, m_2D, UIColor.barBackground, UIColor.textTopMarkers);
m_TopMarkersLeft = new TopMarkers(this, m_2D, UIColor.barBackground, UIColor.textTopMarkers);
m_TopMarkersRight = new TopMarkers(this, m_2D, UIColor.barBackground, UIColor.textTopMarkers);
m_ThreadActivity = ThreadActivity.None;
m_ThreadProgress = 0;
m_ThreadPhase = 0;
List<int> values = new List<int>();
List<String> strings = new List<string>();
for (int i = 1; i <= 10; i++)
{
values.Add(i);
strings.Add(i.ToString());
}
m_TopValues = values.ToArray();
m_TopStrings = strings.ToArray();
m_TopNumber = 3;
List<string> unitNames = new List<string>(DisplayUnits.UnitNames);
unitNames.RemoveAt(unitNames.Count - 1);
m_UnitNames = unitNames.ToArray();
// Regrenerate analysis if just re initialised with the existing profile data reloaded from serialisation (e.g. on enter play mode)
// As we don't serialise the analysis itself.
// UpdateActiveTab(true);
UpdateThreadNames();
if (m_ProfileSingleView.analysis != null)
{
CreateProfileTable();
m_RequestRepaint = true;
}
if (m_ProfileLeftView.analysis != null && m_ProfileRightView.analysis != null)
{
CreateComparisonTable();
m_RequestRepaint = true;
}
// Mouse movement calls OnGui
wantsMouseMove = true;
}
void OnDisable()
{
if (ProfileAnalyzerExportWindow.IsOpen())
ProfileAnalyzerExportWindow.CloseAll();
m_ProfilerWindowInterface.selectedMarkerChanged -= OnProfilerWindowCpuModuleSelectionChanged;
m_ProfilerWindowInterface.selectedFrameChanged -= OnProfilerWindowSelectedFrameChanged;
m_ProfilerWindowInterface.OnDisable();
m_ProfilerWindowInterface = null;
}
void OnDestroy()
{
if (m_BackgroundThread != null)
m_BackgroundThread.Abort();
if (m_ProfileSingleView != null && m_ProfileSingleView.data != null)
m_ProfileSingleView.data.DeleteTmpFiles();
if (m_ProfileLeftView != null && m_ProfileLeftView.data != null)
m_ProfileLeftView.data.DeleteTmpFiles();
if (m_ProfileRightView != null && m_ProfileRightView.data != null)
m_ProfileRightView.data.DeleteTmpFiles();
if (Directory.Exists(TmpDir) && Directory.GetFiles(TmpDir).Length == 0)
Directory.Delete(TmpDir, true);
}
bool DisplayCount()
{
switch (m_SingleModeFilter.mode)
{
case MarkerColumnFilter.Mode.CountTotals:
case MarkerColumnFilter.Mode.CountPerFrame:
return true;
default:
return false;
}
}
void OnGUI()
{
if (Event.current.type != EventType.MouseMove)
{
m_2D.OnGUI();
Draw();
}
ProcessInput();
}
bool TmpInUse(ProfileDataView dv, string path)
{
if (dv != m_ProfileSingleView && m_ProfileSingleView.data != null && m_ProfileSingleView.data.FilePath == path)
return true;
if (dv != m_ProfileLeftView && m_ProfileLeftView.data != null && m_ProfileLeftView.data.FilePath == path)
return true;
if (dv != m_ProfileRightView && m_ProfileRightView.data != null && m_ProfileRightView.data.FilePath == path)
return true;
return false;
}
void SetView(ProfileDataView dst, ProfileData data, string path, FrameTimeGraph graph)
{
if (!data.IsSame(dst.data))
{
if (dst == m_ProfileSingleView)
m_NewDataLoaded = true;
else
m_NewComparisonDataLoaded = true;
}
if (dst.data != null && (m_NewDataLoaded || m_NewComparisonDataLoaded) && !TmpInUse(dst, dst.data.FilePath))
dst.data.DeleteTmpFiles();
dst.data = data;
dst.path = path;
dst.SelectFullRange();
graph.Reset();
graph.SetData(GetFrameTimeData(dst.data));
// One of the views changed so make sure the export window knows if its open
ProfileAnalyzerExportWindow exportWindow = ProfileAnalyzerExportWindow.FindOpenWindow();
if (exportWindow != null)
{
exportWindow.SetData(m_ProfileSingleView, m_ProfileLeftView, m_ProfileRightView);
}
}
void SetView(ProfileDataView dst, ProfileDataView src, FrameTimeGraph graph)
{
SetView(dst, src.data, src.path, graph);
}
void UpdateThreadNames()
{
// Update threads list
switch (m_ActiveTab)
{
case ActiveTab.Summary:
GetThreadNames(m_ProfileSingleView.data, out m_ThreadUINames, out m_ThreadNames, out m_ThreadNameToUIName);
break;
case ActiveTab.Compare:
GetThreadNames(m_ProfileLeftView.data, m_ProfileRightView.data, out m_ThreadUINames, out m_ThreadNames, out m_ThreadNameToUIName);
break;
}
UpdateThreadGroupSelection(m_ThreadNames, m_ThreadSelection);
m_ThreadSelectionSummary = CalculateSelectedThreadsSummary();
}
void ProcessTabSwitch()
{
if (m_NextActiveTab != m_ActiveTab)
{
m_ActiveTab = m_NextActiveTab;
// Copy data if none present for this tab
switch (m_ActiveTab)
{
case ActiveTab.Summary:
if (!m_ProfileSingleView.IsDataValid())
{
if (m_ProfileLeftView.IsDataValid())
{
SetView(m_ProfileSingleView, m_ProfileLeftView, m_FrameTimeGraph);
m_RequestAnalysis = true;
m_FullAnalysisRequired = true;
}
else if (m_ProfileRightView.IsDataValid())
{
SetView(m_ProfileSingleView, m_ProfileRightView, m_FrameTimeGraph);
m_RequestAnalysis = true;
m_FullAnalysisRequired = true;
}
}
break;
case ActiveTab.Compare:
if ((!m_ProfileLeftView.IsDataValid() || !m_ProfileRightView.IsDataValid()) && m_ProfileSingleView.IsDataValid())
{
if (!m_ProfileLeftView.IsDataValid())
{
SetView(m_ProfileLeftView, m_ProfileSingleView, m_LeftFrameTimeGraph);
}
if (!m_ProfileRightView.IsDataValid())
{
SetView(m_ProfileRightView, m_ProfileSingleView, m_RightFrameTimeGraph);
}
// Remove pairing of both left/right point at the same data
if (m_ProfileLeftView.path == m_ProfileRightView.path)
{
SetFrameTimeGraphPairing(false);
}
m_RequestCompare = true;
m_FullCompareRequired = true;
}
break;
}
UpdateThreadNames();
BuildRemoveMarkerList();
if (!m_OtherTableDirty)
SelectMarker(m_SelectedMarker.name);
if (m_OtherTabDirty)
{
UpdateActiveTab(true, false); // Make sure any depth/thread updates are applied when switching tabs, but don't dirty the other tab
m_OtherTabDirty = false;
}
if (m_OtherTableDirty)
{
UpdateMarkerTable(false); // Make sure any marker selection updates are applied when switching tabs, but don't dirty the other tab
m_OtherTableDirty = false;
}
if (!m_RequestAnalysis && !m_RequestCompare)
m_DepthSliceUI.UpdateDepthFilters(m_ActiveTab == ActiveTab.Summary, m_ProfileSingleView, m_ProfileLeftView, m_ProfileRightView);
}
}
bool IsDocked()
{
return docked;
}
void CheckScreenSizeChanges()
{
// We get a 5 pixel change in y height during initialization.
// We could wait before considering size changes but using a delta is also useful
float sizeDeltaForChange = 10;
Vector2 sizeDiff = position.size - m_LastScreenSize;
if (Math.Abs(sizeDiff.x) > sizeDeltaForChange || Math.Abs(sizeDiff.y) > sizeDeltaForChange)
{
if (m_LastScreenSize.x != 0) // At initialization time the screen size has not yet been recorded. Don't consider this a screen size change
{
m_LastScreenSize = position.size;
if (!m_ScreenSizeChanged)
{
// Record when we started the change
m_ScreenSizeChanged = true;
m_ScreenSizeChangedTimeStarted = EditorApplication.timeSinceStartup;
}
// Record the last time of a change
m_ScreenSizeChangedTimeFinished = EditorApplication.timeSinceStartup;
// Record which tab we were on when it was changed
m_ScreenSizeChangedTab = m_ActiveTab;
}
}
if (m_ScreenSizeChanged)
{
double secondsSinceChanged = (EditorApplication.timeSinceStartup - m_ScreenSizeChangedTimeFinished);
double secondsToDelay = 3f;
if (secondsSinceChanged > secondsToDelay)
{
// Send analytic
var uiResizeView = m_ScreenSizeChangedTab == ActiveTab.Summary ? ProfileAnalyzerAnalytics.UIResizeView.Single : ProfileAnalyzerAnalytics.UIResizeView.Comparison;
float durationInSeconds = (float)(m_ScreenSizeChangedTimeFinished - m_ScreenSizeChangedTimeStarted);
ProfileAnalyzerAnalytics.SendUIResizeEvent(uiResizeView, durationInSeconds, position.size.x, position.size.y, IsDocked());
m_ScreenSizeChanged = false;
}
}
}
internal void RequestRepaint()
{
m_RequestRepaint = true;
}
void ProcessInput()
{
FrameTimeGraph.State inputStatus = FrameTimeGraph.State.None;
if (m_ActiveTab == ActiveTab.Summary)
{
inputStatus = m_FrameTimeGraph.ProcessInput();
}
else if (m_ActiveTab == ActiveTab.Compare)
{
if (m_ProfileLeftView.IsDataValid() && inputStatus == FrameTimeGraph.State.None)
inputStatus = m_LeftFrameTimeGraph.ProcessInput();
if (m_ProfileRightView.IsDataValid() && inputStatus == FrameTimeGraph.State.None)
inputStatus = m_RightFrameTimeGraph.ProcessInput();
}
switch (inputStatus)
{
case FrameTimeGraph.State.Dragging:
m_RequestRepaint = true;
break;
case FrameTimeGraph.State.DragComplete:
m_RequestCompare = true;
break;
}
if (Event.current.isKey && Event.current.type == EventType.KeyDown)
{
switch (Event.current.keyCode)
{
case KeyCode.Alpha1:
if (m_ActiveTab == ActiveTab.Summary)
{
m_FrameTimeGraph.MakeGraphActive(true);
GUI.FocusControl("FrameTimeGraph");
}
else if (m_ActiveTab == ActiveTab.Compare)
{
m_LeftFrameTimeGraph.MakeGraphActive(true);
GUI.FocusControl("LeftFrameTimeGraph");
}
m_RequestRepaint = true;
break;
case KeyCode.Alpha2:
if (m_ActiveTab == ActiveTab.Compare)
{
m_RightFrameTimeGraph.MakeGraphActive(true);
GUI.FocusControl("RightFrameTimeGraph");
}
m_RequestRepaint = true;
break;
}
}
}
//Check if the ProfileDataView is in sync with the loaded frame data inside the profiler window
//We are required to do this check in order to either enable or disable the ability to
//jump into the matching frame data(loaded in the profiler window) for a specific profile analyzer capture
void VerifyFrameDataInSyncWithProfilerWindow(ProfileDataView dataView)
{
var firstFrameIdx = m_ProfilerFirstFrameIndex - 1;
var incompleteFrameCount = 0;
if (dataView != null && dataView.data != null)
incompleteFrameCount = (dataView.data.FirstFrameIncomplete ? 1 : 0) + (dataView.data.LastFrameIncomplete ? 1 : 0);
var loadedFrameCount = m_ProfilerLastFrameIndex - m_ProfilerFirstFrameIndex + (firstFrameIdx != -1 ? 1 : 0)
- incompleteFrameCount;
if (loadedFrameCount == 0
|| !dataView.IsDataValid() //check if the data is valid and potentially reload the file, .data shouldn't be accessed before this point
|| dataView.data.GetFrameCount() != loadedFrameCount
|| m_ProfilerWindowInterface.GetThreadCountForFrame(firstFrameIdx) != dataView.data.GetFrame(0).threads.Count)
{
dataView.inSyncWithProfilerData = false;
}
else
{
var pDataFrame = dataView.data.GetFrame(0); //get the first frame we don't care about the offset as we only need to compare frames
var loadedFrame = m_ProfilerWindowInterface.GetProfileFrameForThread(firstFrameIdx, 0);
//compare frame start time and duration
//todo improve this
if (pDataFrame.msStartTime != loadedFrame.msStartTime
|| pDataFrame.msFrame != loadedFrame.msFrame)
{
dataView.inSyncWithProfilerData = false;
}
else
{
dataView.inSyncWithProfilerData = true;
}
}
}
//Returns true if we were able to sync with the window, but not necessarily if the data is in sync
bool SyncWithProfilerWindow()
{
if (m_ProfilerWindowInterface.IsReady())
{
// Check if a new profile has been recorded (or loaded) by checking the frame index range.
int first;
int last;
m_ProfilerWindowInterface.GetFrameRangeFromProfiler(out first, out last);
if (first != m_ProfilerFirstFrameIndex || last != m_ProfilerLastFrameIndex)
{
// Store the updated range and alter the pull range
m_ProfilerFirstFrameIndex = first;
m_ProfilerLastFrameIndex = last;
}
VerifyFrameDataInSyncWithProfilerWindow(m_ProfileSingleView);
VerifyFrameDataInSyncWithProfilerWindow(m_ProfileLeftView);
VerifyFrameDataInSyncWithProfilerWindow(m_ProfileRightView);
return true;
}
m_ProfilerWindowInterface.GetProfilerWindowHandle();
return false;
}
void OnProfilerWindowCpuModuleSelectionChanged(string selectedMarker, string threadGroupName, string threadName)
{
// selectedMarker can be "" if in play mode and no active timeline shown (on versions pre 2021.1
if (!string.IsNullOrEmpty(selectedMarker) && selectedMarker != m_LastProfilerSelectedMarker)
{
m_LastProfilerSelectedMarker = selectedMarker;
m_SelectionEventFromProfilerWindowInProgress = true;
SelectMarker(selectedMarker, threadGroupName, threadName);
m_SelectionEventFromProfilerWindowInProgress = false;
Repaint();
}
}
void OnProfilerWindowSelectedFrameChanged(int newlySelectedFrame)
{
// selectedMarker can be "" if in play mode and no active timeline shown (on versions pre 2021.1
if (!string.IsNullOrEmpty(m_LastProfilerSelectedMarker))
{
m_SelectionEventFromProfilerWindowInProgress = true;
UpdateSelectedMarkerName(m_LastProfilerSelectedMarker);
m_SelectionEventFromProfilerWindowInProgress = false;
Repaint();
}
}
void Update()
{
CheckScreenSizeChanges();
// Check if profiler is open
if (SyncWithProfilerWindow())
{
// Check if the selected marker in the profiler has changed
m_ProfilerWindowInterface.PollProfilerWindowMarkerName();
m_ProfilerWindowInterface.PollSelectedFrameChanges();
}
// Deferred to here so drawing isn't messed up by changing tab half way through a function rendering the old tab
ProcessTabSwitch();
// Force repaint for the progress bar
if (IsAnalysisRunning())
{
int loadingProgress;
int analysisProgress;
if (IsLoading())
{
loadingProgress = (int)(ProfileData.GetLoadingProgress() * 100);
analysisProgress = 0;
}
else
{
loadingProgress = 100;
analysisProgress = m_ProfileAnalyzer.GetProgress();
if (m_ThreadPhases > 1)
{
// Use thread phases to evaluate the progress as analysis process might contain multiple ProfileAnalyzer passes.
analysisProgress = (100 * m_ThreadPhase) / m_ThreadPhases;
}
}
int progress = (loadingProgress + analysisProgress) / 2;
if (m_ThreadProgress != progress)
{
m_ThreadProgress = progress;
m_RequestRepaint = true;
}
}
if (m_ThreadSelectionNew != null)
{
m_ThreadSelection = new ThreadSelection(m_ThreadSelectionNew);
m_ThreadSelectionNew = null;
m_ThreadSelectionSummary = CalculateSelectedThreadsSummary();
}
switch (m_ThreadActivity)
{
case ThreadActivity.AnalyzeDone:
// Create table when analysis complete
UpdateAnalysisFromAsyncProcessing(m_ProfileSingleView, m_FullAnalysisRequired);
m_FullAnalysisRequired = false;
UpdateThreadNames();
BuildRemoveMarkerList();
if (m_ProfileSingleView.analysis != null)
{
CreateProfileTable();
m_RequestRepaint = true;
}
m_ThreadActivity = ThreadActivity.None;
if (m_NewDataLoaded)
{
if (m_ProfileSingleView.IsDataValid())
{
// Don't bother sending an analytic if the data set is empty (should never occur anyway but consistent with comparison flow)
ProfileAnalyzerAnalytics.SendUIUsageModeEvent(ProfileAnalyzerAnalytics.UIUsageMode.Single, m_LastAnalysisTimeMilliseconds / 1000f);
}
m_NewDataLoaded = false;
}
SelectMarker(m_SelectedMarker.name);
break;
case ThreadActivity.CompareDone:
UpdateAnalysisFromAsyncProcessing(m_ProfileLeftView, m_FullCompareRequired);
UpdateAnalysisFromAsyncProcessing(m_ProfileRightView, m_FullCompareRequired);
m_FullCompareRequired = false;
m_Pairings = m_PairingsNew;
m_TotalCombinedMarkerCount = m_TotalCombinedMarkerCountNew;
UpdateThreadNames();
BuildRemoveMarkerList();
if (m_ProfileLeftView.analysis != null && m_ProfileRightView.analysis != null)
{
CreateComparisonTable();
m_RequestRepaint = true;
}
m_ThreadActivity = ThreadActivity.None;
if (m_NewComparisonDataLoaded)
{
if (m_ProfileLeftView.IsDataValid() && m_ProfileRightView.IsDataValid())
{
// Don't bother sending an analytic when one (or more) of the data sets is blank (as no comparison is really made)
ProfileAnalyzerAnalytics.SendUIUsageModeEvent(ProfileAnalyzerAnalytics.UIUsageMode.Comparison, m_LastCompareTimeMilliseconds / 1000f);
}
m_NewComparisonDataLoaded = false;
}
SelectMarker(m_SelectedMarker.name);
break;
case ThreadActivity.LoadDone:
SetView(GetActiveView, m_ProfilerData, m_Path, GetActiveFrameTimeGraph);
switch (m_ActiveTab)
{
case ActiveTab.Compare:
// Remove pairing if both left/right point at the same data
if (m_ProfileLeftView.path == m_ProfileRightView.path)
{
SetFrameTimeGraphPairing(false);
}
m_FullCompareRequired = true;
m_RequestCompare = true;
break;
case ActiveTab.Summary:
m_RequestAnalysis = true;
m_FullAnalysisRequired = true;
break;
default:
throw new ArgumentOutOfRangeException();
}
m_ThreadActivity = ThreadActivity.None;
break;
}
if (m_RequestAnalysis)
{
if (!IsAnalysisRunning())
{
Analyze();
m_RequestAnalysis = false;
}
}
if (m_RequestCompare)
{
if (!IsAnalysisRunning())
{
Compare();
m_RequestCompare = false;
}
}
if (m_RequestRepaint)
{
Repaint();
m_RequestRepaint = false;
}
if (m_AnalyzeInUpdatePhase > 0)
{
switch (m_AnalyzeInUpdatePhase)
{
case 1:
UnityEngine.Profiling.Profiler.enabled = true;
m_AnalyzeInUpdatePhase++;
return;
case 2:
AnalyzeSync();
UpdateAnalysisFromAsyncProcessing(m_ProfileSingleView, m_FullAnalysisRequired);
m_FullAnalysisRequired = false;
m_AnalyzeInUpdatePhase++;
return;
case 3:
m_AnalyzeInUpdatePhase++;
return;
case 4:
UnityEngine.Profiling.Profiler.enabled = false;
m_AnalyzeInUpdatePhase++;
return;
default:
m_AnalyzeInUpdatePhase = 0;
break;
}
}
}
ProfileDataView GetActiveView
{
get
{
switch (m_ActiveLoadingView)
{
case ActiveView.Single:
return m_ProfileSingleView;
case ActiveView.Left:
return m_ProfileLeftView;
case ActiveView.Right:
return m_ProfileRightView;
default:
throw new ArgumentOutOfRangeException();
}
}
}
FrameTimeGraph GetActiveFrameTimeGraph
{
get
{
switch (m_ActiveLoadingView)
{
case ActiveView.Single:
return m_FrameTimeGraph;
case ActiveView.Left:
return m_LeftFrameTimeGraph;
case ActiveView.Right:
return m_RightFrameTimeGraph;
default:
throw new ArgumentOutOfRangeException();
}
}
}
void UpdateAnalysisFromAsyncProcessing(ProfileDataView view, bool full)
{
view.analysis = view.analysisNew;
if (full)
{
if (view.selectedIndices != null && view.IsDataValid() && view.selectedIndices.Count == view.data.GetFrameCount())
view.analysisFull = view.analysis;
else
view.analysisFull = view.analysisFullNew;
if (view.IsDataValid())
view.FindKeyMarkers();
}
}
List<FrameTimeGraph.Data> GetFrameTimeData(ProfileData profileData)
{
List<FrameTimeGraph.Data> data = new List<FrameTimeGraph.Data>();
int frames = profileData.GetFrameCount();
bool removeFrameSyncTime = false;
int removeMarkerIndex = -1;
ThreadIdentifier mainThreadSelection = new ThreadIdentifier("Main Thread", 1);;
string removeMarker = GetRemoveMarker();
if (removeMarker != null)
{
// Find marker to remove
removeMarkerIndex = profileData.GetMarkerIndex(removeMarker);
removeFrameSyncTime = (removeMarkerIndex != -1);
}
for (int frameOffset = 0; frameOffset < frames; frameOffset++)
{
ProfileFrame frame = profileData.GetFrame(frameOffset);
float ms = frame.msFrame;
if (removeFrameSyncTime)
{
for (int threadIndex = 0; threadIndex < frame.threads.Count; threadIndex++)
{
ProfileThread thread = frame.threads[threadIndex];
// Marker only on main thread
if (profileData.GetThreadName(thread) == mainThreadSelection.threadNameWithIndex)
{
foreach (var marker in thread.markers)
{
if (marker.nameIndex != removeMarkerIndex)
continue;
// May be multiple instances of this marker in the 'custom' case (so we can't just break out of marker loop here)
ms-= marker.msMarkerTotal;
}
break;
}
}
}
FrameTimeGraph.Data dataPoint = new FrameTimeGraph.Data(ms, frameOffset);
data.Add(dataPoint);
}
return data;
}
void Load()
{
m_Path = EditorUtility.OpenFilePanel("Load profile analyzer data file", "", "pdata");
if (m_Path.Length != 0)
{
m_ActiveLoadingView = ActiveView.Single;
BeginAsyncAction(ThreadActivity.Load);
}
GUIUtility.ExitGUI();
}
void UpdateMatchingProfileData(ProfileData data, ref string path, ProfileAnalysis analysis, string newPath)
{
// Update left/right data if we are effectively overwriting it.
if (m_ProfileLeftView.path == newPath)
{
SetView(m_ProfileLeftView, data, newPath, m_LeftFrameTimeGraph);
m_RequestCompare = true;
m_FullCompareRequired = true;
}
if (m_ProfileRightView.path == newPath)
{
SetView(m_ProfileRightView, data, newPath, m_RightFrameTimeGraph);
m_RequestCompare = true;
m_FullCompareRequired = true;
}
// Update single view if needed
if (m_ProfileSingleView.path == newPath)
{
SetView(m_ProfileSingleView, data, newPath, m_FrameTimeGraph);
m_ProfileSingleView.analysis = analysis;
}
path = newPath;
}
void Save(ProfileDataView dataView, bool updateDataViewWithSelectedPath = false)
{
string newPath = EditorUtility.SaveFilePanel("Save profile analyzer data file", "", "capture.pdata", "pdata");
if (newPath.Length != 0)
{
if (updateDataViewWithSelectedPath)
{
dataView.path = newPath;
}
if (ProfileData.Save(newPath, dataView.data))
{
UpdateMatchingProfileData(dataView.data, ref dataView.path, dataView.analysis, newPath);
}
}
GUIUtility.ExitGUI();
}
int GetTotalCombinedMarkerCount(ProfileData left, ProfileData right)
{
if (left == null)
return 0;
if (right == null)
return 0;
List<string> leftMarkers = left.GetMarkerNames();
if (leftMarkers == null)
return 0;
List<string> rightMarkers = right.GetMarkerNames();
if (rightMarkers == null)
return 0;
HashSet<string> markerPairs = new HashSet<string>();
for (int index = 0; index < leftMarkers.Count; index++)
{
string markerName = leftMarkers[index];
markerPairs.Add(markerName);
}
for (int index = 0; index < rightMarkers.Count; index++)
{
string markerName = rightMarkers[index];
if (!markerPairs.Contains(markerName))
{
markerPairs.Add(markerName);
}
}
return markerPairs.Count;
}
List<MarkerPairing> GeneratePairings(ProfileAnalysis leftAnalysis, ProfileAnalysis rightAnalysis)
{
if (leftAnalysis == null)
return null;
if (rightAnalysis == null)
return null;
List<MarkerData> leftMarkers = leftAnalysis.GetMarkers();
if (leftMarkers == null)
return null;
List<MarkerData> rightMarkers = rightAnalysis.GetMarkers();
if (rightMarkers == null)
return null;
Dictionary<string, MarkerPairing> markerPairs = new Dictionary<string, MarkerPairing>();
for (int index = 0; index < leftMarkers.Count; index++)
{
MarkerData marker = leftMarkers[index];
MarkerPairing pair = new MarkerPairing
{
name = marker.name,
leftIndex = index,
rightIndex = -1
};
markerPairs[marker.name] = pair;
}
for (int index = 0; index < rightMarkers.Count; index++)
{
MarkerData marker = rightMarkers[index];
if (markerPairs.ContainsKey(marker.name))
{
MarkerPairing pair = markerPairs[marker.name];
pair.rightIndex = index;
markerPairs[marker.name] = pair;
}
else
{
MarkerPairing pair = new MarkerPairing
{
name = marker.name,
leftIndex = -1,
rightIndex = index
};
markerPairs[marker.name] = pair;
}
}
List<MarkerPairing> pairings = new List<MarkerPairing>();
foreach (MarkerPairing pair in markerPairs.Values)
pairings.Add(pair);
return pairings;
}
void SetThreadPhaseCount(ThreadActivity activity)
{
// Will be refined by the analysis functions
if (activity == ThreadActivity.Compare)
{
m_ThreadPhases = 8;
}
else
{
m_ThreadPhases = 2;
}
}
void BeginAsyncAction(ThreadActivity activity)
{
if (IsAnalysisRunning())
return;
m_ThreadActivity = activity;
m_ThreadProgress = 0;
m_ThreadPhase = 0;
SetThreadPhaseCount(activity);
m_BackgroundThread = new Thread(BackgroundThread);
m_BackgroundThread.Start();
}
void CreateComparisonTable()
{
UpdateThreadNames();
// Set default sorting state
int sortedColumn = (int)ComparisonTable.MyColumns.AbsDiff;
bool sortAscending = false;
// Query last sorting state
if (m_ComparisonMulticolumnHeaderState != null)
{
if (m_ComparisonMulticolumnHeaderState.sortedColumnIndex >= 0)
{
sortedColumn = m_ComparisonMulticolumnHeaderState.sortedColumnIndex;
if (sortedColumn >= 0 && sortedColumn < m_ComparisonMulticolumnHeaderState.columns.Length)
sortAscending = m_ComparisonMulticolumnHeaderState.columns[sortedColumn].sortedAscending;
}
}
if (m_ComparisonTreeViewState == null)
m_ComparisonTreeViewState = new TreeViewState();
m_ComparisonMulticolumnHeaderState = ComparisonTable.CreateDefaultMultiColumnHeaderState(m_CompareModeFilter);
var multiColumnHeader = new MultiColumnHeader(m_ComparisonMulticolumnHeaderState);
multiColumnHeader.SetSorting(sortedColumn, sortAscending);
multiColumnHeader.ResizeToFit();
m_ComparisonTable = new ComparisonTable(m_ComparisonTreeViewState, multiColumnHeader, m_ProfileLeftView, m_ProfileRightView, m_Pairings, m_hideRemovedMarkers, this, m_2D, UIColor.left, UIColor.right);
if (!IsSelectedMarkerNameValid())
SelectPairing(0);
else
SelectPairingByName(m_SelectedMarker.name);
}
void CalculatePairingbuckets(ProfileAnalysis left, ProfileAnalysis right, List<MarkerPairing> pairings)
{
var leftMarkers = left.GetMarkers();
var rightMarkers = right.GetMarkers();
// using a for loop instead of foreach is surprisingly faster on Mono
for (int i = 0, n = pairings.Count; i < n; i++)
{
var pairing = pairings[i];
float min = float.MaxValue;
float max = 0.0f;
MarkerData leftMarker = null;
MarkerData rightMarker = null;
if (pairing.leftIndex >= 0)
{
leftMarker = leftMarkers[pairing.leftIndex];
max = Math.Max(max, leftMarker.msMax);
min = Math.Min(min, leftMarker.msMin);
}
if (pairing.rightIndex >= 0)
{
rightMarker = rightMarkers[pairing.rightIndex];
max = Math.Max(max, rightMarker.msMax);
min = Math.Min(min, rightMarker.msMin);
}
int countMin = int.MaxValue;
int countMax = 0;
if (leftMarker != null)
{
countMax = Math.Max(countMax, leftMarker.countMax);
countMin = Math.Min(countMin, leftMarker.countMin);
}
if (rightMarker != null)
{
countMax = Math.Max(countMax, rightMarker.countMax);
countMin = Math.Min(countMin, rightMarker.countMin);
}
if (leftMarker != null)
{
leftMarker.ComputeBuckets(min, max);
leftMarker.ComputeCountBuckets(countMin, countMax);
}
if (rightMarker != null)
{
rightMarker.ComputeBuckets(min, max);
rightMarker.ComputeCountBuckets(countMin, countMax);
}
}
}
bool CompareSync()
{
if (!m_ProfileLeftView.IsDataValid())
return false;
if (!m_ProfileRightView.IsDataValid())
return false;
List<string> threadUINamesNew;
List<string> threadNamesNew;
Dictionary<string, string> threadNameToUINameNew;
GetThreadNames(m_ProfileLeftView.data, m_ProfileRightView.data, out threadUINamesNew, out threadNamesNew, out threadNameToUINameNew);
List<string> threadSelection = GetLimitedThreadSelection(threadNamesNew, m_ThreadSelection);
System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
int updateDepthPhase = m_NewComparisonDataLoaded ? 2 : 0;
int fullLeftPhase = (m_FullCompareRequired && m_ProfileLeftView.selectedIndices.Count != m_ProfileLeftView.data.GetFrameCount()) ? 1 : 0;
int fullRightPhase = (m_FullCompareRequired && m_ProfileLeftView.selectedIndices.Count != m_ProfileLeftView.data.GetFrameCount()) ? 1 : 0;
m_ThreadPhases = 2 /*scan left and right*/ + updateDepthPhase + 2 /*fullLeftPhase and fullRightPhase*/ + 2 /*analyze left and right*/;
bool selfTimes = IsSelfTime();
// First scan just the frames
m_ThreadPhase = 0;
var leftAnalysisNew = m_ProfileAnalyzer.Analyze(m_ProfileLeftView.data, m_ProfileLeftView.selectedIndices, null, m_DepthSliceUI.depthFilter1, selfTimes, m_ParentMarker, 0, GetRemoveMarker());
m_ThreadPhase++;
var rightAnalysisNew = m_ProfileAnalyzer.Analyze(m_ProfileRightView.data, m_ProfileRightView.selectedIndices, null, m_DepthSliceUI.depthFilter2, selfTimes, m_ParentMarker, 0, GetRemoveMarker());
m_ThreadPhase++;
if (leftAnalysisNew == null || rightAnalysisNew == null)
{
stopwatch.Stop();
return false;
}
// Calculate the max frame time of the two scans
float timeScaleMax = Math.Max(leftAnalysisNew.GetFrameSummary().msMax, rightAnalysisNew.GetFrameSummary().msMax);
// Need to recalculate the depth difference when thread filters change
// For now do it always if the depth is auto and not 'all'
if (updateDepthPhase != 0)
{
var leftAnalysis = m_ProfileAnalyzer.Analyze(m_ProfileLeftView.data, m_ProfileLeftView.selectedIndices, threadSelection, ProfileAnalyzer.kDepthAll, selfTimes, m_ParentMarker, timeScaleMax, GetRemoveMarker());
m_ThreadPhase++;
var rightAnalysis = m_ProfileAnalyzer.Analyze(m_ProfileRightView.data, m_ProfileRightView.selectedIndices, threadSelection, ProfileAnalyzer.kDepthAll, selfTimes, m_ParentMarker, timeScaleMax, GetRemoveMarker());
m_ThreadPhase++;
var pairings = GeneratePairings(leftAnalysis, rightAnalysis);
if (m_DepthSliceUI.UpdateDepthForCompareSync(leftAnalysis, rightAnalysis, pairings, m_ProfileLeftView, m_ProfileRightView))
{
// New depth diff calculated to we need to do the full analysis
if (fullLeftPhase == 0)
fullLeftPhase = 1;
if (fullRightPhase == 0)
fullRightPhase = 1;
}
}
// Now process the markers and setup buckets using the overall max frame time
List<int> selection = new List<int>();
if (fullLeftPhase != 0)
{
selection.Clear();
for (int frameOffset = 0; frameOffset < m_ProfileLeftView.data.GetFrameCount(); frameOffset++)
{
selection.Add(m_ProfileLeftView.data.OffsetToDisplayFrame(frameOffset));
}
// We don't pass timeScaleMax as that is only for the selected region.
// Pass 0 to auto select full range
m_ProfileLeftView.analysisFullNew = m_ProfileAnalyzer.Analyze(m_ProfileLeftView.data, selection, threadSelection, m_DepthSliceUI.depthFilter1, selfTimes, m_ParentMarker, 0, GetRemoveMarker());
m_ThreadPhase++;
}
m_ThreadPhase++;
if (fullRightPhase != 0)
{
selection.Clear();
for (int frameOffset = 0; frameOffset < m_ProfileRightView.data.GetFrameCount(); frameOffset++)
{
selection.Add(m_ProfileRightView.data.OffsetToDisplayFrame(frameOffset));
}
// We don't pass timeScaleMax as that is only for the selected region.
// Pass 0 to auto select full range
m_ProfileRightView.analysisFullNew = m_ProfileAnalyzer.Analyze(m_ProfileRightView.data, selection, threadSelection, m_DepthSliceUI.depthFilter2, selfTimes, m_ParentMarker, 0, GetRemoveMarker());
m_ThreadPhase++;
}
m_ThreadPhase++;
m_ProfileLeftView.analysisNew = m_ProfileAnalyzer.Analyze(m_ProfileLeftView.data, m_ProfileLeftView.selectedIndices, threadSelection, m_DepthSliceUI.depthFilter1, selfTimes, m_ParentMarker, timeScaleMax, GetRemoveMarker());
m_ThreadPhase++;
m_ProfileRightView.analysisNew = m_ProfileAnalyzer.Analyze(m_ProfileRightView.data, m_ProfileRightView.selectedIndices, threadSelection, m_DepthSliceUI.depthFilter2, selfTimes, m_ParentMarker, timeScaleMax, GetRemoveMarker());
m_ThreadPhase++;
m_TotalCombinedMarkerCountNew = GetTotalCombinedMarkerCount(m_ProfileLeftView.data, m_ProfileRightView.data);
m_PairingsNew = GeneratePairings(m_ProfileLeftView.analysisNew, m_ProfileRightView.analysisNew);
CalculatePairingbuckets(m_ProfileLeftView.analysisNew, m_ProfileRightView.analysisNew, m_PairingsNew);
stopwatch.Stop();
m_LastCompareTimeMilliseconds = stopwatch.ElapsedMilliseconds;
TimeSpan ts = stopwatch.Elapsed;
if (ts.Minutes > 0)
m_LastCompareTime = string.Format("Last compare time {0} mins {1} secs {2} ms ", ts.Minutes, ts.Seconds, ts.Milliseconds);
else if (ts.Seconds > 0)
m_LastCompareTime = string.Format("Last compare time {0} secs {1} ms ", ts.Seconds, ts.Milliseconds);
else
m_LastCompareTime = string.Format("Last compare time {0} ms ", ts.Milliseconds);
return true;
}
void Compare()
{
if (m_Async)
{
//m_comparisonTable = null;
//m_ProfileLeftView.analysis = null;
//m_ProfileRightView.analysis = null;
BeginAsyncAction(ThreadActivity.Compare);
}
else
{
CompareSync();
UpdateAnalysisFromAsyncProcessing(m_ProfileLeftView, m_FullCompareRequired);
UpdateAnalysisFromAsyncProcessing(m_ProfileRightView, m_FullCompareRequired);
m_FullCompareRequired = false;
}
}
List<MarkerPairing> GetPairings()
{
return m_Pairings;
}
int GetUnsavedIndex(string path)
{
if (path == null)
return 0;
Regex unsavedRegExp = new Regex(@"^Unsaved[\s*]([\d]*)", RegexOptions.IgnoreCase);
Match match = unsavedRegExp.Match(path);
if (match.Length <= 0)
return 0;
return Int32.Parse(match.Groups[1].Value);
}
void PullFromProfiler(int firstFrame, int lastFrame, ProfileDataView view, FrameTimeGraph frameTimeGraph)
{
m_ProgressBar.InitProgressBar("Pulling Frames from Profiler", "Please wait...", lastFrame - firstFrame);
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
ProfileData newProfileData = m_ProfilerWindowInterface.PullFromProfiler(firstFrame, lastFrame);
ProfileAnalyzerAnalytics.SendUIButtonEvent(ProfileAnalyzerAnalytics.UIButton.Pull, analytic);
frameTimeGraph.Reset();
frameTimeGraph.SetData(GetFrameTimeData(newProfileData));
// Check if this is new data (rather than repulling the same data)
if (!newProfileData.IsSame(view.data))
{
if (view == m_ProfileSingleView)
m_NewDataLoaded = true;
else
m_NewComparisonDataLoaded = true;
}
// Update the path to use the same saved file name if this is the same data as another view
if (newProfileData.IsSame(m_ProfileLeftView.data))
{
view.path = m_ProfileLeftView.path;
}
else if (newProfileData.IsSame(m_ProfileRightView.data))
{
view.path = m_ProfileRightView.path;
}
else if (newProfileData.IsSame(m_ProfileSingleView.data))
{
view.path = m_ProfileSingleView.path;
}
else
{
int lastIndex = 0;
lastIndex = Math.Max(lastIndex, GetUnsavedIndex(m_ProfileSingleView.path));
lastIndex = Math.Max(lastIndex, GetUnsavedIndex(m_ProfileLeftView.path));
lastIndex = Math.Max(lastIndex, GetUnsavedIndex(m_ProfileRightView.path));
view.path = string.Format("Unsaved {0}", lastIndex + 1);
}
if (view.data != null && !TmpInUse(view, view.data.FilePath))
view.data.DeleteTmpFiles();
view.data = newProfileData;
view.SelectFullRange();
// Remove pairing if both left/right point at the same data
if (m_ProfileLeftView.path == m_ProfileRightView.path)
{
SetFrameTimeGraphPairing(false);
}
m_ProgressBar.ClearProgressBar();
}
void BackgroundThread()
{
try
{
switch (m_ThreadActivity)
{
case ThreadActivity.Analyze:
AnalyzeSync();
m_ThreadActivity = ThreadActivity.AnalyzeDone;
break;
case ThreadActivity.Compare:
CompareSync();
m_ThreadActivity = ThreadActivity.CompareDone;
break;
case ThreadActivity.AnalyzeDone:
break;
case ThreadActivity.CompareDone:
break;
case ThreadActivity.Load:
m_ThreadActivity = ProfileData.Load(m_Path, out m_ProfilerData) ? ThreadActivity.LoadDone : ThreadActivity.None;
break;
case ThreadActivity.LoadDone:
break;
default:
// m_threadActivity = ThreadActivity.None;
break;
}
}
catch (ThreadAbortException)
{
var activity = (m_ThreadActivity == ThreadActivity.Load) ? "Load" : "Analysis";
Debug.LogFormat("{0} failed due to a domain reload. Please try again.", activity);
}
}
void SelectFirstMarkerInTable()
{
// SelectMarkerByIndex(0) would only select the first one found, not the first in the sorted list
if (m_ProfileTable == null)
return;
var rows = m_ProfileTable.GetRows();
if (rows == null || rows.Count < 1)
return;
SelectMarkerByName(rows[0].displayName);
}
bool IsSelectedMarkerNameValid()
{
if (string.IsNullOrEmpty(m_SelectedMarker.name))
return false;
if (m_hideRemovedMarkers && m_SelectedMarker.name == GetRemoveMarker())
return false;
return true;
}
void CreateProfileTable()
{
if (m_ProfileTreeViewState == null)
m_ProfileTreeViewState = new TreeViewState();
// Set default sorting state
int sortedColumn = (int)ProfileTable.MyColumns.Median;
bool sortAscending = false;
// Query last sorting state
if (m_ProfileMulticolumnHeaderState != null)
{
if (m_ProfileMulticolumnHeaderState.sortedColumnIndex >= 0)
{
sortedColumn = m_ProfileMulticolumnHeaderState.sortedColumnIndex;
if (sortedColumn >= 0 && sortedColumn < m_ProfileMulticolumnHeaderState.columns.Length)
sortAscending = m_ProfileMulticolumnHeaderState.columns[sortedColumn].sortedAscending;
}
}
m_ProfileMulticolumnHeaderState = ProfileTable.CreateDefaultMultiColumnHeaderState(m_SingleModeFilter);
var multiColumnHeader = new MultiColumnHeader(m_ProfileMulticolumnHeaderState);
multiColumnHeader.SetSorting(sortedColumn, sortAscending);
multiColumnHeader.ResizeToFit();
m_ProfileTable = new ProfileTable(m_ProfileTreeViewState, multiColumnHeader, m_ProfileSingleView, m_hideRemovedMarkers, this, m_2D, UIColor.bar);
if (!IsSelectedMarkerNameValid())
SelectFirstMarkerInTable();
else
SelectMarkerByName(m_SelectedMarker.name);
UpdateThreadNames();
}
void AnalyzeSync()
{
if (!m_ProfileSingleView.IsDataValid())
return;
System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
List<string> threadUINamesNew;
List<string> threadNamesNew;
Dictionary<string, string> threadNameToUINameNew;
GetThreadNames(m_ProfileSingleView.data, out threadUINamesNew, out threadNamesNew, out threadNameToUINameNew);
List<string> threadSelection = GetLimitedThreadSelection(threadNamesNew, m_ThreadSelection);
int fullPhase = (m_FullAnalysisRequired && (m_ProfileSingleView.selectedIndices.Count != m_ProfileSingleView.data.GetFrameCount())) ? 1 : 0;
m_ThreadPhases = 1 + fullPhase;
bool selfTimes = IsSelfTime();
m_ThreadPhase = 0;
if (fullPhase == 1)
{
List<int> selection = new List<int>();
for (int frameOffset = 0; frameOffset < m_ProfileSingleView.data.GetFrameCount(); frameOffset++)
{
selection.Add(m_ProfileSingleView.data.OffsetToDisplayFrame(frameOffset));
}
m_ProfileSingleView.analysisFullNew = m_ProfileAnalyzer.Analyze(m_ProfileSingleView.data, selection, threadSelection, m_DepthSliceUI.depthFilter, selfTimes, m_ParentMarker, 0, GetRemoveMarker());
m_ThreadPhase++;
}
m_ProfileSingleView.analysisNew = m_ProfileAnalyzer.Analyze(m_ProfileSingleView.data, m_ProfileSingleView.selectedIndices, threadSelection, m_DepthSliceUI.depthFilter, selfTimes, m_ParentMarker, 0, GetRemoveMarker());
m_ThreadPhase++;
stopwatch.Stop();
m_LastAnalysisTimeMilliseconds = stopwatch.ElapsedMilliseconds;
TimeSpan ts = stopwatch.Elapsed;
if (ts.Minutes > 0)
m_LastAnalysisTime = string.Format("Last analysis time {0} mins {1} secs {2} ms ", ts.Minutes, ts.Seconds, ts.Milliseconds);
else if (ts.Seconds > 0)
m_LastAnalysisTime = string.Format("Last analysis time {0} secs {1} ms ", ts.Seconds, ts.Milliseconds);
else
m_LastAnalysisTime = string.Format("Last analysis time {0} ms ", ts.Milliseconds);
}
void Analyze()
{
if (m_EnableAnalysisProfiling)
{
m_AnalyzeInUpdatePhase = 1;
return;
}
if (m_Async)
{
//m_profileTable = null;
//m_ProfileSingleView.analysis = null;
BeginAsyncAction(ThreadActivity.Analyze);
}
else
{
AnalyzeSync();
UpdateAnalysisFromAsyncProcessing(m_ProfileSingleView, m_FullAnalysisRequired);
m_FullAnalysisRequired = false;
}
}
void GetThreadNames(ProfileData profleData, out List<string> threadUINames, out List<string> threadFilters, out Dictionary<string, string> threadNameToUIName)
{
GetThreadNames(profleData, null, out threadUINames, out threadFilters, out threadNameToUIName);
}
public string GetUIThreadName(string threadNameWithIndex)
{
string threadName = "";
m_ThreadNameToUIName.TryGetValue(threadNameWithIndex, out threadName);
return threadName;
}
string GetFriendlyThreadName(string threadNameWithIndex, bool single)
{
if (string.IsNullOrEmpty(threadNameWithIndex))
return "";
var info = threadNameWithIndex.Split(':');
int threadGroupIndex = int.Parse(info[0]);
var threadName = info[1].Trim();
if (single) // Single instance of this thread name
{
return threadName;
}
else
{
// The original format was "Worker 0"
// The internal formatting is 1:Worker (1+original value).
// Hence the -1 here
return string.Format("{0} {1}", threadName, threadGroupIndex - 1);
}
}
internal int CompareUINames(string a, string b)
{
var aSpaceIndex = a.LastIndexOf(' ');
var bSpaceIndex = b.LastIndexOf(' ');
if (aSpaceIndex >= 0 && bSpaceIndex >= 0)
{
var aThreadName = a.Substring(0, aSpaceIndex);
var bThreadName = b.Substring(0, bSpaceIndex);
if (aThreadName == bThreadName)
{
var aThreadIndex = a.Substring(aSpaceIndex + 1);
var bThreadIndex = b.Substring(bSpaceIndex + 1);
if (aThreadIndex == "All" && bThreadIndex != "All")
return -1;
if (aThreadIndex != "All" && bThreadIndex == "All")
return 1;
int aGroupIndex;
if (int.TryParse(aThreadIndex, out aGroupIndex))
{
int bGroupIndex;
if (int.TryParse(bThreadIndex, out bGroupIndex))
{
return aGroupIndex.CompareTo(bGroupIndex);
}
}
}
}
return a.CompareTo(b);
}
void GetThreadNames(ProfileData leftData, ProfileData rightData, out List<string> threadUINames, out List<string> threadFilters, out Dictionary<string, string> threadNameToUIName)
{
List<string> threadNames = (leftData != null) ? new List<string>(leftData.GetThreadNames()) : new List<string>();
if (rightData != null)
{
foreach (var threadNameWithIndex in rightData.GetThreadNames())
{
if (!threadNames.Contains(threadNameWithIndex))
{
// TODO: Insert after last thread with same name (or at end)
threadNames.Add(threadNameWithIndex);
}
}
}
Dictionary<string, string> threadNamesDict = new Dictionary<string, string>();
for (int index = 0; index < threadNames.Count; index++)
{
var threadNameWithIndex = threadNames[index];
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
if (threadIdentifier.index == 1)
{
if (threadNames.Contains(string.Format("2:{0}", threadIdentifier.name)))
{
var threadGroupIdentifier = new ThreadIdentifier(threadIdentifier);
threadGroupIdentifier.SetAll();
// First thread name of a group with the same name
// Add an 'all' selection
threadNamesDict[string.Format("{0} : All", threadIdentifier.name)] = threadGroupIdentifier.threadNameWithIndex;
// And add the first item too
threadNamesDict[GetFriendlyThreadName(threadNameWithIndex, false)] = threadNameWithIndex;
}
else
{
// Single instance of this thread name
threadNamesDict[GetFriendlyThreadName(threadNameWithIndex, true)] = threadNameWithIndex;
}
}
else
{
threadNamesDict[GetFriendlyThreadName(threadNameWithIndex, false)] = threadNameWithIndex;
}
}
List<string> uiNames = new List<string>();
foreach (string uiName in threadNamesDict.Keys)
uiNames.Add(uiName);
uiNames.Sort(CompareUINames);
var allThreadIdentifier = new ThreadIdentifier();
allThreadIdentifier.SetName("All");
allThreadIdentifier.SetAll();
threadUINames = new List<string>();
threadFilters = new List<string>();
threadNameToUIName = new Dictionary<string, string>();
threadUINames.Add(allThreadIdentifier.name);
threadFilters.Add(allThreadIdentifier.threadNameWithIndex);
threadNameToUIName[allThreadIdentifier.name] = allThreadIdentifier.threadNameWithIndex;
foreach (string uiName in uiNames)
{
// Strip off the group name
// Note we don't do this in GetFriendlyThreadName else we would collapse the same named threads (in different groups) in the dict
string groupName;
string threadName = ProfileData.GetThreadNameWithoutGroup(uiName, out groupName);
string threadFilter = threadNamesDict[uiName];
threadUINames.Add(threadName);
threadFilters.Add(threadFilter);
threadNameToUIName[threadFilter] = threadName;
}
}
void UpdateThreadGroupSelection(List<string> threadNames, ThreadSelection threadSelection)
{
// Make sure all members of active groups are present
foreach (string threadGroupNameWithIndex in threadSelection.groups)
{
var threadGroupIdentifier = new ThreadIdentifier(threadGroupNameWithIndex);
if (threadGroupIdentifier.name == "All" && threadGroupIdentifier.index == ThreadIdentifier.kAll)
{
foreach (string threadNameWithIndex in threadNames)
{
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
if (threadIdentifier.index != ThreadIdentifier.kAll)
{
if (!threadSelection.selection.Contains(threadNameWithIndex))
threadSelection.selection.Add(threadNameWithIndex);
}
}
}
else
{
foreach (string threadNameWithIndex in threadNames)
{
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
if (threadIdentifier.name == threadGroupIdentifier.name &&
threadIdentifier.index != ThreadIdentifier.kAll)
{
if (!threadSelection.selection.Contains(threadNameWithIndex))
threadSelection.selection.Add(threadNameWithIndex);
}
}
}
}
}
List<string> GetLimitedThreadSelection(List<string> threadNames, ThreadSelection threadSelection)
{
List<string> limitedThreadSelection = new List<string>();
if (threadSelection.selection == null)
return limitedThreadSelection;
foreach (string threadNameWithIndex in threadSelection.selection)
{
if (threadNames.Contains(threadNameWithIndex))
limitedThreadSelection.Add(threadNameWithIndex);
}
// Make sure all members of active groups are present
foreach (string threadGroupNameWithIndex in threadSelection.groups)
{
var threadGroupIdentifier = new ThreadIdentifier(threadGroupNameWithIndex);
if (threadGroupIdentifier.name == "All" && threadGroupIdentifier.index == ThreadIdentifier.kAll)
{
foreach (string threadNameWithIndex in threadNames)
{
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
if (threadIdentifier.index != ThreadIdentifier.kAll)
{
if (!limitedThreadSelection.Contains(threadNameWithIndex))
limitedThreadSelection.Add(threadNameWithIndex);
}
}
}
else
{
foreach (string threadNameWithIndex in threadNames)
{
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
if (threadIdentifier.name == threadGroupIdentifier.name &&
threadIdentifier.index != ThreadIdentifier.kAll)
{
if (!limitedThreadSelection.Contains(threadNameWithIndex))
limitedThreadSelection.Add(threadNameWithIndex);
}
}
}
}
return limitedThreadSelection;
}
int ClampToRange(int value, int min, int max)
{
if (value < min)
value = min;
if (value > max)
value = max;
return value;
}
void GraphActive(bool active)
{
RequestRepaint();
}
void SetRange(List<int> selectedOffsets, int clickCount, FrameTimeGraph.State inputStatus)
{
if (inputStatus == FrameTimeGraph.State.Dragging)
return;
if (clickCount == 2)
{
if (selectedOffsets.Count > 0 && m_ProfileSingleView.inSyncWithProfilerData)
JumpToFrame(m_ProfileSingleView.data.OffsetToDisplayFrame(selectedOffsets[0]), m_ProfileSingleView.data, false);
}
else
{
m_ProfileSingleView.selectedIndices.Clear();
foreach (int offset in selectedOffsets)
{
m_ProfileSingleView.selectedIndices.Add(m_ProfileSingleView.data.OffsetToDisplayFrame(offset));
}
// Keep indices sorted
m_ProfileSingleView.selectedIndices.Sort();
m_RequestAnalysis = true;
}
}
internal void ClearSelection()
{
if (m_ActiveTab == ActiveTab.Summary)
{
m_ProfileSingleView.ClearSelection();
m_RequestAnalysis = true;
}
if (m_ActiveTab == ActiveTab.Compare)
{
m_ProfileLeftView.ClearSelection();
m_ProfileRightView.ClearSelection();
m_RequestCompare = true;
}
}
internal void SelectAllFrames()
{
if (m_ActiveTab == ActiveTab.Summary)
{
m_ProfileSingleView.SelectFullRange();
m_RequestAnalysis = true;
}
if (m_ActiveTab == ActiveTab.Compare)
{
m_ProfileLeftView.SelectFullRange();
m_ProfileRightView.SelectFullRange();
m_RequestCompare = true;
}
}
internal void SelectFramesContainingMarker(string markerName, bool inSelection)
{
if (m_ActiveTab == ActiveTab.Summary)
{
if (m_ProfileSingleView.SelectAllFramesContainingMarker(markerName, inSelection))
{
m_RequestAnalysis = true;
}
}
if (m_ActiveTab == ActiveTab.Compare)
{
if (m_ProfileLeftView.SelectAllFramesContainingMarker(markerName, inSelection))
{
m_RequestCompare = true;
}
if (m_ProfileRightView.SelectAllFramesContainingMarker(markerName, inSelection))
{
m_RequestCompare = true;
}
}
}
static List<string> GetNameFilters(string nameFilter)
{
List<string> nameFilters = new List<string>();
if (string.IsNullOrEmpty(nameFilter))
return nameFilters;
// Get all quoted strings, without the quotes
MatchCollection matches = quotedStringWithoutQuotes.Matches(nameFilter);
foreach (Match match in matches)
{
var theData = match.Groups[1].Value;
nameFilters.Add(theData);
}
// Get a new string with the quoted strings removed
string remaining = quotedString.Replace(nameFilter, "");
// Get all the remaining strings (that are space separated)
matches = stringWithoutWhiteSpace.Matches(remaining);
foreach (Match match in matches)
{
string theData = match.Groups[1].Value;
nameFilters.Add(theData);
}
return nameFilters;
}
internal List<string> GetNameFilters()
{
return GetNameFilters(m_NameFilter);
}
internal List<string> GetNameExcludes()
{
return GetNameFilters(m_NameExclude);
}
internal bool DoesMarkerPassFilter(string name)
{
return m_MarkerFilter.DoesMarkerPassFilter(this, name);
}
internal bool NameInIncludeList(string name, List<string> nameFilters)
{
return NameInFilterList(name, nameFilters, m_NameFilterOperation);
}
internal bool NameInExcludeList(string name, List<string> nameExcludes)
{
return NameInFilterList(name, nameExcludes, m_NameExcludeOperation);
}
static bool NameInFilterList(string name, List<string> nameFilters, NameFilterOperation operation)
{
switch (operation)
{
default:
//case NameFilterOperation.All:
{
foreach (string subString in nameFilters)
{
// As soon as name doesn't match one in the list then return false
if (name.IndexOf(subString, StringComparison.OrdinalIgnoreCase) < 0)
return false;
}
// Name is matching all the filters in the list
return true;
}
case NameFilterOperation.Any:
{
foreach (string subString in nameFilters)
{
// As soon as names matches one in the list then return true
if (name.IndexOf(subString, StringComparison.OrdinalIgnoreCase) >= 0)
return true;
}
return false;
}
}
}
static string FilterWithQuotes(string markerName)
{
return markerName.Contains(" ") ? string.Format("\"{0}\"", markerName) : markerName;
}
static void AddFilter(ref string filter, string quotedMarkerName)
{
if (string.IsNullOrEmpty(filter))
filter = quotedMarkerName;
else
filter = string.Format("{0} {1}", filter, quotedMarkerName);
}
static bool AddFilter(List<string> nameFilters, ref string filter, string markerName)
{
bool justAdded = false;
string quotedMarkerName = FilterWithQuotes(markerName);
if (!nameFilters.Contains(quotedMarkerName))
{
AddFilter(ref filter, quotedMarkerName);
justAdded = true;
}
return justAdded;
}
internal void AddToIncludeFilter(string markerName)
{
if (AddFilter(GetNameFilters(), ref m_NameFilter, markerName))
{
m_MarkerFilter.Clear();
UpdateMarkerTable();
}
// Remove from exclude list if in the include list
RemoveFromExcludeFilter(markerName);
}
internal void AddToExcludeFilter(string markerName)
{
if (AddFilter(GetNameExcludes(), ref m_NameExclude, markerName))
{
m_MarkerFilter.Clear();
UpdateMarkerTable();
}
// Remove from include list if in the include list
RemoveFromIncludeFilter(markerName);
}
internal void RemoveFromFilter(string markerName, List<string> nameFilters, ref string newFilters)
{
if (nameFilters.Count == 0)
return;
string nameFilterString = "";
bool updated = false;
foreach (string filter in nameFilters)
{
if (string.Compare(filter, markerName, StringComparison.CurrentCultureIgnoreCase) != 0)
AddFilter(ref nameFilterString, FilterWithQuotes(filter));
else
updated = true;
}
if (updated)
{
newFilters = nameFilterString;
m_MarkerFilter.Clear();
UpdateMarkerTable();
}
}
internal void RemoveFromIncludeFilter(string markerName)
{
RemoveFromFilter(markerName, GetNameFilters(), ref m_NameFilter);
}
internal void RemoveFromExcludeFilter(string markerName)
{
RemoveFromFilter(markerName, GetNameExcludes(), ref m_NameExclude);
}
internal void SetAsParentMarkerFilter(string markerName)
{
if (markerName != m_ParentMarker)
{
m_ParentMarker = markerName;
UpdateActiveTab(true);
}
}
float GetFilenameWidth(string path)
{
if (path == null)
return 0f;
string filename = System.IO.Path.GetFileName(path);
GUIContent content = new GUIContent(filename, path);
Vector2 size = GUI.skin.label.CalcSize(content);
return size.x;
}
void ShowFilename(string path)
{
if (path != null)
{
string filename = System.IO.Path.GetFileNameWithoutExtension(path);
GUIContent content = new GUIContent(filename, path);
Vector2 size = GUI.skin.label.CalcSize(content);
float width = Math.Min(size.x, 200f);
EditorGUILayout.LabelField(content, GUILayout.MaxWidth(width));
}
}
void DrawLoadSave()
{
EditorGUILayout.BeginHorizontal(GUILayout.MaxWidth(300), GUILayout.ExpandWidth(false));
GUIStyle buttonStyle = GUI.skin.button;
bool lastEnabled = GUI.enabled;
bool isAnalysisRunning = IsAnalysisRunning();
GUI.enabled = !isAnalysisRunning;
if (GUILayout.Button("Load", buttonStyle, GUILayout.ExpandWidth(false), GUILayout.Width(50)))
Load();
GUI.enabled = !isAnalysisRunning && (m_ProfileSingleView.IsDataValid());
if (GUILayout.Button("Save", buttonStyle, GUILayout.ExpandWidth(false), GUILayout.Width(50)))
Save(m_ProfileSingleView);
GUI.enabled = lastEnabled;
ShowFilename(m_ProfileSingleView.path);
EditorGUILayout.EndHorizontal();
}
bool IsSelectedMarkerValid()
{
bool valid = true;
if (m_ActiveTab == ActiveTab.Summary)
{
if (IsAnalysisValid())
{
valid = false;
List<MarkerData> markers = m_ProfileSingleView.analysis.GetMarkers();
if (markers != null)
{
int markerAt = m_SelectedMarker.id;
if (markerAt >= 0 && markerAt < markers.Count)
{
valid = true;
}
}
}
}
else if (m_ActiveTab == ActiveTab.Compare)
{
if (IsAnalysisValid())
{
valid = false;
List<MarkerData> leftMarkers = m_ProfileLeftView.analysis.GetMarkers();
List<MarkerData> rightMarkers = m_ProfileRightView.analysis.GetMarkers();
int pairingAt = m_SelectedPairing;
if (leftMarkers != null && rightMarkers != null && m_Pairings != null)
{
if (pairingAt >= 0 && pairingAt < m_Pairings.Count)
{
valid = true;
}
}
}
}
return valid;
}
void ShowSelectedMarker()
{
bool valid = IsSelectedMarkerValid();
if (valid)
{
DrawSelectedText(m_SelectedMarker.name);
}
else
{
var markerInThread = m_SelectedMarker.id == -1 && !string.IsNullOrEmpty(m_SelectedMarker.threadName);
var threadText = markerInThread ?
string.Format(" (Selected in: {0}{1}{2})",
m_SelectedMarker.threadGroupName,
string.IsNullOrEmpty(m_SelectedMarker.threadGroupName) ? "" : ".",
m_SelectedMarker.threadName) :
null;
string text = string.Format("{0}{1} not in selection", m_SelectedMarker.name, threadText);
GUIContent content = new GUIContent(text, text);
Vector2 size = GUI.skin.label.CalcSize(content);
Rect rect = EditorGUILayout.GetControlRect(GUILayout.MaxWidth(size.x), GUILayout.Height(size.y));
if (Event.current.type == EventType.Repaint)
{
GUI.Label(rect, content);
}
}
}
internal bool AllSelected()
{
if (m_ActiveTab == ActiveTab.Summary)
{
if (m_ProfileSingleView.AllSelected())
return true;
}
if (m_ActiveTab == ActiveTab.Compare)
{
if (m_ProfileLeftView.AllSelected() && m_ProfileRightView.AllSelected())
return true;
}
return false;
}
internal bool HasSelection()
{
if (m_ActiveTab == ActiveTab.Summary)
{
if (m_ProfileSingleView.HasSelection())
return true;
}
if (m_ActiveTab == ActiveTab.Compare)
{
if (m_ProfileLeftView.HasSelection())
return true;
if (m_ProfileRightView.HasSelection())
return true;
}
return false;
}
internal int GetRemappedUIFrameIndex(int frameIndex, ProfileDataView context)
{
if (context.inSyncWithProfilerData)
return RemapFrameIndex(frameIndex, context.data.FrameIndexOffset);
else
return k_ProfileDataDefaultDisplayOffset + context.data.DisplayFrameToOffset(frameIndex);
}
internal bool CanExportComparisonTable()
{
return m_ProfileLeftView != null && m_ProfileLeftView.IsDataValid() && m_ProfileRightView != null && m_ProfileRightView.IsDataValid() &&
m_ComparisonTable != null && m_ActiveTab == ActiveTab.Compare;
}
internal bool TryExportComparisonTable(StreamWriter writer)
{
if (m_ComparisonTable == null || m_ActiveTab != ActiveTab.Compare)
return false;
m_ComparisonTable.WriteTableContentsCSV(writer);
return true;
}
int GetRemappedUIFirstFrameOffset(ProfileDataView context)
{
if (context.inSyncWithProfilerData)
return RemapFrameIndex(context.data.OffsetToDisplayFrame(0), context.data.FrameIndexOffset);
else
return context.data.OffsetToDisplayFrame(0);
}
int GetRemappedUIFirstFrameDisplayOffset(ProfileDataView context)
{
if (context.inSyncWithProfilerData)
return RemapFrameIndex(context.data.OffsetToDisplayFrame(0), context.data.FrameIndexOffset);
else
return k_ProfileDataDefaultDisplayOffset;
}
static readonly ProfilerMarkerAbstracted m_DrawFrameTimeGraphProfilerMarker = new ProfilerMarkerAbstracted("ProfileAnalyzer.DrawFrameTimeGraph");
void DrawFrameTimeGraph(float height)
{
using (m_DrawFrameTimeGraphProfilerMarker.Auto())
{
GUI.SetNextControlName("FrameTimeGraph");
Rect rect = EditorGUILayout.GetControlRect(GUILayout.Height(height));
if (m_ProfileSingleView.IsDataValid())
{
if (!m_FrameTimeGraph.HasData())
m_FrameTimeGraph.SetData(GetFrameTimeData(m_ProfileSingleView.data));
if (!m_ProfileSingleView.HasValidSelection())
m_ProfileSingleView.SelectFullRange();
List<int> selectedOffsets = new List<int>();
foreach (int index in m_ProfileSingleView.selectedIndices)
{
selectedOffsets.Add(m_ProfileSingleView.data.DisplayFrameToOffset(index));
}
float yRange = m_FrameTimeGraph.GetDataRange();
int offsetToDisplayMapping = GetRemappedUIFirstFrameDisplayOffset(m_ProfileSingleView);
int offsetToIndexMapping = GetRemappedUIFirstFrameOffset(m_ProfileSingleView);
bool enabled = !IsAnalysisRunning();
m_FrameTimeGraph.SetEnabled(enabled);
bool valid = IsSelectedMarkerValid();
string validMarkerName = valid ? m_SelectedMarker.name : "";
m_FrameTimeGraph.Draw(rect, m_ProfileSingleView.analysis, selectedOffsets, yRange, offsetToDisplayMapping, offsetToIndexMapping,
validMarkerName, 0, m_ProfileSingleView.analysisFull);
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
ShowSelectedMarker();
EditorGUILayout.EndHorizontal();
}
else
{
GUI.Label(rect, Styles.dataMissing, m_StyleUpperLeft);
}
}
}
void DrawParentFilter()
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(Styles.parentMarker, GUILayout.Width(100));
if (!string.IsNullOrEmpty(m_ParentMarker))
{
bool lastEnabled = GUI.enabled;
bool enabled = !IsAnalysisRunning();
GUI.enabled = enabled;
if (GUILayout.Button("Clear", EditorStyles.miniButton, GUILayout.MaxWidth(LayoutSize.FilterOptionsEnumWidth)))
{
SetAsParentMarkerFilter("");
}
GUI.enabled = lastEnabled;
DrawSelectedText(m_ParentMarker);
}
else
{
EditorGUILayout.LabelField(Styles.selectParentMarker);
}
EditorGUILayout.EndHorizontal();
}
internal void SetThreadSelection(ThreadSelection threadSelection)
{
m_ThreadSelectionNew = new ThreadSelection(threadSelection);
UpdateActiveTab(true);
}
string CalculateSelectedThreadsSummary()
{
if (m_ThreadSelection.selection == null || m_ThreadSelection.selection.Count == 0)
return "None";
// Count all threads in a group
var threadDict = new Dictionary<string, int>();
var threadSelectionDict = new Dictionary<string, int>();
foreach (var threadNameWithIndex in m_ThreadNames)
{
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
if (threadIdentifier.index == ThreadIdentifier.kAll)
continue;
int count;
if (threadDict.TryGetValue(threadIdentifier.name, out count))
threadDict[threadIdentifier.name] = count + 1;
else
threadDict[threadIdentifier.name] = 1;
threadSelectionDict[threadIdentifier.name] = 0;
}
// Count all the threads we have 'selected' in a group
foreach (var threadNameWithIndex in m_ThreadSelection.selection)
{
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
if (threadDict.ContainsKey(threadIdentifier.name) &&
threadSelectionDict.ContainsKey(threadIdentifier.name) &&
threadIdentifier.index <= threadDict[threadIdentifier.name])
{
// Selected thread valid and in the thread list
// and also within the range of valid threads for this data set
threadSelectionDict[threadIdentifier.name]++;
}
}
// Count all thread groups where we have 'selected all the threads'
int threadsSelected = 0;
foreach (var threadName in threadDict.Keys)
{
if (threadSelectionDict[threadName] != threadDict[threadName])
continue;
threadsSelected++;
}
// If we've just added all the thread names we have everything selected
// Note we don't compare against the m_ThreadNames directly as this contains the 'all' versions
if (threadsSelected == threadDict.Keys.Count)
return "All";
// Add all the individual threads were we haven't already added the group
List<string> threads = new List<string>();
foreach (var threadName in threadSelectionDict.Keys)
{
int selectionCount = threadSelectionDict[threadName];
if (selectionCount <= 0)
continue;
int threadCount = threadDict[threadName];
if (threadCount == 1)
threads.Add(threadName);
else if (selectionCount != threadCount)
threads.Add(string.Format("{0} ({1} of {2})", threadName, selectionCount, threadCount));
else
threads.Add(string.Format("{0} (All)", threadName));
}
// Maintain alphabetical order
threads.Sort(CompareUINames);
if (threads.Count == 0)
return "None";
string threadsSelectedText = string.Join(", ", threads.ToArray());
return threadsSelectedText;
}
string GetSelectedThreadsSummary()
{
return m_ThreadSelectionSummary;
}
void DrawThreadFilter(ProfileData profileData)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(Styles.threadFilter, GUILayout.Width(LayoutSize.FilterOptionsLeftLabelWidth));
if (profileData != null)
{
if (m_ThreadNames.Count > 0)
{
bool lastEnabled = GUI.enabled;
bool enabled = !IsAnalysisRunning() && !ThreadSelectionWindow.IsOpen();
GUI.enabled = enabled;
if (GUILayout.Button(Styles.threadFilterSelect, GUILayout.Width(LayoutSize.FilterOptionsEnumWidth)))
{
Vector2 windowPosition = new Vector2(Event.current.mousePosition.x + LayoutSize.FilterOptionsEnumWidth, Event.current.mousePosition.y + GUI.skin.label.lineHeight);
Vector2 screenPosition = GUIUtility.GUIToScreenPoint(windowPosition);
ThreadSelectionWindow.Open(screenPosition.x, screenPosition.y, this, m_ThreadSelection, m_ThreadNames, m_ThreadUINames);
EditorGUIUtility.ExitGUI();
}
GUI.enabled = lastEnabled;
ShowSelectedThreads();
GUILayout.FlexibleSpace();
}
}
EditorGUILayout.EndHorizontal();
}
void DrawSelectedText(string text)
{
if (text == null)
return;
GUIStyle treeViewSelectionStyle = "TV Selection";
GUIStyle backgroundStyle = new GUIStyle(treeViewSelectionStyle);
GUIStyle treeViewLineStyle = "TV Line";
GUIStyle textStyle = new GUIStyle(treeViewLineStyle);
GUIContent content = new GUIContent(text, text);
Vector2 size = textStyle.CalcSize(content);
Rect rect = EditorGUILayout.GetControlRect(GUILayout.MaxWidth(size.x), GUILayout.Height(size.y));
if (Event.current.type == EventType.Repaint)
{
backgroundStyle.Draw(rect, false, false, true, true);
GUI.Label(rect, content, textStyle);
}
}
void ShowSelectedThreads()
{
string threadsSelected = GetSelectedThreadsSummary();
DrawSelectedText(threadsSelected);
}
void DrawUnitFilter()
{
EditorGUILayout.BeginHorizontal(GUILayout.Width(LayoutSize.FilterOptionsRightLabelWidth + LayoutSize.FilterOptionsRightEnumWidth));
EditorGUILayout.LabelField(Styles.unitFilter, m_StyleMiddleRight, GUILayout.Width(LayoutSize.FilterOptionsRightEnumWidth));
bool lastEnabled = GUI.enabled;
bool enabled = !IsAnalysisRunning();
GUI.enabled = enabled;
//Units units = (Units)EditorGUILayout.EnumPopup(m_DisplayUnits.Units, GUILayout.Width(LayoutSize.FilterOptionsRightEnumWidth));
Units units = (Units)EditorGUILayout.Popup((int)m_DisplayUnits.Units, m_UnitNames, GUILayout.Width(LayoutSize.FilterOptionsRightEnumWidth));
GUI.enabled = lastEnabled;
if (units != m_DisplayUnits.Units)
{
SetUnits(units);
m_FrameTimeGraph.SetUnits(m_DisplayUnits.Units);
m_LeftFrameTimeGraph.SetUnits(m_DisplayUnits.Units);
m_RightFrameTimeGraph.SetUnits(m_DisplayUnits.Units);
UpdateMarkerTable();
}
EditorGUILayout.EndHorizontal();
}
bool IsSelfTime()
{
return (m_TimingOption == TimingOptions.TimingOption.Self) ? true : false;
}
void DrawTimingFilter()
{
EditorGUILayout.BeginHorizontal(GUILayout.Width(LayoutSize.FilterOptionsRightLabelWidth + LayoutSize.FilterOptionsRightEnumWidth));
EditorGUILayout.LabelField(Styles.timingFilter, m_StyleMiddleRight, GUILayout.Width(LayoutSize.FilterOptionsRightLabelWidth));
bool lastEnabled = GUI.enabled;
bool enabled = !IsAnalysisRunning();
GUI.enabled = enabled;
var timingOption = (TimingOptions.TimingOption)EditorGUILayout.Popup((int)m_TimingOption, TimingOptions.TimingOptionNames, GUILayout.Width(LayoutSize.FilterOptionsRightEnumWidth));
GUI.enabled = lastEnabled;
if (timingOption != m_TimingOption)
{
m_TimingOption = timingOption;
UpdateActiveTab(true, true);
}
EditorGUILayout.EndHorizontal();
}
internal string GetDisplayUnits()
{
return m_DisplayUnits.Postfix();
}
internal string ToDisplayUnits(float ms, bool showUnits = false, int limitToDigits = 5, bool showFullValueWhenBelowZero = false)
{
return m_DisplayUnits.ToString(ms, showUnits, limitToDigits, showFullValueWhenBelowZero);
}
internal string ToDisplayUnits(double ms, bool showUnits = false, int limitToDigits = 5, bool showFullValueWhenBelowZero = false)
{
return m_DisplayUnits.ToString((float)ms, showUnits, limitToDigits, showFullValueWhenBelowZero);
}
internal string ToTooltipDisplayUnits(float ms, bool showUnits = false, int frameIndex = -1)
{
return m_DisplayUnits.ToTooltipString(ms, showUnits, frameIndex);
}
internal GUIContent ToDisplayUnitsWithTooltips(float ms, bool showUnits = false, int frameIndex = -1)
{
return m_DisplayUnits.ToGUIContentWithTooltips(ms, showUnits, 5, frameIndex);
}
void SetUnits(Units units)
{
m_DisplayUnits = new DisplayUnits(units);
}
void UpdateActiveTab(bool fullAnalysisRequired = false, bool markOtherDirty = true)
{
switch (m_ActiveTab)
{
case ActiveTab.Summary:
m_RequestAnalysis = true;
m_FullAnalysisRequired = fullAnalysisRequired;
break;
case ActiveTab.Compare:
m_RequestCompare = true;
m_FullCompareRequired = fullAnalysisRequired;
break;
}
if (markOtherDirty)
m_OtherTabDirty = true;
}
void UpdateMarkerTable(bool markOtherDirty = true)
{
switch (m_ActiveTab)
{
case ActiveTab.Summary:
if (m_ProfileTable != null)
m_ProfileTable.Reload();
break;
case ActiveTab.Compare:
if (m_ComparisonTable != null)
m_ComparisonTable.Reload();
break;
}
if (markOtherDirty)
m_OtherTableDirty = true;
}
void DrawNameFilter()
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(Styles.nameFilter, GUILayout.Width(LayoutSize.FilterOptionsLeftLabelWidth));
NameFilterOperation lastNameFilterOperation = m_NameFilterOperation;
bool lastEnabled = GUI.enabled;
bool enabled = !IsAnalysisRunning();
GUI.enabled = enabled;
m_NameFilterOperation = (NameFilterOperation)EditorGUILayout.Popup((int)m_NameFilterOperation, Styles.nameFilterOperation, GUILayout.MaxWidth(LayoutSize.FilterOptionsEnumWidth));
GUI.enabled = lastEnabled;
if (m_NameFilterOperation != lastNameFilterOperation)
{
m_MarkerFilter.Clear();
UpdateMarkerTable();
}
string lastFilter = m_NameFilter;
GUI.enabled = enabled;
GUI.SetNextControlName("NameFilter");
m_NameFilter = EditorGUILayout.TextField(m_NameFilter, GUILayout.MinWidth(200 - LayoutSize.FilterOptionsEnumWidth));
GUI.enabled = lastEnabled;
if (m_NameFilter != lastFilter)
{
m_MarkerFilter.Clear();
UpdateMarkerTable();
}
EditorGUILayout.LabelField(Styles.nameExclude, GUILayout.Width(LayoutSize.FilterOptionsLeftLabelWidth));
NameFilterOperation lastNameExcludeOperation = m_NameExcludeOperation;
GUI.enabled = enabled;
m_NameExcludeOperation = (NameFilterOperation)EditorGUILayout.Popup((int)m_NameExcludeOperation, Styles.nameFilterOperation, GUILayout.MaxWidth(LayoutSize.FilterOptionsEnumWidth));
GUI.enabled = lastEnabled;
if (m_NameExcludeOperation != lastNameExcludeOperation)
{
m_MarkerFilter.Clear();
UpdateMarkerTable();
}
string lastExclude = m_NameExclude;
GUI.enabled = enabled;
GUI.SetNextControlName("ExcludeFilter");
m_NameExclude = EditorGUILayout.TextField(m_NameExclude, GUILayout.MinWidth(200 - LayoutSize.FilterOptionsEnumWidth));
GUI.enabled = lastEnabled;
if (m_NameExclude != lastExclude)
{
m_MarkerFilter.Clear();
UpdateMarkerTable();
}
EditorGUILayout.EndHorizontal();
}
internal void SetMode(MarkerColumnFilter.Mode newMode)
{
m_SingleModeFilter.mode = newMode;
m_CompareModeFilter.mode = newMode;
if (m_ProfileTable != null)
m_ProfileTable.SetMode(m_SingleModeFilter);
if (m_ComparisonTable != null)
m_ComparisonTable.SetMode(m_CompareModeFilter);
}
internal void SetSingleModeColumns(int[] visibleColumns)
{
// If selecting the columns manually then override the currently stored selection with the current
m_ProfileMulticolumnHeaderState.visibleColumns = visibleColumns;
m_SingleModeFilter.mode = MarkerColumnFilter.Mode.Custom;
m_SingleModeFilter.visibleColumns = visibleColumns;
}
internal void SetComparisonModeColumns(int[] visibleColumns)
{
// If selecting the columns manually then override the currently stored selection with the current
m_ComparisonMulticolumnHeaderState.visibleColumns = visibleColumns;
m_CompareModeFilter.mode = MarkerColumnFilter.Mode.Custom;
m_CompareModeFilter.visibleColumns = visibleColumns;
}
void DrawMarkerColumnFilter()
{
EditorGUILayout.BeginHorizontal(GUILayout.Width(LayoutSize.FilterOptionsRightLabelWidth + LayoutSize.FilterOptionsRightEnumWidth));
EditorGUILayout.LabelField(Styles.markerColumns, m_StyleMiddleRight, GUILayout.Width(LayoutSize.FilterOptionsRightLabelWidth));
bool lastEnabled = GUI.enabled;
bool enabled = !IsAnalysisRunning();
GUI.enabled = enabled;
var filterMode = m_ActiveTab == ActiveTab.Summary ? m_SingleModeFilter : m_CompareModeFilter;
var oldMode = filterMode.mode;
filterMode.mode = (MarkerColumnFilter.Mode)EditorGUILayout.IntPopup((int)filterMode.mode, MarkerColumnFilter.ModeNames, MarkerColumnFilter.ModeValues, GUILayout.Width(LayoutSize.FilterOptionsRightEnumWidth));
if (filterMode.mode != oldMode)
{
if (m_ActiveTab == ActiveTab.Summary && m_ProfileTable != null)
m_ProfileTable.SetMode(filterMode);
else if (m_ActiveTab == ActiveTab.Compare && m_ComparisonTable != null)
m_ComparisonTable.SetMode(filterMode);
}
GUI.enabled = lastEnabled;
EditorGUILayout.EndHorizontal();
}
enum InDataSet
{
Left,
Both,
Right
};
int GetCombinedThreadCount(out int matchingCount, out int uniqueLeft, out int uniqueRight)
{
var threads = new Dictionary<string, InDataSet>();
foreach (var threadName in m_ProfileLeftView.data.GetThreadNames())
{
threads[threadName] = InDataSet.Left;
}
foreach (var threadName in m_ProfileRightView.data.GetThreadNames())
{
if (threads.ContainsKey(threadName))
threads[threadName] = InDataSet.Both;
else
threads[threadName] = InDataSet.Right;
}
matchingCount = 0;
uniqueLeft = 0;
uniqueRight = 0;
int total = 0;
foreach (var thread in threads)
{
switch (thread.Value)
{
case InDataSet.Left:
uniqueLeft++;
break;
case InDataSet.Both:
matchingCount++;
break;
case InDataSet.Right:
uniqueRight++;
break;
}
total++;
}
return total;
}
void DrawMarkerCount()
{
if (!IsAnalysisValid())
return;
if (m_ActiveTab == ActiveTab.Summary)
{
int markersCount = m_ProfileSingleView.analysis.GetFrameSummary().totalMarkers;
int filteredMarkersCount = (m_ProfileTable != null) ? m_ProfileTable.GetRows().Count : 0;
var content = new GUIContent(
String.Format("{0} of {1} markers", filteredMarkersCount, markersCount),
"Number of markers in the filtered set, compared to the total in the data set");
Vector2 size = GUI.skin.label.CalcSize(content);
Rect rect = EditorGUILayout.GetControlRect(GUILayout.Width(size.x), GUILayout.Height(size.y));
EditorGUI.LabelField(rect, content);
}
if (m_ActiveTab == ActiveTab.Compare)
{
int markersCount = m_TotalCombinedMarkerCount;
int filteredMarkersCount = (m_ComparisonTable != null) ? m_ComparisonTable.GetRows().Count : 0;
var content = new GUIContent(
String.Format("{0} of {1} markers", filteredMarkersCount, markersCount),
"Number of markers in the filtered set, compared to total unique markers in the combined data sets");
Vector2 size = GUI.skin.label.CalcSize(content);
Rect rect = EditorGUILayout.GetControlRect(GUILayout.Width(size.x), GUILayout.Height(size.y));
EditorGUI.LabelField(rect, content);
}
}
string GetThreadCountToolTipUnion(int allThreadsCount, int matchingCount)
{
return String.Format(
"Total\n{0} Union : Combined over both data sets\n{1} Intersection : Matching in both data sets",
allThreadsCount,
matchingCount
);
}
string GetThreadCountToolTipDifference(int allThreadsCount, int matchingCount, int uniqueLeft, int uniqueRight)
{
return String.Format(
"Difference\n{0}\n{1} Unique to left\n{2} Unique to right",
allThreadsCount - matchingCount,
uniqueLeft,
uniqueRight);
}
string GetThreadCountToolTip(int allThreadsCount, int matchingCount, int uniqueLeft, int uniqueRight)
{
return String.Format(
"{0}\n\n{1}",
GetThreadCountToolTipUnion(allThreadsCount, matchingCount),
GetThreadCountToolTipDifference(allThreadsCount, matchingCount, uniqueLeft, uniqueRight)
);
}
void DrawThreadCount()
{
if (!IsAnalysisValid())
return;
if (m_ActiveTab == ActiveTab.Summary)
{
int allThreadsCount = m_ProfileSingleView.data.GetThreadNames().Count;
List<string> threadSelection = GetLimitedThreadSelection(m_ThreadNames, m_ThreadSelection);
int selectedThreads = threadSelection.Count;
var content = new GUIContent(
String.Format("{0} of {1} threads", selectedThreads, allThreadsCount),
"Number of threads in the filtered set, compared to the total in the data set");
Vector2 size = GUI.skin.label.CalcSize(content);
Rect rect = EditorGUILayout.GetControlRect(GUILayout.Width(size.x), GUILayout.Height(size.y));
EditorGUI.LabelField(rect, content);
}
if (m_ActiveTab == ActiveTab.Compare)
{
int matchingCount, uniqueLeft, uniqueRight;
int allThreadsCount = GetCombinedThreadCount(out matchingCount, out uniqueLeft, out uniqueRight);
List<string> threadSelection = GetLimitedThreadSelection(m_ThreadNames, m_ThreadSelection);
int selectedThreads = threadSelection.Count;
string partialTooltip = GetThreadCountToolTip(allThreadsCount, matchingCount, uniqueLeft, uniqueRight);
var content = new GUIContent(
String.Format("{0} of {1} threads", selectedThreads, allThreadsCount),
String.Format("Number of threads in the filtered set, compared to total unique threads in the combined data sets\n\n{0}", partialTooltip)
);
Vector2 size = GUI.skin.label.CalcSize(content);
Rect rect = EditorGUILayout.GetControlRect(GUILayout.Width(size.x), GUILayout.Height(size.y));
EditorGUI.LabelField(rect, content);
}
}
static readonly ProfilerMarkerAbstracted m_DrawAnalysisOptionsProfilerMarker = new ProfilerMarkerAbstracted("ProfileAnalyzer.DrawAnalysisOptions");
void UpdateRemoveMarkerDisplay()
{
UpdateActiveTab(true);
// Force an update
m_FrameTimeGraph.ClearData();
m_LeftFrameTimeGraph.ClearData();
m_RightFrameTimeGraph.ClearData();
}
void BuildRemoveMarkerList()
{
var entries = new List<GUIContent>();
var values = new List<int>();
m_removeMarkerSomeMissing = false;
foreach (RemoveMarkerOperation filterOperation in Enum.GetValues(typeof(RemoveMarkerOperation)))
{
int value = (int)filterOperation;
GUIContent entry = new GUIContent(Styles.removeMarkerOperation[value]);
bool found = true;
switch(filterOperation)
{
case RemoveMarkerOperation.HideWaitForFPS:
{
switch (m_ActiveTab)
{
case ActiveTab.Summary:
if (!m_ProfileSingleView.containsWaitForFPS)
found = false;
break;
case ActiveTab.Compare:
if (!m_ProfileLeftView.containsWaitForFPS && !m_ProfileRightView.containsWaitForFPS)
found = false;
break;
}
}
break;
case RemoveMarkerOperation.HideWaitForPresent:
{
switch (m_ActiveTab)
{
case ActiveTab.Summary:
if (!m_ProfileSingleView.containsWaitForPresent)
found = false;
break;
case ActiveTab.Compare:
if (!m_ProfileLeftView.containsWaitForPresent && !m_ProfileRightView.containsWaitForPresent)
found = false;
break;
}
}
break;
}
if (!found)
{
entry.text += " (not in capture)";
m_removeMarkerSomeMissing = true;
}
entries.Add(entry);
values.Add(value);
}
m_removeMarkerDisplay = entries.ToArray();
m_removeMarkerValues = values.ToArray();
}
void DrawRemoveMarker()
{
bool update = false;
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(Styles.removeMarker, GUILayout.Width(LayoutSize.FilterOptionsLeftLabelWidth));
bool lastEnabled = GUI.enabled;
bool enabled = !IsAnalysisRunning();
GUI.enabled = enabled;
if (m_removeMarkerDisplay == null || m_removeMarkerValues == null)
BuildRemoveMarkerList();
// Hack to make popup wider if some markers missing, and have extra text to indicate that
int width = (m_removeMarkerSomeMissing ? LayoutSize.RemoveMarkerMissingOptionsEnumWidth : LayoutSize.RemoveMarkerOptionsEnumWidth);
RemoveMarkerOperation removeMarkerOperation = (RemoveMarkerOperation)EditorGUILayout.IntPopup((int)m_removeMarkerOperation, m_removeMarkerDisplay, m_removeMarkerValues, GUILayout.Width(width));
if (removeMarkerOperation != m_removeMarkerOperation)
update = true;
if (m_removeMarkerOperation != RemoveMarkerOperation.ShowAll)
{
bool hide = EditorGUILayout.Toggle(Styles.hideRemoveMarkers, m_hideRemovedMarkers, GUILayout.ExpandWidth(false));
if (hide != m_hideRemovedMarkers)
{
m_hideRemovedMarkers = hide;
update = true;
}
}
switch (m_removeMarkerOperation)
{
case RemoveMarkerOperation.ShowAll:
EditorGUILayout.LabelField("");
break;
/*
// Could make custom marker editable, but we don't support multiple markers yet
// So currently we limit this to being set by right click context menu
case removeMarkerOperation.Custom:
string removeMarkerCustomRemoveMarker = EditorGUILayout.DelayedTextField(m_removeMarkerCustomRemoveMarker, GUILayout.MinWidth(200 - LayoutSize.removeMarkerOptionsEnumWidth));
if (removeMarkerCustomRemoveMarker != m_removeMarkerCustomRemoveMarker)
{
m_removeMarkerCustomRemoveMarker = removeMarkerCustomRemoveMarker;
update = true;
}
break;
*/
default:
EditorGUILayout.LabelField(GetRemoveMarker());
break;
}
GUI.enabled = lastEnabled;
EditorGUILayout.EndHorizontal();
if (update)
{
m_removeMarkerOperation = removeMarkerOperation;
UpdateRemoveMarkerDisplay();
}
}
internal void SetAsRemoveMarker(string markerName)
{
if (markerName == "")
{
if (m_removeMarkerOperation != RemoveMarkerOperation.ShowAll)
{
m_removeMarkerOperation = RemoveMarkerOperation.ShowAll;
UpdateRemoveMarkerDisplay();
}
}
else
{
m_removeMarkerCustomRemoveMarker = markerName;
m_removeMarkerOperation = RemoveMarkerOperation.Custom;
UpdateRemoveMarkerDisplay();
}
}
string GetRemoveMarker()
{
switch (m_removeMarkerOperation)
{
default:
case RemoveMarkerOperation.ShowAll:
return null;
case RemoveMarkerOperation.HideWaitForFPS:
// Gfx.WaitForPresentOnGfxThread is not always present on Android
// E.g. when Application.targetFrameRate locks the frame rate
// So we support removing WaitForTargetFPS
// Often WaitForTargetFPS will be negligable on console and Gfx.WaitForPresentOnGfxThread is key
return "WaitForTargetFPS";
case RemoveMarkerOperation.HideWaitForPresent:
// Gfx.WaitForPresentOnGfxThread is seen on consoles
return "Gfx.WaitForPresentOnGfxThread";
case RemoveMarkerOperation.Custom:
if (m_removeMarkerCustomRemoveMarker == null || m_removeMarkerCustomRemoveMarker=="")
m_removeMarkerCustomRemoveMarker = "WaitForTargetFPS";
return m_removeMarkerCustomRemoveMarker;
}
}
void DrawAnalysisOptions()
{
using (m_DrawAnalysisOptionsProfilerMarker.Auto())
{
EditorGUILayout.BeginVertical(GUI.skin.box);
bool lastShowFilters = m_ShowFilters;
m_ShowFilters = BoldFoldout(m_ShowFilters, Styles.filters);
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
if (m_ShowFilters)
{
DrawRemoveMarker();
DrawNameFilter();
EditorGUILayout.BeginHorizontal();
DrawThreadFilter(m_ProfileSingleView.data);
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
m_DepthSliceUI.DrawDepthFilter(IsAnalysisRunning(), m_ActiveTab == ActiveTab.Summary,
m_ProfileSingleView, m_ProfileLeftView, m_ProfileRightView);
DrawTimingFilter();
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
DrawParentFilter();
DrawUnitFilter();
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
bool lastEnabled = GUI.enabled;
GUI.enabled = !IsAnalysisRunning();
if (GUILayout.Button(new GUIContent("Analyze", m_LastAnalysisTime), GUILayout.Width(100)))
m_RequestAnalysis = true;
GUI.enabled = lastEnabled;
DrawMarkerCount();
EditorGUILayout.LabelField(",", GUILayout.Width(10), GUILayout.ExpandWidth(false));
DrawThreadCount();
GUILayout.FlexibleSpace();
DrawMarkerColumnFilter();
EditorGUILayout.EndHorizontal();
}
if (m_ShowFilters != lastShowFilters)
{
ProfileAnalyzerAnalytics.SendUIVisibilityEvent(ProfileAnalyzerAnalytics.UIVisibility.Filters,
analytic.GetDurationInSeconds(), m_ShowFilters);
}
EditorGUILayout.EndVertical();
}
}
internal bool IsAnalysisRunning()
{
return m_ThreadActivity != ThreadActivity.None;
}
internal bool IsLoading()
{
return m_ThreadActivity == ThreadActivity.Load;
}
float GetProgress()
{
// We return the value from the update loop so the data doesn't change over the time onGui is called for layout and repaint
// m_ThreadProgress ranges from 0 to 100.
return m_ThreadProgress * 0.01f;
}
bool IsAnalysisValid(ProfileDataView view, bool checkFrameCount = false)
{
if (!view.IsDataValid())
return false;
if (view.analysis == null)
return false;
if (checkFrameCount)
{
if (view.analysis.GetFrameSummary().frames.Count <= 0)
return false;
}
return true;
}
bool IsAnalysisValid(bool checkFrameCount = false)
{
switch (m_ActiveTab)
{
case ActiveTab.Summary:
if (!IsAnalysisValid(m_ProfileSingleView, checkFrameCount))
return false;
break;
case ActiveTab.Compare:
if (!IsAnalysisValid(m_ProfileLeftView, checkFrameCount))
return false;
if (!IsAnalysisValid(m_ProfileRightView, checkFrameCount))
return false;
break;
}
//if (IsAnalysisRunning())
// return false;
return true;
}
void DrawProgress(Rect rect)
{
if (IsAnalysisRunning())
{
EditorGUILayout.BeginHorizontal(GUILayout.MaxWidth(140));
float x = 0;
float y = 0;
float width = rect.width;
float height = k_ProgressBarHeight;
if (m_2D.DrawStart(width, height, Draw2D.Origin.TopLeft))
{
float barLength = width * GetProgress();
m_2D.DrawFilledBox(x, y, barLength, height, UIColor.white);
m_2D.DrawEnd();
}
EditorGUILayout.EndHorizontal();
}
else
{
EditorGUILayout.BeginVertical();
GUILayout.Space(k_ProgressBarHeight);
EditorGUILayout.EndVertical();
}
}
void DrawPullButton(Color color, ProfileDataView view, FrameTimeGraph frameTimeGraph)
{
bool lastEnabled = GUI.enabled;
GUI.enabled = !IsAnalysisRunning();
GUIContent content;
if (!IsProfilerWindowOpen())
{
content = Styles.pullOpen;
GUI.enabled = false;
}
else if (m_ProfilerFirstFrameIndex == 0 && m_ProfilerLastFrameIndex == 0)
{
content = Styles.pullRange;
GUI.enabled = false;
}
/*
// Commented out so we can capture even if recording
else if (m_ProfilerWindowInterface.IsRecording())
{
content = Styles.pullRecording;
GUI.enabled = false;
}
*/
else
{
content = Styles.pull;
}
Color oldColor = GUI.backgroundColor;
GUI.backgroundColor = color;
bool pull = GUILayout.Button(content, GUILayout.Width(100));
GUI.backgroundColor = oldColor;
if (pull)
{
PullFromProfiler(m_ProfilerFirstFrameIndex, m_ProfilerLastFrameIndex, view, frameTimeGraph);
UpdateActiveTab(true, false);
}
GUI.enabled = lastEnabled;
}
static readonly ProfilerMarkerAbstracted m_DrawFilesLoadedProfilerMarker = new ProfilerMarkerAbstracted("ProfileAnalyzer.DrawFilesLoaded");
void DrawFilesLoaded()
{
using (m_DrawFilesLoadedProfilerMarker.Auto())
{
var boxStyle = GUI.skin.box;
var rect = EditorGUILayout.BeginVertical(boxStyle);
if (m_ActiveTab == ActiveTab.Summary)
{
EditorGUILayout.BeginHorizontal(GUILayout.Height(100 + GUI.skin.label.lineHeight +
(2 * (GUI.skin.label.margin.vertical +
GUI.skin.label.padding.vertical))));
float filenameWidth = GetFilenameWidth(m_ProfileSingleView.path);
filenameWidth = Math.Min(filenameWidth, 200);
EditorGUILayout.BeginVertical(GUILayout.MaxWidth(100 + filenameWidth),
GUILayout.ExpandWidth(false));
DrawPullButton(GUI.backgroundColor, m_ProfileSingleView, m_FrameTimeGraph);
DrawLoadSave();
EditorGUILayout.EndVertical();
EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true));
DrawFrameTimeGraph(100);
EditorGUILayout.EndVertical();
EditorGUILayout.EndHorizontal();
}
if (m_ActiveTab == ActiveTab.Compare)
{
DrawComparisonLoadSave();
}
rect.width -= boxStyle.margin.right;
DrawProgress(rect);
EditorGUILayout.EndVertical();
}
}
void ShowHelp()
{
EditorGUILayout.BeginVertical(GUI.skin.box);
m_HelpScroll = EditorGUILayout.BeginScrollView(m_HelpScroll, GUILayout.ExpandHeight(true));
GUILayout.TextArea(Styles.helpText);
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
}
static readonly ProfilerMarkerAbstracted m_DrawAnalysisProfilerMarker = new ProfilerMarkerAbstracted("ProfileAnalyzer.DrawAnalysis");
static readonly ProfilerMarkerAbstracted m_TopNMarkersProfilerMarker = new ProfilerMarkerAbstracted("ProfileAnalyzer.TopNMarkers");
static readonly ProfilerMarkerAbstracted m_DrawMarkerTableProfilerMarker = new ProfilerMarkerAbstracted("ProfileAnalyzer.DrawMarkerTable");
void DrawAnalysis()
{
using (m_DrawAnalysisProfilerMarker.Auto())
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.BeginVertical();
DrawFilesLoaded();
if (m_ProfileSingleView.IsDataValid() && m_ProfileSingleView.data.GetFrameCount() > 0)
{
DrawAnalysisOptions();
if (IsAnalysisValid())
{
EditorGUILayout.BeginVertical(GUI.skin.box);
string title = string.Format("Top {0} markers on median frame", m_TopNBars);
GUIContent markersTitle = new GUIContent(title, Styles.topMarkersTooltip);
bool lastShowTopMarkers = m_ShowTopNMarkers;
m_ShowTopNMarkers = BoldFoldout(m_ShowTopNMarkers, markersTitle);
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
if (m_ShowTopNMarkers)
{
using (m_TopNMarkersProfilerMarker.Auto())
{
m_TopMarkers.SetData(m_ProfileSingleView, m_DepthSliceUI.depthFilter, GetNameFilters(),
GetNameExcludes(), m_TimingOption, m_ThreadSelection.selection.Count, m_hideRemovedMarkers);
EditorGUILayout.BeginVertical(GUILayout.Height(20));
EditorGUILayout.BeginHorizontal();
FrameSummary frameSummary = m_ProfileSingleView.analysis.GetFrameSummary();
if (frameSummary.count > 0)
DrawFrameIndexButton(frameSummary.medianFrameIndex, m_ProfileSingleView);
else
GUILayout.Label("", GUILayout.MinWidth(50));
Rect rect = EditorGUILayout.GetControlRect(GUILayout.ExpandWidth(true),
GUILayout.ExpandHeight(true));
float range = m_TopMarkers.GetTopMarkerTimeRange();
m_TopMarkers.Draw(rect, UIColor.bar, m_TopNBars, range, UIColor.barBackground,
Color.black, Color.white, true, true);
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
EditorGUILayout.BeginVertical(GUILayout.Height(20));
GUILayout.Label(m_DepthSliceUI.GetUIInfo(false));
EditorGUILayout.EndVertical();
}
}
if (m_ShowTopNMarkers != lastShowTopMarkers)
{
ProfileAnalyzerAnalytics.SendUIVisibilityEvent(ProfileAnalyzerAnalytics.UIVisibility.TopTen,
analytic.GetDurationInSeconds(), m_ShowTopNMarkers);
}
EditorGUILayout.EndVertical();
if (m_ProfileTable != null)
{
m_ShowMarkerTable = BoldFoldout(m_ShowMarkerTable, Styles.profileTable);
if (m_ShowMarkerTable)
{
using (m_DrawMarkerTableProfilerMarker.Auto())
{
Rect r = EditorGUILayout.GetControlRect(GUILayout.ExpandHeight(true));
float scrollBarWidth = GUI.skin.verticalScrollbar.fixedWidth +
GUI.skin.verticalScrollbar.border.horizontal +
GUI.skin.verticalScrollbar.margin.horizontal +
GUI.skin.verticalScrollbar.padding.horizontal;
scrollBarWidth += LayoutSize.ScrollBarPadding;
//offset vertically to get correct clipping behaviour
Rect clipRect = new Rect(r.x, m_ProfileTable.state.scrollPos.y,
r.width - scrollBarWidth,
r.height -
(m_ProfileTable.multiColumnHeader.height + GUI.skin.box.padding.top) -
(m_ProfileTable.ShowingHorizontalScroll
? (scrollBarWidth - LayoutSize.ScrollBarPadding)
: 0));
m_2D.SetClipRect(clipRect);
m_ProfileTable.OnGUI(r);
m_2D.ClearClipRect();
}
}
}
}
}
else
{
ShowHelp();
}
EditorGUILayout.EndVertical();
EditorGUILayout.BeginVertical(GUILayout.Width(LayoutSize.WidthRHS));
GUILayout.Space(4);
DrawFrameSummary();
DrawThreadSummary();
DrawSelected();
EditorGUILayout.EndVertical();
EditorGUILayout.EndHorizontal();
}
}
void SetRange(List<int> selectedOffsets, int clickCount, FrameTimeGraph.State inputStatus, ProfileDataView mainData, List<int> selectedIndices)
{
if (inputStatus == FrameTimeGraph.State.Dragging)
return;
var data = mainData.data;
if (clickCount == 2)
{
if (mainData.inSyncWithProfilerData)
{
int index = data.OffsetToDisplayFrame(selectedOffsets[0]);
JumpToFrame(index, mainData.data, false);
}
}
else
{
selectedIndices.Clear();
foreach (int offset in selectedOffsets)
{
selectedIndices.Add(data.OffsetToDisplayFrame(offset));
}
// Keep indices sorted
selectedIndices.Sort();
m_RequestCompare = true;
}
}
void SetLeftRange(List<int> selectedOffsets, int clickCount, FrameTimeGraph.State inputStatus)
{
SetRange(selectedOffsets, clickCount, inputStatus, m_ProfileLeftView, m_ProfileLeftView.selectedIndices);
}
void SetRightRange(List<int> selectedOffsets, int clickCount, FrameTimeGraph.State inputStatus)
{
SetRange(selectedOffsets, clickCount, inputStatus, m_ProfileRightView, m_ProfileRightView.selectedIndices);
}
void DrawComparisonLoadSaveButton(Color color, ProfileDataView view, FrameTimeGraph frameTimeGraph, ActiveView activeView)
{
bool lastEnabled = GUI.enabled;
bool isAnalysisRunning = IsAnalysisRunning();
EditorGUILayout.BeginHorizontal(GUILayout.MaxWidth(300), GUILayout.ExpandWidth(false));
GUIStyle buttonStyle = GUI.skin.button;
Color oldColor = GUI.backgroundColor;
GUI.backgroundColor = color;
GUI.enabled = !isAnalysisRunning;
bool load = GUILayout.Button("Load", buttonStyle, GUILayout.ExpandWidth(false), GUILayout.Width(50));
GUI.enabled = lastEnabled;
GUI.backgroundColor = oldColor;
if (load)
{
m_Path = EditorUtility.OpenFilePanel("Load profile analyzer data file", "", "pdata");
if (m_Path.Length != 0)
{
m_ActiveLoadingView = activeView;
BeginAsyncAction(ThreadActivity.Load);
}
GUIUtility.ExitGUI();
}
GUI.backgroundColor = color;
GUI.enabled = !isAnalysisRunning && view.IsDataValid();
bool save = GUILayout.Button("Save", buttonStyle, GUILayout.ExpandWidth(false), GUILayout.Width(50));
GUI.enabled = lastEnabled;
GUI.backgroundColor = oldColor;
if (save)
{
Save(view, true);
}
ShowFilename(view.path);
EditorGUILayout.EndHorizontal();
}
float GetComparisonYRange()
{
float yRangeLeft = m_ProfileLeftView.IsDataValid() ? m_LeftFrameTimeGraph.GetDataRange() : 0f;
float yRangeRight = m_ProfileRightView.IsDataValid() ? m_RightFrameTimeGraph.GetDataRange() : 0f;
float yRange = Math.Max(yRangeLeft, yRangeRight);
return yRange;
}
void SetFrameTimeGraphPairing(bool paired)
{
if (paired != m_FrameTimeGraphsPaired)
{
m_FrameTimeGraphsPaired = paired;
m_LeftFrameTimeGraph.PairWith(m_FrameTimeGraphsPaired ? m_RightFrameTimeGraph : null);
}
}
void DrawComparisonLoadSave()
{
int leftFrames = m_ProfileLeftView.IsDataValid() ? m_ProfileLeftView.data.GetFrameCount() : 0;
int rightFrames = m_ProfileRightView.IsDataValid() ? m_ProfileRightView.data.GetFrameCount() : 0;
int maxFrames = Math.Max(leftFrames, rightFrames);
float yRange = GetComparisonYRange();
EditorGUILayout.BeginHorizontal(GUILayout.Height(100 + GUI.skin.label.lineHeight + (2 * (GUI.skin.label.margin.vertical + GUI.skin.label.padding.vertical))));
float leftFilenameWidth = GetFilenameWidth(m_ProfileLeftView.path);
float rightFilenameWidth = GetFilenameWidth(m_ProfileRightView.path);
float filenameWidth = Math.Max(leftFilenameWidth, rightFilenameWidth);
filenameWidth = Math.Min(filenameWidth, 200);
EditorGUILayout.BeginVertical(GUILayout.MaxWidth(100 + filenameWidth), GUILayout.ExpandWidth(false));
DrawPullButton(UIColor.left, m_ProfileLeftView, m_LeftFrameTimeGraph);
DrawComparisonLoadSaveButton(UIColor.left, m_ProfileLeftView, m_LeftFrameTimeGraph, ActiveView.Left);
DrawPullButton(UIColor.right, m_ProfileRightView, m_RightFrameTimeGraph);
DrawComparisonLoadSaveButton(UIColor.right, m_ProfileRightView, m_RightFrameTimeGraph, ActiveView.Right);
EditorGUILayout.EndVertical();
bool lastEnabled = GUI.enabled;
bool enabled = !IsAnalysisRunning();
EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true));
bool valid = IsSelectedMarkerValid();
string validMarkerName = valid ? m_SelectedMarker.name : "";
GUI.SetNextControlName("LeftFrameTimeGraph");
Rect rect = EditorGUILayout.GetControlRect(GUILayout.Height(50));
if (m_ProfileLeftView.IsDataValid())
{
if (!m_LeftFrameTimeGraph.HasData())
m_LeftFrameTimeGraph.SetData(GetFrameTimeData(m_ProfileLeftView.data));
if (!m_ProfileLeftView.HasValidSelection())
m_ProfileLeftView.SelectFullRange();
List<int> selectedOffsets = new List<int>();
foreach (int index in m_ProfileLeftView.selectedIndices)
{
selectedOffsets.Add(m_ProfileLeftView.data.DisplayFrameToOffset(index));
}
int offsetToDisplayMapping = GetRemappedUIFirstFrameDisplayOffset(m_ProfileLeftView);
int offsetToIndexMapping = GetRemappedUIFirstFrameOffset(m_ProfileLeftView);
m_LeftFrameTimeGraph.SetEnabled(enabled);
m_LeftFrameTimeGraph.Draw(rect, m_ProfileLeftView.analysis, selectedOffsets, yRange, offsetToDisplayMapping, offsetToIndexMapping, validMarkerName, maxFrames, m_ProfileLeftView.analysisFull);
}
else
{
GUI.Label(rect, Styles.comparisonDataMissing, m_StyleUpperLeft);
}
GUI.SetNextControlName("RightFrameTimeGraph");
rect = EditorGUILayout.GetControlRect(GUILayout.Height(50));
if (m_ProfileRightView.IsDataValid())
{
if (!m_RightFrameTimeGraph.HasData())
m_RightFrameTimeGraph.SetData(GetFrameTimeData(m_ProfileRightView.data));
if (!m_ProfileRightView.HasValidSelection())
m_ProfileRightView.SelectFullRange();
List<int> selectedOffsets = new List<int>();
foreach (int index in m_ProfileRightView.selectedIndices)
{
selectedOffsets.Add(m_ProfileRightView.data.DisplayFrameToOffset(index));
}
int offsetToDisplayMapping = GetRemappedUIFirstFrameDisplayOffset(m_ProfileRightView);
int offsetToIndexMapping = GetRemappedUIFirstFrameOffset(m_ProfileRightView);
m_RightFrameTimeGraph.SetEnabled(enabled);
m_RightFrameTimeGraph.Draw(rect, m_ProfileRightView.analysis, selectedOffsets, yRange, offsetToDisplayMapping, offsetToIndexMapping, validMarkerName, maxFrames, m_ProfileRightView.analysisFull);
}
else
{
GUI.Label(rect, Styles.comparisonDataMissing, m_StyleUpperLeft);
}
EditorGUILayout.BeginHorizontal();
if (m_ProfileLeftView.IsDataValid() && m_ProfileRightView.IsDataValid() && m_ProfileLeftView.data.GetFrameCount() > 0 && m_ProfileRightView.data.GetFrameCount() > 0)
{
GUIStyle lockButtonStyle = "IN LockButton";
GUIStyle style = new GUIStyle(lockButtonStyle);
style.padding.left = 20;
//bool paired = GUILayout.Toggle(m_frameTimeGraphsPaired, Styles.graphPairing, style);
GUI.enabled = enabled;
bool paired = EditorGUILayout.ToggleLeft(Styles.graphPairing, m_FrameTimeGraphsPaired, style, GUILayout.MaxWidth(200));
GUI.enabled = lastEnabled;
SetFrameTimeGraphPairing(paired);
GUILayout.FlexibleSpace();
ShowSelectedMarker();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
EditorGUILayout.EndHorizontal();
}
void DrawComparisonHistogram(float height, float minValue, float maxValue, int bucketCount, int[] leftBuckets, int[] rightBuckets, int leftCount, int rightCount, bool leftValid, bool rightValid, DisplayUnits displayUnits)
{
Histogram histogram = new Histogram(m_2D, displayUnits.Units);
float width = LayoutSize.HistogramWidth;
float min = minValue;
float max = maxValue;
float spacing = 2;
float range = max - min;
// bucketCount = (range == 0f) ? 1 : bucketCount;
float x = (spacing / 2);
float y = 0;
float w = ((width + spacing) / bucketCount) - spacing;
float h = height;
histogram.DrawStart(width);
if (m_2D.DrawStart(width, height, Draw2D.Origin.BottomLeft))
{
float bucketWidth = (range / bucketCount);
Rect rect = GUILayoutUtility.GetLastRect();
histogram.DrawBackground(width, height, bucketCount, min, max, spacing);
if (!IsAnalysisRunning())
{
for (int bucketAt = 0; bucketAt < bucketCount; bucketAt++)
{
float leftBarCount = leftValid ? leftBuckets[bucketAt] : 0;
float rightBarCount = rightValid ? rightBuckets[bucketAt] : 0;
float leftBarHeight = leftValid ? ((h * leftBarCount) / leftCount) : 0;
float rightBarHeight = rightValid ? ((h * rightBarCount) / rightCount) : 0;
if (leftBarCount > 0) // Make sure we always slow a small bar if non zero
leftBarHeight = Mathf.Max(1.0f, leftBarHeight);
if (rightBarCount > 0) // Make sure we always slow a small bar if non zero
rightBarHeight = Mathf.Max(1.0f, rightBarHeight);
if ((int)rightBarHeight == (int)leftBarHeight)
{
m_2D.DrawFilledBox(x, y, w, leftBarHeight, UIColor.both);
}
else if (rightBarHeight > leftBarHeight)
{
m_2D.DrawFilledBox(x, y, w, rightBarHeight, UIColor.right);
m_2D.DrawFilledBox(x, y, w, leftBarHeight, UIColor.both);
}
else
{
m_2D.DrawFilledBox(x, y, w, leftBarHeight, UIColor.left);
m_2D.DrawFilledBox(x, y, w, rightBarHeight, UIColor.both);
}
float bucketStart = min + (bucketAt * bucketWidth);
float bucketEnd = bucketStart + bucketWidth;
string tooltip = string.Format(
"{0}-{1}\nLeft: {2} {3}\nRight: {4} {5}\n\nBar width: {6}",
displayUnits.ToTooltipString(bucketStart, false),
displayUnits.ToTooltipString(bucketEnd, true),
leftBarCount, leftBarCount == 1 ? "frame" : "frames",
rightBarCount, rightBarCount == 1 ? "frame" : "frames",
displayUnits.ToTooltipString(bucketWidth, true));
GUI.Label(new Rect(rect.x + x, rect.y + y, w, h),
new GUIContent("", tooltip)
);
x += w;
x += spacing;
}
}
m_2D.DrawEnd();
}
histogram.DrawEnd(width, min, max, spacing);
}
void DrawComparisonFrameSummary()
{
EditorGUILayout.BeginVertical(GUI.skin.box, GUILayout.Width(LayoutSize.WidthRHS));
bool lastShowFrameSummary = m_ShowFrameSummary;
m_ShowFrameSummary = BoldFoldout(m_ShowFrameSummary, Styles.frameSummary);
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
if (m_ShowFrameSummary)
{
EditorGUILayout.BeginVertical(); // To match indenting on the marker summary where a scroll area is present
if (IsAnalysisValid())
{
var leftFrameSummary = m_ProfileLeftView.analysis.GetFrameSummary();
var rightFrameSummary = m_ProfileRightView.analysis.GetFrameSummary();
m_Columns.SetColumnSizes(LayoutSize.WidthColumn0, LayoutSize.WidthColumn1, LayoutSize.WidthColumn2, LayoutSize.WidthColumn3);
m_Columns.Draw4("", "Left", "Right", "Diff");
int diff = rightFrameSummary.count - leftFrameSummary.count;
m_Columns.Draw4(Styles.frameCount, GetFrameCountText(m_ProfileLeftView), GetFrameCountText(m_ProfileRightView), new GUIContent(diff.ToString(), ""));
m_Columns.Draw3(Styles.frameStart, GetFirstFrameText(m_ProfileLeftView), GetFirstFrameText(m_ProfileRightView));
m_Columns.Draw3(Styles.frameEnd, GetLastFrameText(m_ProfileLeftView), GetLastFrameText(m_ProfileRightView));
m_Columns.Draw(0, "");
string units = GetDisplayUnits();
m_Columns.Draw4("", units, units, units);
Draw4DiffMs(Styles.max, leftFrameSummary.msMax, leftFrameSummary.maxFrameIndex, rightFrameSummary.msMax, rightFrameSummary.maxFrameIndex);
Draw4DiffMs(Styles.upperQuartile, leftFrameSummary.msUpperQuartile, rightFrameSummary.msUpperQuartile);
Draw4DiffMs(Styles.median, leftFrameSummary.msMedian, leftFrameSummary.medianFrameIndex, rightFrameSummary.msMedian, rightFrameSummary.medianFrameIndex);
Draw4DiffMs(Styles.mean, leftFrameSummary.msMean, rightFrameSummary.msMean);
Draw4DiffMs(Styles.lowerQuartile, leftFrameSummary.msLowerQuartile, rightFrameSummary.msLowerQuartile);
Draw4DiffMs(Styles.min, leftFrameSummary.msMin, leftFrameSummary.minFrameIndex, rightFrameSummary.msMin, rightFrameSummary.minFrameIndex);
GUIStyle style = GUI.skin.label;
GUILayout.Space(style.lineHeight);
EditorGUILayout.BeginHorizontal();
int leftBucketCount = leftFrameSummary.buckets.Length;
int rightBucketCount = rightFrameSummary.buckets.Length;
float msFrameMax = Math.Max(leftFrameSummary.msMax, rightFrameSummary.msMax);
float yRange = msFrameMax;
if (leftBucketCount != rightBucketCount)
{
Debug.Log("Error left frame summary bucket count doesn't equal right summary");
}
else
{
DrawComparisonHistogram(40, 0, yRange, leftBucketCount, leftFrameSummary.buckets, rightFrameSummary.buckets, leftFrameSummary.count, rightFrameSummary.count, true, true, m_DisplayUnits);
}
BoxAndWhiskerPlot boxAndWhiskerPlot = new BoxAndWhiskerPlot(m_2D, m_DisplayUnits.Units);
float plotWidth = 40 + GUI.skin.box.padding.horizontal;
float plotHeight = 40;
plotWidth /= 2.0f;
boxAndWhiskerPlot.Draw(plotWidth, plotHeight, leftFrameSummary.msMin, leftFrameSummary.msLowerQuartile,
leftFrameSummary.msMedian, leftFrameSummary.msUpperQuartile, leftFrameSummary.msMax, 0, yRange,
UIColor.boxAndWhiskerLineColorLeft, UIColor.boxAndWhiskerBoxColorLeft);
boxAndWhiskerPlot.Draw(plotWidth, plotHeight, rightFrameSummary.msMin, rightFrameSummary.msLowerQuartile,
rightFrameSummary.msMedian, rightFrameSummary.msUpperQuartile, rightFrameSummary.msMax, 0, yRange,
UIColor.boxAndWhiskerLineColorRight, UIColor.boxAndWhiskerBoxColorRight);
boxAndWhiskerPlot.DrawText(m_Columns.GetColumnWidth(3), plotHeight, 0, yRange,
"Min frame time for selected frames in the 2 data sets",
"Max frame time for selected frames in the 2 data sets");
EditorGUILayout.EndHorizontal();
}
else
{
EditorGUILayout.LabelField("No analysis data selected");
}
EditorGUILayout.EndVertical();
}
if (m_ShowFrameSummary != lastShowFrameSummary)
{
ProfileAnalyzerAnalytics.SendUIVisibilityEvent(ProfileAnalyzerAnalytics.UIVisibility.Frames, analytic.GetDurationInSeconds(), m_ShowFrameSummary);
}
EditorGUILayout.EndVertical();
}
void ShowThreadRange()
{
EditorGUILayout.BeginHorizontal();
m_Columns.Draw(0, Styles.threadGraphScale);
m_ThreadRange = (ThreadRange)EditorGUILayout.Popup((int)m_ThreadRange, Styles.threadRanges, GUILayout.Width(160));
EditorGUILayout.EndHorizontal();
}
float GetThreadTimeRange(ProfileAnalysis profileAnalysis)
{
if (profileAnalysis == null)
return 0.0f;
var frameSummary = profileAnalysis.GetFrameSummary();
float range = frameSummary.msMax;
switch (m_ThreadRange)
{
case ThreadRange.Median:
range = frameSummary.msMedian;
break;
case ThreadRange.UpperQuartile:
range = frameSummary.msUpperQuartile;
break;
case ThreadRange.Max:
range = frameSummary.msMax;
break;
}
return range;
}
int GetThreadSelectionCount(out int leftSelectionCount, out int rightSelectionCount)
{
List<string> threadSelection = GetLimitedThreadSelection(m_ThreadNames, m_ThreadSelection);
leftSelectionCount = 0;
foreach (var threadName in m_ProfileLeftView.data.GetThreadNames())
{
if (threadSelection.Contains(threadName))
{
leftSelectionCount++;
}
}
rightSelectionCount = 0;
foreach (var threadName in m_ProfileRightView.data.GetThreadNames())
{
if (threadSelection.Contains(threadName))
{
rightSelectionCount++;
}
}
return threadSelection.Count;
}
void DrawComparisonThreadSummary()
{
EditorGUILayout.BeginVertical(GUI.skin.box, GUILayout.Width(LayoutSize.WidthRHS));
bool lastShowThreadSummary = m_ShowThreadSummary;
m_ShowThreadSummary = BoldFoldout(m_ShowThreadSummary, Styles.threadSummary);
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
if (m_ShowThreadSummary)
{
EditorGUILayout.BeginVertical(); // To match indenting on the marker summary where a scroll area is present
if (IsAnalysisValid())
{
m_Columns.SetColumnSizes(LayoutSize.WidthColumn0, LayoutSize.WidthColumn1, LayoutSize.WidthColumn2, LayoutSize.WidthColumn3);
EditorGUILayout.BeginHorizontal();
m_Columns.Draw4("", "Left", "Right", "Total");
EditorGUILayout.EndHorizontal();
int matchingCount, uniqueLeft, uniqueRight;
int allThreadsCount = GetCombinedThreadCount(out matchingCount, out uniqueLeft, out uniqueRight);
EditorGUILayout.BeginHorizontal();
m_Columns.Draw(0, "Total Count : ");
m_Columns.Draw(1, new GUIContent(m_ProfileLeftView.data.GetThreadCount().ToString(), "Total threads in left data set"));
m_Columns.Draw(2, new GUIContent(m_ProfileRightView.data.GetThreadCount().ToString(), "Total threads in right data set"));
string tooltip = GetThreadCountToolTipUnion(allThreadsCount, matchingCount);
m_Columns.Draw(3, new GUIContent(allThreadsCount.ToString(), tooltip));
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
m_Columns.Draw(0, "Unique Count : ");
m_Columns.Draw(1, new GUIContent(uniqueLeft.ToString(), "Unique to left data set"));
m_Columns.Draw(2, new GUIContent(uniqueRight.ToString(), "Unique to right data set"));
tooltip = GetThreadCountToolTipDifference(allThreadsCount, matchingCount, uniqueLeft, uniqueRight);
m_Columns.Draw(3, new GUIContent((allThreadsCount - matchingCount).ToString(), tooltip));
EditorGUILayout.EndHorizontal();
int leftSelectionCount, rightSelectionCount;
int selectedThreads = GetThreadSelectionCount(out leftSelectionCount, out rightSelectionCount);
EditorGUILayout.BeginHorizontal();
m_Columns.Draw(0, "Selected : ");
m_Columns.Draw(1, new GUIContent(leftSelectionCount.ToString(), "Left selected"));
m_Columns.Draw(2, new GUIContent(rightSelectionCount.ToString(), "Right selected"));
m_Columns.Draw(3, new GUIContent(selectedThreads.ToString(), "Total selected"));
EditorGUILayout.EndHorizontal();
m_Columns.SetColumnSizes(LayoutSize.WidthColumn0, LayoutSize.WidthColumn1, LayoutSize.WidthColumn2 + LayoutSize.WidthColumn3, 0);
ShowThreadRange();
float width = 100;
float height = GUI.skin.label.lineHeight;
float xAxisMin = 0.0f;
float xAxisMax = GetThreadTimeRange(m_ProfileLeftView.analysis);
m_Columns.Draw3(Styles.emptyString, Styles.median, Styles.thread);
m_ThreadScroll = EditorGUILayout.BeginScrollView(m_ThreadScroll, GUIStyle.none, GUI.skin.verticalScrollbar);
Rect clipRect = new Rect(m_ThreadScroll.x, m_ThreadScroll.y, m_ComparisonThreadsAreaRect.width, m_ComparisonThreadsAreaRect.height);
m_2D.SetClipRect(clipRect);
for (int i = 0; i < m_ThreadUINames.Count; i++)
{
string threadNameWithIndex = m_ThreadNames[i];
bool include = ProfileAnalyzer.MatchThreadFilter(threadNameWithIndex, m_ThreadSelection.selection);
if (!include)
continue;
ThreadData threadLeft = m_ProfileLeftView.analysis.GetThreadByName(threadNameWithIndex);
ThreadData threadRight = m_ProfileRightView.analysis.GetThreadByName(threadNameWithIndex);
ThreadData thread = threadLeft != null ? threadLeft : threadRight;
if (thread == null)
continue;
bool singleThread = thread.threadsInGroup > 1 ? false : true;
BoxAndWhiskerPlot boxAndWhiskerPlot = new BoxAndWhiskerPlot(m_2D, m_DisplayUnits.Units);
EditorGUILayout.BeginHorizontal();
if (threadLeft != null)
boxAndWhiskerPlot.DrawHorizontal(width, height, threadLeft.msMin, threadLeft.msLowerQuartile, threadLeft.msMedian, threadLeft.msUpperQuartile, threadLeft.msMax, xAxisMin, xAxisMax, UIColor.boxAndWhiskerLineColorLeft, UIColor.boxAndWhiskerBoxColorLeft, GUI.skin.label);
else
EditorGUILayout.LabelField(Styles.noThread, GUILayout.Width(width));
m_Columns.Draw(1, (threadLeft != null) ? ToDisplayUnitsWithTooltips(threadLeft.msMedian) : Styles.noThread);
m_Columns.Draw(2, GetThreadNameWithGroupTooltip(thread.threadNameWithIndex, singleThread));
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (threadRight != null)
boxAndWhiskerPlot.DrawHorizontal(width, height, threadRight.msMin, threadRight.msLowerQuartile, threadRight.msMedian, threadRight.msUpperQuartile, threadRight.msMax, xAxisMin, xAxisMax, UIColor.boxAndWhiskerLineColorRight, UIColor.boxAndWhiskerBoxColorRight, GUI.skin.label);
else
EditorGUILayout.LabelField(Styles.noThread, GUILayout.Width(width));
m_Columns.Draw(1, (threadRight != null) ? ToDisplayUnitsWithTooltips(threadRight.msMedian) : Styles.noThread);
m_Columns.Draw(2, "");
EditorGUILayout.EndHorizontal();
}
m_2D.ClearClipRect();
EditorGUILayout.EndScrollView();
if (Event.current.type == EventType.Repaint)
{
// This value is not valid at layout phase
m_ComparisonThreadsAreaRect = GUILayoutUtility.GetLastRect();
}
}
else
{
EditorGUILayout.LabelField("No analysis data selected");
}
EditorGUILayout.EndVertical();
}
if (m_ShowThreadSummary != lastShowThreadSummary)
{
ProfileAnalyzerAnalytics.SendUIVisibilityEvent(ProfileAnalyzerAnalytics.UIVisibility.Threads, analytic.GetDurationInSeconds(), m_ShowThreadSummary);
}
EditorGUILayout.EndVertical();
}
static readonly ProfilerMarkerAbstracted m_DrawCompareOptionsProfilerMarker = new ProfilerMarkerAbstracted("ProfileAnalyzer.DrawCompareOptions");
void DrawCompareOptions()
{
using (m_DrawCompareOptionsProfilerMarker.Auto())
{
EditorGUILayout.BeginVertical(GUI.skin.box);
bool lastShowFilters = m_ShowFilters;
m_ShowFilters = BoldFoldout(m_ShowFilters, Styles.filters);
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
if (m_ShowFilters)
{
DrawRemoveMarker();
DrawNameFilter();
EditorGUILayout.BeginHorizontal();
DrawThreadFilter(m_ProfileLeftView.data);
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
m_DepthSliceUI.DrawDepthFilter(IsAnalysisRunning(), m_ActiveTab == ActiveTab.Summary,
m_ProfileSingleView, m_ProfileLeftView, m_ProfileRightView);
DrawTimingFilter();
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
DrawParentFilter();
DrawUnitFilter();
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
if (m_ProfileLeftView.IsDataValid() && m_ProfileRightView.IsDataValid())
{
bool lastEnabled = GUI.enabled;
GUI.enabled = !IsAnalysisRunning();
if (GUILayout.Button(new GUIContent("Compare", m_LastCompareTime), GUILayout.Width(100)))
m_RequestCompare = true;
GUI.enabled = lastEnabled;
}
DrawMarkerCount();
EditorGUILayout.LabelField(",", GUILayout.Width(10), GUILayout.ExpandWidth(false));
DrawThreadCount();
GUILayout.FlexibleSpace();
DrawMarkerColumnFilter();
EditorGUILayout.EndHorizontal();
}
if (m_ShowFilters != lastShowFilters)
{
ProfileAnalyzerAnalytics.SendUIVisibilityEvent(ProfileAnalyzerAnalytics.UIVisibility.Filters,
analytic.GetDurationInSeconds(), m_ShowFilters);
}
EditorGUILayout.EndVertical();
}
}
static readonly ProfilerMarkerAbstracted m_DrawComparisonProfilerMarker = new ProfilerMarkerAbstracted("ProfileAnalyzer.DrawComparison");
static readonly ProfilerMarkerAbstracted m_DrawComparisonTableProfilerMarker = new ProfilerMarkerAbstracted("ProfileAnalyzer.DrawComparisonTable");
void DrawComparison()
{
using (m_DrawComparisonProfilerMarker.Auto())
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.BeginVertical();
DrawFilesLoaded();
if (m_ProfileLeftView.IsDataValid() && m_ProfileRightView.IsDataValid() &&
m_ProfileLeftView.data.GetFrameCount() > 0 && m_ProfileRightView.data.GetFrameCount() > 0)
{
DrawCompareOptions();
if (m_ComparisonTable != null)
{
EditorGUILayout.BeginVertical(GUI.skin.box);
string title = string.Format("Top {0} markers on median frames", m_TopNBars);
GUIContent markersTitle = new GUIContent(title, Styles.topMarkersTooltip);
bool lastShowTopMarkers = m_ShowTopNMarkers;
m_ShowTopNMarkers = BoldFoldout(m_ShowTopNMarkers, markersTitle);
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
if (m_ShowTopNMarkers)
{
using (m_TopNMarkersProfilerMarker.Auto())
{
EditorGUILayout.BeginVertical(GUILayout.Height(40));
Rect rect = EditorGUILayout.GetControlRect(GUILayout.ExpandWidth(true),
GUILayout.ExpandHeight(true));
rect.height = rect.height / 2;
var nameFilters = GetNameFilters();
var nameExcludes = GetNameExcludes();
m_TopMarkersLeft.SetData(m_ProfileLeftView, m_DepthSliceUI.depthFilter1, nameFilters,
nameExcludes, m_TimingOption, m_ThreadSelection.selection.Count, m_hideRemovedMarkers);
m_TopMarkersRight.SetData(m_ProfileRightView, m_DepthSliceUI.depthFilter2, nameFilters,
nameExcludes, m_TimingOption, m_ThreadSelection.selection.Count, m_hideRemovedMarkers);
float leftRange = m_TopMarkersLeft.GetTopMarkerTimeRange();
float rightRange = m_TopMarkersRight.GetTopMarkerTimeRange();
if (m_TopTenDisplay == TopTenDisplay.LongestTime)
{
float max = Math.Max(leftRange, rightRange);
leftRange = max;
rightRange = max;
}
int leftMedian = 0;
int rightMedian = 0;
if (m_ProfileLeftView.analysis != null)
{
FrameSummary frameSummary = m_ProfileLeftView.analysis.GetFrameSummary();
if (frameSummary.count > 0)
leftMedian = frameSummary.medianFrameIndex;
}
if (m_ProfileRightView.analysis != null)
{
FrameSummary frameSummary = m_ProfileRightView.analysis.GetFrameSummary();
if (frameSummary.count > 0)
rightMedian = frameSummary.medianFrameIndex;
}
int maxMedian = Math.Max(leftMedian, rightMedian);
Rect frameIndexRect = new Rect(rect);
Vector2 size =
GUI.skin.button.CalcSize(new GUIContent(string.Format("{0}", maxMedian)));
frameIndexRect.width =
Math.Max(size.x, 50); // DrawFrameIndexButton should always be at least 50 wide
if (leftMedian != 0f)
DrawFrameIndexButton(frameIndexRect, leftMedian, m_ProfileLeftView);
else
GUI.Label(frameIndexRect, "");
float padding = 2;
rect.x += frameIndexRect.width + padding;
rect.width -= frameIndexRect.width;
m_TopMarkersLeft.Draw(rect, UIColor.left, m_TopNBars, leftRange, UIColor.barBackground,
Color.black, Color.white, true, true);
rect.y += rect.height;
frameIndexRect.y += rect.height;
if (rightMedian != 0f)
DrawFrameIndexButton(frameIndexRect, rightMedian, m_ProfileRightView);
else
GUI.Label(frameIndexRect, "");
m_TopMarkersRight.Draw(rect, UIColor.right, m_TopNBars, rightRange,
UIColor.barBackground,
Color.black, Color.white, true, true);
EditorGUILayout.EndVertical();
EditorGUILayout.BeginHorizontal();
GUILayout.Label(m_DepthSliceUI.GetUIInfo(true), GUILayout.ExpandWidth(true));
GUILayout.Label(Styles.topMarkerRatio, GUILayout.ExpandWidth(false));
m_TopTenDisplay = (TopTenDisplay)EditorGUILayout.Popup((int)m_TopTenDisplay, Styles.topTenDisplayOptions, GUILayout.MaxWidth(100));
EditorGUILayout.EndHorizontal();
}
}
if (m_ShowTopNMarkers != lastShowTopMarkers)
{
ProfileAnalyzerAnalytics.SendUIVisibilityEvent(
ProfileAnalyzerAnalytics.UIVisibility.Markers, analytic.GetDurationInSeconds(),
m_ShowTopNMarkers);
}
EditorGUILayout.EndVertical();
if (m_ComparisonTable != null)
{
m_ShowMarkerTable = BoldFoldout(m_ShowMarkerTable, Styles.comparisonTable);
if (m_ShowMarkerTable)
{
using (m_DrawComparisonTableProfilerMarker.Auto())
{
Rect r = EditorGUILayout.GetControlRect(GUILayout.ExpandHeight(true));
float scrollBarWidth = GUI.skin.verticalScrollbar.fixedWidth +
GUI.skin.verticalScrollbar.border.horizontal +
GUI.skin.verticalScrollbar.margin.horizontal +
GUI.skin.verticalScrollbar.padding.horizontal;
scrollBarWidth += LayoutSize.ScrollBarPadding;
//offset vertically to get correct clipping behaviour
Rect clipRect = new Rect(r.x, m_ComparisonTable.state.scrollPos.y,
r.width - scrollBarWidth,
r.height - (m_ComparisonTable.multiColumnHeader.height + GUI.skin.box.padding.top) -
(m_ComparisonTable.ShowingHorizontalScroll
? (scrollBarWidth - LayoutSize.ScrollBarPadding)
: 0));
m_2D.SetClipRect(clipRect);
m_ComparisonTable.OnGUI(r);
m_2D.ClearClipRect();
}
}
}
}
}
else
{
ShowHelp();
}
EditorGUILayout.EndVertical();
EditorGUILayout.BeginVertical(GUILayout.Width(LayoutSize.WidthRHS));
GUILayout.Space(4);
DrawComparisonFrameSummary();
DrawComparisonThreadSummary();
DrawComparisonSelected();
EditorGUILayout.EndVertical();
EditorGUILayout.EndHorizontal();
}
}
bool BoldFoldout(bool toggle, GUIContent content)
{
GUIStyle foldoutStyle = new GUIStyle(EditorStyles.foldout);
foldoutStyle.fontStyle = FontStyle.Bold;
return EditorGUILayout.Foldout(toggle, content, true, foldoutStyle);
}
void DrawComparisonSelectedStats(MarkerData leftMarker, MarkerData rightMarker)
{
GUIStyle style = GUI.skin.label;
string units = GetDisplayUnits();
m_Columns.Draw4("", units, units, units);
Draw4DiffMs(Styles.max, MarkerData.GetMsMax(leftMarker), MarkerData.GetMaxFrameIndex(leftMarker), MarkerData.GetMsMax(rightMarker), MarkerData.GetMaxFrameIndex(rightMarker));
Draw4DiffMs(Styles.upperQuartile, MarkerData.GetMsUpperQuartile(leftMarker), MarkerData.GetMsUpperQuartile(rightMarker));
Draw4DiffMs(Styles.median, MarkerData.GetMsMedian(leftMarker), MarkerData.GetMedianFrameIndex(leftMarker), MarkerData.GetMsMedian(rightMarker), MarkerData.GetMedianFrameIndex(rightMarker));
Draw4DiffMs(Styles.mean, MarkerData.GetMsMean(leftMarker), MarkerData.GetMsMean(rightMarker));
Draw4DiffMs(Styles.lowerQuartile, MarkerData.GetMsLowerQuartile(leftMarker), MarkerData.GetMsLowerQuartile(rightMarker));
Draw4DiffMs(Styles.min, MarkerData.GetMsMin(leftMarker), MarkerData.GetMinFrameIndex(leftMarker), MarkerData.GetMsMin(rightMarker), MarkerData.GetMinFrameIndex(rightMarker));
GUILayout.Space(style.lineHeight);
Draw4DiffMs(Styles.individualMax, MarkerData.GetMsMaxIndividual(leftMarker), MarkerData.GetMsMaxIndividual(rightMarker));
Draw4DiffMs(Styles.individualMin, MarkerData.GetMsMinIndividual(leftMarker), MarkerData.GetMsMinIndividual(rightMarker));
}
void DrawComparisonSelected()
{
EditorGUILayout.BeginVertical(GUI.skin.box, GUILayout.Width(LayoutSize.WidthRHS));
GUIStyle style = GUI.skin.label;
bool lastMarkerSummary = m_ShowMarkerSummary;
m_ShowMarkerSummary = BoldFoldout(m_ShowMarkerSummary, Styles.markerSummary);
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
if (m_ShowMarkerSummary)
{
EditorGUILayout.BeginVertical(); // To match indenting on the marker summary where a scroll area is present
if (IsAnalysisValid())
{
List<MarkerData> leftMarkers = m_ProfileLeftView.analysis.GetMarkers();
List<MarkerData> rightMarkers = m_ProfileRightView.analysis.GetMarkers();
int pairingAt = m_SelectedPairing;
if (leftMarkers != null && rightMarkers != null && m_Pairings != null)
{
if (pairingAt >= 0 && pairingAt < m_Pairings.Count)
{
m_MarkerSummaryScroll = GUILayout.BeginScrollView(m_MarkerSummaryScroll, GUIStyle.none, GUI.skin.verticalScrollbar);
Rect clipRect = new Rect(m_MarkerSummaryScroll.x, m_MarkerSummaryScroll.y, LayoutSize.WidthRHS, 500);
m_2D.SetClipRect(clipRect);
EditorGUILayout.BeginVertical();
var pairing = m_Pairings[pairingAt];
var leftMarker = (pairing.leftIndex >= 0 && pairing.leftIndex < leftMarkers.Count) ? leftMarkers[pairing.leftIndex] : null;
var rightMarker = (pairing.rightIndex >= 0 && pairing.rightIndex < rightMarkers.Count) ? rightMarkers[pairing.rightIndex] : null;
EditorGUILayout.LabelField(pairing.name,
GUILayout.MaxWidth(LayoutSize.WidthRHS -
(GUI.skin.box.padding.horizontal + GUI.skin.box.margin.horizontal)));
DrawComparisonFrameRatio(leftMarker, rightMarker);
m_Columns.SetColumnSizes(LayoutSize.WidthColumn0, LayoutSize.WidthColumn1, LayoutSize.WidthColumn2, LayoutSize.WidthColumn3);
EditorGUILayout.BeginHorizontal();
m_Columns.Draw(0, Styles.firstFrame);
if (leftMarker != null)
DrawFrameIndexButton(leftMarker.firstFrameIndex, m_ProfileLeftView);
else
m_Columns.Draw(1, Styles.emptyString);
if (rightMarker != null)
DrawFrameIndexButton(rightMarker.firstFrameIndex, m_ProfileRightView);
else
m_Columns.Draw(2, Styles.emptyString);
EditorGUILayout.EndHorizontal();
DrawTopComparison(leftMarker, rightMarker);
GUILayout.Space(style.lineHeight);
EditorGUILayout.BeginHorizontal();
int leftBucketCount = leftMarker != null ? leftMarker.buckets.Length : 0;
int rightBucketCount = rightMarker != null ? rightMarker.buckets.Length : 0;
float leftMin = MarkerData.GetMsMin(leftMarker);
float rightMin = MarkerData.GetMsMin(rightMarker);
float leftMax = MarkerData.GetMsMax(leftMarker);
float rightMax = MarkerData.GetMsMax(rightMarker);
int[] leftBuckets = leftMarker != null ? leftMarker.buckets : new int[0];
int[] rightBuckets = rightMarker != null ? rightMarker.buckets : new int[0];
Units units = m_DisplayUnits.Units;
string unitName = "marker time";
if (DisplayCount())
{
units = Units.Count;
unitName = "count";
leftBucketCount = leftMarker != null ? leftMarker.countBuckets.Length : 0;
rightBucketCount = rightMarker != null ? rightMarker.countBuckets.Length : 0;
leftMin = MarkerData.GetCountMin(leftMarker);
rightMin = MarkerData.GetCountMin(rightMarker);
leftMax = MarkerData.GetCountMax(leftMarker);
rightMax = MarkerData.GetCountMax(rightMarker);
leftBuckets = leftMarker != null ? leftMarker.countBuckets : new int[0];
rightBuckets = rightMarker != null ? rightMarker.countBuckets : new int[0];
}
DisplayUnits displayUnits = new DisplayUnits(units);
float minValue;
float maxValue;
if (leftMarker != null && rightMarker != null)
{
minValue = Math.Min(leftMin, rightMin);
maxValue = Math.Max(leftMax, rightMax);
}
else if (leftMarker != null)
{
minValue = leftMin;
maxValue = leftMax;
}
else // Either valid or 0
{
minValue = rightMin;
maxValue = rightMax;
}
if (leftBucketCount > 0 && rightBucketCount > 0 && leftBucketCount != rightBucketCount)
{
Debug.Log("Error - number of buckets doesn't match in the left and right marker analysis");
}
else
{
int bucketCount = Math.Max(leftBucketCount, rightBucketCount);
int leftFrameCount = MarkerData.GetPresentOnFrameCount(leftMarker);
int rightFrameCount = MarkerData.GetPresentOnFrameCount(rightMarker);
DrawComparisonHistogram(100, minValue, maxValue, bucketCount, leftBuckets, rightBuckets, leftFrameCount, rightFrameCount, leftMarker != null, rightMarker != null, displayUnits);
}
float plotWidth = 40 + GUI.skin.box.padding.horizontal;
float plotHeight = 100;
plotWidth /= 2.0f;
BoxAndWhiskerPlot boxAndWhiskerPlot = new BoxAndWhiskerPlot(m_2D, units);
DrawBoxAndWhiskerPlotForMarker(boxAndWhiskerPlot, plotWidth, plotHeight, m_ProfileLeftView.analysis, leftMarker, minValue, maxValue,
UIColor.boxAndWhiskerLineColorLeft, UIColor.boxAndWhiskerBoxColorLeft);
DrawBoxAndWhiskerPlotForMarker(boxAndWhiskerPlot, plotWidth, plotHeight, m_ProfileRightView.analysis, rightMarker, minValue, maxValue,
UIColor.boxAndWhiskerLineColorRight, UIColor.boxAndWhiskerBoxColorRight);
boxAndWhiskerPlot.DrawText(m_Columns.GetColumnWidth(3), plotHeight, minValue, maxValue,
string.Format("Min {0} for selected frames in the 2 data sets", unitName),
string.Format("Max {0} for selected frames in the 2 data sets", unitName));
EditorGUILayout.EndHorizontal();
GUILayout.Space(style.lineHeight);
DrawComparisonSelectedStats(leftMarker, rightMarker);
EditorGUILayout.EndVertical();
m_2D.ClearClipRect();
GUILayout.EndScrollView();
}
else
{
EditorGUILayout.LabelField("Marker not in selection");
}
}
}
else
{
EditorGUILayout.LabelField("No marker data selected");
}
EditorGUILayout.EndVertical();
}
if (m_ShowMarkerSummary != lastMarkerSummary)
{
ProfileAnalyzerAnalytics.SendUIVisibilityEvent(ProfileAnalyzerAnalytics.UIVisibility.Markers, analytic.GetDurationInSeconds(), m_ShowMarkerSummary);
}
EditorGUILayout.EndVertical();
}
void SelectTab(ActiveTab newTab)
{
m_NextActiveTab = newTab;
}
static readonly ProfilerMarkerAbstracted m_DrawToolbarProfilerMarker = new ProfilerMarkerAbstracted("ProfileAnalyzer.DrawToolbar");
void DrawToolbar()
{
using (m_DrawToolbarProfilerMarker.Auto())
{
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
EditorGUILayout.LabelField("Mode:", GUILayout.Width(40));
ActiveTab newTab = (ActiveTab)GUILayout.Toolbar((int)m_ActiveTab, new string[] {"Single", "Compare"},
EditorStyles.toolbarButton, GUILayout.ExpandWidth(false));
if (newTab != m_ActiveTab)
{
SelectTab(newTab);
}
//GUILayout.FlexibleSpace();
EditorGUILayout.Separator();
bool lastEnabled = GUI.enabled;
bool enabled = GUI.enabled;
if (m_ProfileSingleView.IsDataValid() ||
(m_ProfileLeftView.IsDataValid() && m_ProfileRightView.IsDataValid()))
GUI.enabled = true;
else
GUI.enabled = false;
if (GUILayout.Button(Styles.export, EditorStyles.toolbarButton, GUILayout.Width(50)))
{
Vector2 windowPosition = new Vector2(Event.current.mousePosition.x,
Event.current.mousePosition.y + GUI.skin.label.lineHeight);
Vector2 screenPosition = GUIUtility.GUIToScreenPoint(windowPosition);
ProfileAnalyzerExportWindow.Open(screenPosition.x, screenPosition.y, m_ProfileSingleView,
m_ProfileLeftView, m_ProfileRightView, this);
EditorGUIUtility.ExitGUI();
}
GUI.enabled = lastEnabled;
bool profilerOpen = IsProfilerWindowOpen();
if (!profilerOpen)
{
if (GUILayout.Toggle(profilerOpen, "Open Profiler Window", EditorStyles.toolbarButton,
GUILayout.ExpandWidth(false)) == true)
{
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
m_ProfilerWindowInterface.OpenProfilerOrUseExisting();
ProfileAnalyzerAnalytics.SendUIButtonEvent(ProfileAnalyzerAnalytics.UIButton.OpenProfiler,
analytic);
EditorGUIUtility.ExitGUI();
}
}
else
{
if (GUILayout.Toggle(profilerOpen, "Close Profiler Window", EditorStyles.toolbarButton,
GUILayout.ExpandWidth(false)) == false)
{
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
m_ProfilerWindowInterface.CloseProfiler();
ProfileAnalyzerAnalytics.SendUIButtonEvent(ProfileAnalyzerAnalytics.UIButton.CloseProfiler,
analytic);
EditorGUIUtility.ExitGUI();
}
}
EditorGUILayout.Separator();
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
}
}
void SetupStyles()
{
if (!m_StylesSetup)
{
m_StyleMiddleRight = new GUIStyle(GUI.skin.label);
m_StyleMiddleRight.alignment = TextAnchor.MiddleRight;
m_StyleUpperLeft = new GUIStyle(GUI.skin.label);
m_StyleUpperLeft.alignment = TextAnchor.UpperLeft;
m_StylesSetup = true;
}
}
static readonly ProfilerMarkerAbstracted m_DrawProfilerMarker = new ProfilerMarkerAbstracted("ProfileAnalyzer.Draw");
void Draw()
{
// Make sure we start enabled (in case something overrode it last frame)
GUI.enabled = true;
using (m_DrawProfilerMarker.Auto())
{
SetupStyles();
EditorGUILayout.BeginVertical();
DrawToolbar();
switch (m_ActiveTab)
{
case ActiveTab.Summary:
DrawAnalysis();
break;
case ActiveTab.Compare:
DrawComparison();
break;
}
EditorGUILayout.EndVertical();
}
}
int FindSelectionByName(List<MarkerData> markers, string name)
{
int index = 0;
foreach (var marker in markers)
{
if (marker.name == name)
return index;
index++;
}
return -1; // not found
}
/// <summary>
/// Select marker to focus on
/// </summary>
/// <param name="name">Name of the marker</param>
// Version 1.0 of the package exposed this API so we can't remove it until we increment the major package version.
public void SelectMarker(string name)
{
SelectMarker(name, null, null);
}
void SelectMarker(string name, string threadGroupName = null, string threadName = null)
{
switch (m_ActiveTab)
{
case ActiveTab.Summary:
SelectMarkerByName(name, threadGroupName, threadName);
break;
case ActiveTab.Compare:
SelectPairingByName(name, threadGroupName, threadName);
break;
}
}
void UpdateSelectedMarkerName(string markerName)
{
m_SelectedMarker.name = markerName;
// only update the Profiler Window if it wasn't updated successfully with this marker yet.
if (m_LastMarkerSuccesfullySyncedWithProfilerWindow == markerName)
return;
var updatedSelectedSampleSuccesfully = false;
if (m_ProfilerWindowInterface.IsReady() && !m_SelectionEventFromProfilerWindowInProgress && m_ThreadSelection.selection != null && m_ThreadSelection.selection.Count > 0)
{
updatedSelectedSampleSuccesfully = m_ProfilerWindowInterface.SetProfilerWindowMarkerName(markerName, m_ThreadSelection.selection);
}
if (updatedSelectedSampleSuccesfully)
m_LastMarkerSuccesfullySyncedWithProfilerWindow = markerName;
}
internal void SelectMarkerByIndex(int index, string markerNameFallback = null, string threadGroupName = null, string threadName = null)
{
if (m_ProfileSingleView == null || m_ProfileSingleView.analysis == null)
return;
// Check if this marker is in the 'filtered' list
var markers = m_ProfileSingleView.analysis.GetMarkers();
if (markers.Count <= 0)
return;
bool valid = true;
if (index >= 0 && index < markers.Count)
{
var marker = markers[index];
valid = DoesMarkerPassFilter(marker.name);
}
m_SelectedMarker.id = index;
if (m_ProfileTable != null)
{
List<int> selection = new List<int>();
if (index >= 0 && valid)
selection.Add(index);
m_ProfileTable.SetSelection(selection, TreeViewSelectionOptions.RevealAndFrame);
}
var markerName = GetMarkerName(index);
if (index == -1 && !string.IsNullOrEmpty(markerNameFallback))
{
markerName = markerNameFallback;
if (!string.IsNullOrEmpty(threadName))
{
m_SelectedMarker.threadGroupName = threadGroupName;
m_SelectedMarker.threadName = threadName;
}
else
{
m_SelectedMarker.threadGroupName = null;
m_SelectedMarker.threadName = null;
}
}
else
{
m_SelectedMarker.threadGroupName = null;
m_SelectedMarker.threadName = null;
}
if (markerName != null)
UpdateSelectedMarkerName(markerName);
}
/// <summary>
/// Get currently selected marker
/// </summary>
/// <returns>Name of currently selected marker, or null if none selected</returns>
public string GetSelectedMarkerName()
{
switch (m_ActiveTab)
{
case ActiveTab.Summary:
return GetMarkerName(m_SelectedMarker.id);
case ActiveTab.Compare:
return GetPairingName(m_SelectedPairing);
}
return null;
}
string GetMarkerName(int index)
{
if (m_ProfileSingleView.analysis == null)
return null;
var marker = m_ProfileSingleView.analysis.GetMarker(index);
if (marker == null)
return null;
return marker.name;
}
void SelectMarkerByName(string markerName, string threadGroupName = null, string threadName = null)
{
int index = (m_ProfileSingleView.analysis != null) ? m_ProfileSingleView.analysis.GetMarkerIndexByName(markerName) : -1;
SelectMarkerByIndex(index, markerName, threadGroupName, threadName);
}
internal void SelectPairing(int index, string threadGroupName = null, string threadName = null)
{
if (m_Pairings == null || m_Pairings.Count == 0)
return;
// Check if this marker is in the 'filtered' list
bool valid = true;
if (index >= 0 && index < m_Pairings.Count)
{
var pairing = m_Pairings[index];
valid = DoesMarkerPassFilter(pairing.name);
}
m_SelectedPairing = index;
if (m_ComparisonTable != null)
{
List<int> selection = new List<int>();
if (index >= 0 && valid)
selection.Add(index);
m_ComparisonTable.SetSelection(selection, TreeViewSelectionOptions.RevealAndFrame);
}
var markerName = GetPairingName(index);
if (markerName != null)
UpdateSelectedMarkerName(markerName);
}
string GetPairingName(int index)
{
if (m_Pairings == null)
return null;
if (index < 0 || index >= m_Pairings.Count)
return null;
return m_Pairings[index].name;
}
void SelectPairingByName(string pairingName, string threadGroupName = null, string threadName = null)
{
if (m_Pairings != null && pairingName != null)
{
for (int index = 0; index < m_Pairings.Count; index++)
{
var pairing = m_Pairings[index];
if (pairing.name == pairingName)
{
SelectPairing(index, threadGroupName, threadName);
return;
}
}
}
SelectPairing(-1, threadGroupName, threadName);
}
GUIContent GetFrameCountText(ProfileDataView context)
{
var frameSummary = context.analysis.GetFrameSummary();
string text;
string tooltip;
if (frameSummary.first == frameSummary.last)
{
text = string.Format("{0}", frameSummary.count);
tooltip = "";
}
else
{
int rangeSize = (1 + (frameSummary.last - frameSummary.first));
if (frameSummary.count == rangeSize)
{
text = string.Format("{0}", frameSummary.count);
tooltip = string.Format("{0} frames selected\n\n{1} first selected frame\n{2} last selected frame\n\nConsecutive Sequence"
, frameSummary.count
, GetRemappedUIFrameIndex(frameSummary.first, context)
, GetRemappedUIFrameIndex(frameSummary.last, context));
}
else
{
text = string.Format("{0}*", frameSummary.count);
var ranges = RangesText(context);
tooltip = string.Format("{0} frames selected\n\nframe ranges: {1} \n\nNot a consecutive sequence", frameSummary.count, ranges);
}
}
return new GUIContent(text, tooltip);
}
string RangesText(ProfileDataView context)
{
var sortedFrames = context.analysis.GetFrameSummary().frames.OrderBy(x => x.frameIndex).ToArray();
var ranges = "";
int lastAdded = GetRemappedUIFrameIndex(sortedFrames[0].frameIndex, context);
ranges += lastAdded;
for (int n = 1; n < sortedFrames.Length; ++n)
{
if (sortedFrames[n].frameIndex == (sortedFrames[n - 1].frameIndex + 1)) continue;
int nIdx = GetRemappedUIFrameIndex(sortedFrames[n].frameIndex, context);
int pNIdx = GetRemappedUIFrameIndex(sortedFrames[n - 1].frameIndex, context);
if (lastAdded == pNIdx)
{
ranges += ", " + nIdx;
}
else
{
ranges += "-" + pNIdx + ", " + nIdx;
}
lastAdded = nIdx;
}
int remappedLastFrame = GetRemappedUIFrameIndex(sortedFrames.Last().frameIndex, context);
if (lastAdded == remappedLastFrame)
return ranges;
ranges += "-" + remappedLastFrame;
return ranges;
}
GUIContent GetFirstFrameText(ProfileDataView context)
{
var frameSummary = context.analysis.GetFrameSummary();
string text;
string tooltip;
if (frameSummary.count == 0)
{
text = "";
tooltip = "";
}
else if (frameSummary.first == frameSummary.last)
{
int remappedFrame = GetRemappedUIFrameIndex(frameSummary.first, context);
text = string.Format("{0}", remappedFrame);
tooltip = string.Format("Frame {0} selected", remappedFrame);
}
else
{
int rangeSize = (1 + (frameSummary.last - frameSummary.first));
if (frameSummary.count == rangeSize)
{
int remappedFirstFrame = GetRemappedUIFrameIndex(frameSummary.first, context);
text = string.Format("{0}", remappedFirstFrame);
tooltip = string.Format("{0} frames selected\n\n{1} first selected frame\n{2} last selected frame\n\nConsecutive Sequence"
, frameSummary.count
, remappedFirstFrame
, GetRemappedUIFrameIndex(frameSummary.last, context));
}
else
{
text = string.Format("{0}*", GetRemappedUIFrameIndex(frameSummary.first, context));
var ranges = RangesText(context);
tooltip = string.Format("{0} frames selected\n\nframe ranges: {1} \n\nNot a consecutive sequence", frameSummary.count, ranges);
}
}
return new GUIContent(text, tooltip);
}
GUIContent GetLastFrameText(ProfileDataView context)
{
var frameSummary = context.analysis.GetFrameSummary();
string text;
string tooltip;
if (frameSummary.count == 0)
{
text = "";
tooltip = "";
}
else if (frameSummary.first == frameSummary.last)
{
text = "";
tooltip = string.Format("Frame {0} selected", GetRemappedUIFrameIndex(frameSummary.first, context), context);
}
else
{
int rangeSize = (1 + (frameSummary.last - frameSummary.first));
if (frameSummary.count == rangeSize)
{
int remappedLastFrame = GetRemappedUIFrameIndex(frameSummary.last, context);
text = string.Format("{0}", remappedLastFrame);
tooltip = string.Format("{0} frames selected\n\n{1} first selected frame\n{2} last selected frame\n\nConsecutive Sequence",
frameSummary.count,
GetRemappedUIFrameIndex(frameSummary.first, context),
remappedLastFrame);
}
else
{
text = string.Format("{0}*", GetRemappedUIFrameIndex(frameSummary.last, context));
var ranges = RangesText(context);
tooltip = string.Format("{0} frames selected\n\nframe ranges: {1} \n\nNot a consecutive sequence", frameSummary.count, ranges);;
}
}
return new GUIContent(text, tooltip);
}
void DrawFrameSummary()
{
EditorGUILayout.BeginVertical(GUI.skin.box, GUILayout.Width(LayoutSize.WidthRHS));
bool lastShowFrameSummary = m_ShowFrameSummary;
m_ShowFrameSummary = BoldFoldout(m_ShowFrameSummary, Styles.frameSummary);
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
if (m_ShowFrameSummary)
{
EditorGUILayout.BeginVertical(); // To match indenting on the marker summary where a scroll area is present
if (IsAnalysisValid())
{
var frameSummary = m_ProfileSingleView.analysis.GetFrameSummary();
m_Columns.SetColumnSizes(LayoutSize.WidthColumn0, LayoutSize.WidthColumn1, LayoutSize.WidthColumn2, LayoutSize.WidthColumn3);
m_Columns.Draw(0, "");
m_Columns.Draw2(Styles.frameCount, GetFrameCountText(m_ProfileSingleView));
EditorGUILayout.BeginHorizontal();
m_Columns.Draw(0, Styles.frameStart);
GUIContent firstFrameTextContent = GetFirstFrameText(m_ProfileSingleView);
m_Columns.Draw(1, firstFrameTextContent);
if (firstFrameTextContent.text != "")
DrawFrameIndexButton(frameSummary.first, m_ProfileSingleView);
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
m_Columns.Draw(0, Styles.frameEnd);
GUIContent lastFrameTextContent = GetLastFrameText(m_ProfileSingleView);
m_Columns.Draw(1, lastFrameTextContent);
if (lastFrameTextContent.text != "")
DrawFrameIndexButton(frameSummary.last, m_ProfileSingleView);
EditorGUILayout.EndHorizontal();
m_Columns.SetColumnSizes(LayoutSize.WidthColumn0, LayoutSize.WidthColumn1, LayoutSize.WidthColumn2, LayoutSize.WidthColumn3);
m_Columns.Draw(0, "");
m_Columns.Draw3("", GetDisplayUnits(), "Frame");
Draw3LabelMsFrame(Styles.max, frameSummary.msMax, frameSummary.maxFrameIndex, m_ProfileSingleView);
Draw2LabelMs(Styles.upperQuartile, frameSummary.msUpperQuartile);
Draw3LabelMsFrame(Styles.median, frameSummary.msMedian, frameSummary.medianFrameIndex, m_ProfileSingleView);
Draw2LabelMs(Styles.mean, frameSummary.msMean);
Draw2LabelMs(Styles.lowerQuartile, frameSummary.msLowerQuartile);
Draw3LabelMsFrame(Styles.min, frameSummary.msMin, frameSummary.minFrameIndex, m_ProfileSingleView);
GUIStyle style = GUI.skin.label;
GUILayout.Space(style.lineHeight);
EditorGUILayout.BeginHorizontal();
Histogram histogram = new Histogram(m_2D, m_DisplayUnits.Units);
histogram.Draw(LayoutSize.HistogramWidth, 40, frameSummary.buckets, frameSummary.count, 0, frameSummary.msMax, UIColor.bar);
BoxAndWhiskerPlot boxAndWhiskerPlot = new BoxAndWhiskerPlot(m_2D, m_DisplayUnits.Units);
float plotWidth = 40 + GUI.skin.box.padding.horizontal;
float plotHeight = 40;
boxAndWhiskerPlot.Draw(plotWidth, plotHeight, frameSummary.msMin, frameSummary.msLowerQuartile, frameSummary.msMedian, frameSummary.msUpperQuartile, frameSummary.msMax, 0, frameSummary.msMax, UIColor.standardLine, UIColor.standardLine);
boxAndWhiskerPlot.DrawText(m_Columns.GetColumnWidth(3), plotHeight, frameSummary.msMin, frameSummary.msMax,
"Min frame time for selected frames",
"Max frame time for selected frames");
EditorGUILayout.EndHorizontal();
}
else
{
EditorGUILayout.LabelField("No analysis data selected");
}
EditorGUILayout.EndVertical();
}
if (m_ShowFrameSummary != lastShowFrameSummary)
{
ProfileAnalyzerAnalytics.SendUIVisibilityEvent(ProfileAnalyzerAnalytics.UIVisibility.Frames, analytic.GetDurationInSeconds(), m_ShowFrameSummary);
}
EditorGUILayout.EndVertical();
}
GUIContent GetThreadNameWithGroupTooltip(string threadNameWithIndex, bool singleThread)
{
string friendlyThreadName = GetFriendlyThreadName(threadNameWithIndex, singleThread);
string groupName;
friendlyThreadName = ProfileData.GetThreadNameWithoutGroup(friendlyThreadName, out groupName);
if (groupName == "")
return new GUIContent(friendlyThreadName, string.Format("{0}", friendlyThreadName));
else
return new GUIContent(friendlyThreadName, string.Format("{0}\n{1}", friendlyThreadName, groupName));
}
void DrawThreadSummary()
{
EditorGUILayout.BeginVertical(GUI.skin.box, GUILayout.Width(LayoutSize.WidthRHS));
bool lastShowThreadSummary = m_ShowThreadSummary;
m_ShowThreadSummary = BoldFoldout(m_ShowThreadSummary, Styles.threadSummary);
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
if (m_ShowThreadSummary)
{
EditorGUILayout.BeginVertical(); // To match indenting on the marker summary where a scroll area is present
if (IsAnalysisValid())
{
float xAxisMin = 0.0f;
float xAxisMax = GetThreadTimeRange(m_ProfileSingleView.analysis);
m_Columns.SetColumnSizes(LayoutSize.WidthColumn0, LayoutSize.WidthColumn1, LayoutSize.WidthColumn2, LayoutSize.WidthColumn3);
EditorGUILayout.BeginHorizontal();
m_Columns.Draw4("", "", "", "");
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
m_Columns.Draw(0, "Total Count : ");
m_Columns.Draw(1, m_ProfileSingleView.data.GetThreadCount().ToString());
EditorGUILayout.EndHorizontal();
List<string> threadSelection = GetLimitedThreadSelection(m_ThreadNames, m_ThreadSelection);
int selectedThreads = threadSelection.Count;
EditorGUILayout.BeginHorizontal();
m_Columns.Draw(0, "Selected : ");
m_Columns.Draw(1, selectedThreads.ToString());
EditorGUILayout.EndHorizontal();
ShowThreadRange();
m_Columns.SetColumnSizes(LayoutSize.WidthColumn0, LayoutSize.WidthColumn1, LayoutSize.WidthColumn2 + LayoutSize.WidthColumn3, 0);
m_Columns.Draw3("", "Median", "Thread");
m_ThreadScroll = EditorGUILayout.BeginScrollView(m_ThreadScroll, GUIStyle.none, GUI.skin.verticalScrollbar);
Rect clipRect = new Rect(m_ThreadScroll.x, m_ThreadScroll.y, m_ThreadsAreaRect.width, m_ThreadsAreaRect.height);
m_2D.SetClipRect(clipRect);
for (int i = 0; i < m_ThreadUINames.Count; i++)
{
string threadNameWithIndex = m_ThreadNames[i];
if (!threadNameWithIndex.Contains(":"))
continue; // Ignore 'All'
bool include = ProfileAnalyzer.MatchThreadFilter(threadNameWithIndex, m_ThreadSelection.selection);
if (!include)
continue;
ThreadData thread = m_ProfileSingleView.analysis.GetThreadByName(threadNameWithIndex);
if (thread == null) // May be the 'all' field
continue;
bool singleThread = thread.threadsInGroup > 1 ? false : true;
BoxAndWhiskerPlot boxAndWhiskerPlot = new BoxAndWhiskerPlot(m_2D, m_DisplayUnits.Units);
EditorGUILayout.BeginHorizontal();
boxAndWhiskerPlot.DrawHorizontal(100, GUI.skin.label.lineHeight, thread.msMin, thread.msLowerQuartile, thread.msMedian, thread.msUpperQuartile, thread.msMax, xAxisMin, xAxisMax, UIColor.bar, UIColor.barBackground, GUI.skin.label);
m_Columns.Draw(1, ToDisplayUnitsWithTooltips(thread.msMedian));
m_Columns.Draw(2, GetThreadNameWithGroupTooltip(thread.threadNameWithIndex, singleThread));
EditorGUILayout.EndHorizontal();
}
m_2D.ClearClipRect();
EditorGUILayout.EndScrollView();
if (Event.current.type == EventType.Repaint)
{
// This value is not valid at layout phase
m_ThreadsAreaRect = GUILayoutUtility.GetLastRect();
}
}
else
{
EditorGUILayout.LabelField("No analysis data selected");
}
EditorGUILayout.EndVertical();
}
if (m_ShowThreadSummary != lastShowThreadSummary)
{
ProfileAnalyzerAnalytics.SendUIVisibilityEvent(ProfileAnalyzerAnalytics.UIVisibility.Threads, analytic.GetDurationInSeconds(), m_ShowThreadSummary);
}
EditorGUILayout.EndVertical();
}
void DrawHistogramForMarker(Histogram histogram, MarkerData marker)
{
if (DisplayCount())
histogram.Draw(LayoutSize.HistogramWidth, 100, marker.countBuckets, marker.presentOnFrameCount, marker.countMin, marker.countMax, UIColor.bar);
else
histogram.Draw(LayoutSize.HistogramWidth, 100, marker.buckets, marker.presentOnFrameCount, marker.msMin, marker.msMax, UIColor.bar);
}
internal bool IsProfilerWindowOpen()
{
return m_ProfilerWindowInterface.IsReady();
}
/// <summary>
/// Used to remap frame indices when the loaded range in the profiler does not match the range present in the Profile Analyzer capture.
/// This happens when we reload data into the Profiler Window as the index range becomes 1 -> n+1
/// </summary>
/// <param name="frameIndex">target frame index</param>
/// <param name="frameIndexOffset">capture frameIndex offset</param>
/// <returns></returns>
internal int RemapFrameIndex(int frameIndex, int frameIndexOffset)
{
if (m_ProfilerFirstFrameIndex == 1 && frameIndex > frameIndexOffset)
return frameIndex - frameIndexOffset;
else
return frameIndex;
}
internal void JumpToFrame(int frameIndex, ProfileData frameContext, bool reportErrors = true)
{
if (!m_ProfilerWindowInterface.IsReady())
return;
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
m_ProfilerWindowInterface.JumpToFrame(RemapFrameIndex(frameIndex, frameContext.FrameIndexOffset));
if (IsSelectedMarkerNameValid())
m_ProfilerWindowInterface.SetProfilerWindowMarkerName(m_SelectedMarker.name, m_ThreadSelection.selection);
ProfileAnalyzerAnalytics.SendUIButtonEvent(ProfileAnalyzerAnalytics.UIButton.JumpToFrame, analytic);
}
internal float DrawFrameIndexButton(int frameIndex, ProfileDataView frameContext)
{
float defaultWidth = 50f;
if (frameIndex < 0)
return defaultWidth;
bool enabled = GUI.enabled;
if (!IsProfilerWindowOpen() || !frameContext.inSyncWithProfilerData)
GUI.enabled = false;
var remappedIndex = GetRemappedUIFrameIndex(frameIndex, frameContext);
var content = new GUIContent(string.Format("{0}", remappedIndex), string.Format("Jump to frame {0} in the Unity Profiler", remappedIndex));
Vector2 size = GUI.skin.button.CalcSize(content);
//float height = size.y;
float maxWidth = Math.Max(defaultWidth, size.x);
if (GUILayout.Button(content, GUILayout.MinWidth(defaultWidth), GUILayout.MaxWidth(maxWidth)))
{
JumpToFrame(frameIndex, frameContext.data);
}
GUI.enabled = enabled;
return maxWidth;
}
internal void DrawFrameIndexButton(Rect rect, int frameIndex, ProfileDataView frameContext)
{
if (frameIndex < 0)
return;
bool enabled = GUI.enabled;
if (!IsProfilerWindowOpen() || !frameContext.inSyncWithProfilerData)
GUI.enabled = false;
// Clamp to max height to match other buttons
// And centre vertically if needed
var remappedIndex = GetRemappedUIFrameIndex(frameIndex, frameContext);
var content = new GUIContent(string.Format("{0}", remappedIndex), string.Format("Jump to frame {0} in the Unity Profiler", remappedIndex));
Vector2 size = GUI.skin.button.CalcSize(content);
float height = size.y; // was 14
rect.y += (rect.height - height) / 2;
rect.height = Math.Min(rect.height, height);
if (GUI.Button(rect, content))
{
JumpToFrame(frameIndex, frameContext.data);
}
GUI.enabled = enabled;
}
void Draw3LabelMsFrame(GUIContent col1, float ms, int frameIndex, ProfileDataView frameContext)
{
EditorGUILayout.BeginHorizontal();
m_Columns.Draw(0, col1);
m_Columns.Draw(1, ToDisplayUnitsWithTooltips(ms));
DrawFrameIndexButton(frameIndex, frameContext);
EditorGUILayout.EndHorizontal();
}
void Draw2LabelMs(GUIContent col1, float ms)
{
EditorGUILayout.BeginHorizontal();
m_Columns.Draw(0, col1);
m_Columns.Draw(1, ToDisplayUnitsWithTooltips(ms));
EditorGUILayout.EndHorizontal();
}
void Draw4DiffMs(GUIContent col1, float msLeft, float msRight)
{
EditorGUILayout.BeginHorizontal();
m_Columns.Draw(0, col1);
m_Columns.Draw(1, ToDisplayUnitsWithTooltips(msLeft));
m_Columns.Draw(2, ToDisplayUnitsWithTooltips(msRight));
m_Columns.Draw(3, ToDisplayUnitsWithTooltips(msRight - msLeft));
EditorGUILayout.EndHorizontal();
}
void Draw4DiffMs(GUIContent col1, float msLeft, int frameIndexLeft, float msRight, int frameIndexRight)
{
EditorGUILayout.BeginHorizontal();
m_Columns.Draw(0, col1);
m_Columns.Draw(1, ToDisplayUnitsWithTooltips(msLeft, false, frameIndexLeft));
m_Columns.Draw(2, ToDisplayUnitsWithTooltips(msRight, false, frameIndexRight));
m_Columns.Draw(3, ToDisplayUnitsWithTooltips(msRight - msLeft));
EditorGUILayout.EndHorizontal();
}
void Draw4Ms(GUIContent col1, float value2, float value3, float value4)
{
EditorGUILayout.BeginHorizontal();
m_Columns.Draw(0, col1);
m_Columns.Draw(1, ToDisplayUnitsWithTooltips(value2));
m_Columns.Draw(2, ToDisplayUnitsWithTooltips(value3));
m_Columns.Draw(3, ToDisplayUnitsWithTooltips(value4));
EditorGUILayout.EndHorizontal();
}
void DrawBoxAndWhiskerPlotForMarker(BoxAndWhiskerPlot boxAndWhiskerPlot, float width, float height, ProfileAnalysis analysis, MarkerData marker, float yAxisStart, float yAxisEnd, Color color, Color colorBackground)
{
if (marker == null)
{
boxAndWhiskerPlot.Draw(width, height, 0, 0, 0, 0, 0, yAxisStart, yAxisEnd, color, colorBackground);
return;
}
if (DisplayCount())
boxAndWhiskerPlot.Draw(width, height, marker.countMin, marker.countLowerQuartile, marker.countMedian, marker.countUpperQuartile, marker.countMax, yAxisStart, yAxisEnd, color, colorBackground);
else
boxAndWhiskerPlot.Draw(width, height, marker.msMin, marker.msLowerQuartile, marker.msMedian, marker.msUpperQuartile, marker.msMax, yAxisStart, yAxisEnd, color, colorBackground);
}
void DrawBoxAndWhiskerPlotHorizontalForMarker(BoxAndWhiskerPlot boxAndWhiskerPlot, float width, float height, ProfileAnalysis analysis, MarkerData marker, float yAxisStart, float yAxisEnd, Color color, Color colorBackground)
{
boxAndWhiskerPlot.DrawHorizontal(width, height, marker.msMin, marker.msLowerQuartile, marker.msMedian, marker.msUpperQuartile, marker.msMax, yAxisStart, yAxisEnd, color, colorBackground);
}
void DrawFrameRatio(MarkerData marker)
{
var frameSummary = m_ProfileSingleView.analysis.GetFrameSummary();
GUIStyle style = GUI.skin.label;
float w = LayoutSize.WidthColumn0;
float h = style.lineHeight;
float ySpacing = 2;
float barHeight = h - ySpacing;
EditorGUILayout.BeginVertical(GUILayout.Width(w + LayoutSize.WidthColumn1 + LayoutSize.WidthColumn2));
float barMax = frameSummary.msMean;
float barValue = marker.msMean;
string text = "Mean frame contribution";
Units units = m_DisplayUnits.Units;
bool removed = marker.timeIgnored > 0;
if (DisplayCount())
{
units = Units.Count;
barMax = frameSummary.markerCountMaxMean;
barValue = marker.countMean;
text = "Mean count";
}
DisplayUnits displayUnits = new DisplayUnits(units);
float barLength = Math.Min((w * barValue) / barMax, w);
EditorGUILayout.LabelField(text);
m_Columns.SetColumnSizes(LayoutSize.WidthColumn0, LayoutSize.WidthColumn1, LayoutSize.WidthColumn2, LayoutSize.WidthColumn3);
m_Columns.Draw2("", "");
EditorGUILayout.BeginHorizontal();
if (removed)
{
EditorGUILayout.LabelField("(Marker Removed from analysis)");
}
else
{
// NOTE: This can effect the whole width of the region its inside
// Not clear why
if (m_2D.DrawStart(w, h, Draw2D.Origin.TopLeft, style))
{
m_2D.DrawFilledBox(0, ySpacing, barLength, barHeight, UIColor.bar);
m_2D.DrawFilledBox(barLength, ySpacing, w - barLength, barHeight, UIColor.barBackground);
m_2D.DrawEnd();
Rect rect = GUILayoutUtility.GetLastRect();
string tooltip = string.Format("{0}", displayUnits.ToString(barValue, true, 5));
GUI.Label(rect, new GUIContent("", tooltip));
}
EditorGUILayout.LabelField(ShowPercent((100 * barValue) / barMax), GUILayout.MaxWidth(50));
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
GUIContent ShowPercent(float percent)
{
string text;
string tooltip;
if (percent >= 999.95f)
{
text = string.Format("{0:f0}%", percent);
tooltip = string.Format("{0:f2}%", percent);
}
else if (percent >= 99.995f)
{
text = string.Format("{0:f1}%", percent);
tooltip = string.Format("{0:f2}%", percent);
}
else
{
text = string.Format("{0:f2}%", percent);
tooltip = text;
}
return new GUIContent(text, tooltip);
}
void DrawComparisonFrameRatio(MarkerData leftMarker, MarkerData rightMarker)
{
var leftFrameSummary = m_ProfileLeftView.analysis.GetFrameSummary();
var rightFrameSummary = m_ProfileRightView.analysis.GetFrameSummary();
GUIStyle style = GUI.skin.label;
float w = LayoutSize.WidthColumn0;
float h = style.lineHeight;
float ySpacing = 2;
float barHeight = (h - ySpacing) / 2;
EditorGUILayout.BeginVertical(GUILayout.Width(w + LayoutSize.WidthColumn1 + LayoutSize.WidthColumn2));
float leftBarValue = MarkerData.GetMsMean(leftMarker);
float rightBarValue = MarkerData.GetMsMean(rightMarker);
float leftBarMax = leftFrameSummary.msMean;
float rightBarMax = rightFrameSummary.msMean;
string text = "Mean frame contribution";
Units units = m_DisplayUnits.Units;
bool removed = MarkerData.GetTimeIgnored(leftMarker) > 0 || MarkerData.GetTimeIgnored(rightMarker) > 0;
if (DisplayCount())
{
units = Units.Count;
leftBarValue = MarkerData.GetCountMean(leftMarker);
rightBarValue = MarkerData.GetCountMean(rightMarker);
leftBarMax = leftFrameSummary.markerCountMaxMean;
rightBarMax = rightFrameSummary.markerCountMaxMean;
text = "Mean count";
}
DisplayUnits displayUnits = new DisplayUnits(units);
float leftBarLength = (leftBarMax > 0) ? (w * leftBarValue) / leftBarMax : 0f;
leftBarLength = Math.Min(leftBarLength, w);
float rightBarLength = (rightBarMax > 0) ? (w * rightBarValue) / rightBarMax : 0f;
rightBarLength = Math.Min(rightBarLength, w);
EditorGUILayout.LabelField(text);
m_Columns.SetColumnSizes(LayoutSize.WidthColumn0, LayoutSize.WidthColumn1, LayoutSize.WidthColumn2, LayoutSize.WidthColumn3);
if (removed)
m_Columns.Draw4("", "", "", "");
else
m_Columns.Draw4("", "Left", "Right", "Diff");
EditorGUILayout.BeginHorizontal();
if (removed)
{
EditorGUILayout.LabelField("(Marker Removed from analysis)");
}
else
{
if (m_2D.DrawStart(w, h, Draw2D.Origin.TopLeft, style))
{
m_2D.DrawFilledBox(0, ySpacing, w, h - ySpacing, UIColor.barBackground);
m_2D.DrawFilledBox(0, ySpacing, leftBarLength, barHeight, UIColor.left);
m_2D.DrawFilledBox(0, ySpacing + barHeight, rightBarLength, barHeight, UIColor.right);
m_2D.DrawEnd();
Rect rect = GUILayoutUtility.GetLastRect();
string tooltip = string.Format("Left: {0}\nRight: {1}", displayUnits.ToTooltipString(leftBarValue, true), displayUnits.ToTooltipString(rightBarValue, true));
GUI.Label(rect, new GUIContent("", tooltip));
}
float leftPercentage = leftBarMax > 0 ? (100 * leftBarValue) / leftBarMax : 0f;
float rightPercentage = rightBarMax > 0 ? (100 * rightBarValue) / rightBarMax : 0f;
EditorGUILayout.LabelField(ShowPercent(leftPercentage), GUILayout.Width(LayoutSize.WidthColumn1));
EditorGUILayout.LabelField(ShowPercent(rightPercentage), GUILayout.Width(LayoutSize.WidthColumn2));
if (leftMarker != null && rightMarker != null)
EditorGUILayout.LabelField(ShowPercent(rightPercentage - leftPercentage), GUILayout.Width(LayoutSize.WidthColumn3));
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
void DrawTopComparison(MarkerData leftMarker, MarkerData rightMarker)
{
GUIStyle style = GUI.skin.label;
float w = LayoutSize.WidthColumn0;
float h = style.lineHeight;
float ySpacing = 2;
float barHeight = (h - ySpacing) / 2;
EditorGUILayout.BeginVertical(GUILayout.Width(w + LayoutSize.WidthColumn1 + LayoutSize.WidthColumn2));
bool showCount = DisplayCount();
float leftMax = MarkerData.GetMsMax(leftMarker);
float rightMax = MarkerData.GetMsMax(rightMarker);
Units units = m_DisplayUnits.Units;
if (showCount)
{
units = Units.Count;
leftMax = MarkerData.GetCountMax(leftMarker);
rightMax = MarkerData.GetCountMax(rightMarker);
}
DisplayUnits displayUnits = new DisplayUnits(units);
TopMarkerList topMarkerList = new TopMarkerList(m_2D, units,
LayoutSize.WidthColumn0, LayoutSize.WidthColumn1, LayoutSize.WidthColumn2, LayoutSize.WidthColumn3,
UIColor.bar, UIColor.barBackground, DrawFrameIndexButton);
m_TopNumber = topMarkerList.DrawTopNumber(m_TopNumber, m_TopStrings, m_TopValues);
float barMax = Math.Max(leftMax, rightMax);
List<FrameTime> leftFrames = leftMarker != null ? topMarkerList.GetTopN(leftMarker, m_TopNumber, showCount) : new List<FrameTime>();
List<FrameTime> rightFrames = rightMarker != null ? topMarkerList.GetTopN(rightMarker, m_TopNumber, showCount) : new List<FrameTime>();
FrameTime zeroFrameTime = new FrameTime(-1, 0.0f, 0);
for (int i = 0; i < m_TopNumber; i++)
{
bool leftValid = i < leftFrames.Count;
bool rightValid = i < rightFrames.Count;
FrameTime leftFrameTime = leftValid ? leftFrames[i] : zeroFrameTime;
FrameTime rightFrameTime = rightValid ? rightFrames[i] : zeroFrameTime;
float leftBarValue = showCount ? leftFrameTime.count : leftFrameTime.ms;
float rightBarValue = showCount ? rightFrameTime.count : rightFrameTime.ms;
float leftBarLength = Math.Min((w * leftBarValue) / barMax, w);
float rightBarLength = Math.Min((w * rightBarValue) / barMax, w);
EditorGUILayout.BeginHorizontal();
if (m_2D.DrawStart(w, h, Draw2D.Origin.TopLeft, style))
{
if (leftValid || rightValid)
{
m_2D.DrawFilledBox(0, ySpacing, w, h - ySpacing, UIColor.barBackground);
m_2D.DrawFilledBox(0, ySpacing, leftBarLength, barHeight, UIColor.left);
m_2D.DrawFilledBox(0, ySpacing + barHeight, rightBarLength, barHeight, UIColor.right);
}
m_2D.DrawEnd();
Rect rect = GUILayoutUtility.GetLastRect();
string leftContent = leftValid ? displayUnits.ToTooltipString(leftBarValue, true, leftFrameTime.frameIndex) : "None";
string rightContent = rightValid ? displayUnits.ToTooltipString(rightBarValue, true, rightFrameTime.frameIndex) : "None";
GUI.Label(rect, new GUIContent("", string.Format("Left:\t{0}\nRight:\t{1}", leftContent, rightContent)));
}
EditorGUILayout.LabelField(leftValid ? displayUnits.ToGUIContentWithTooltips(leftBarValue, frameIndex: leftFrameTime.frameIndex) : Styles.emptyString, GUILayout.Width(LayoutSize.WidthColumn1));
EditorGUILayout.LabelField(rightValid ? displayUnits.ToGUIContentWithTooltips(rightBarValue, frameIndex: rightFrameTime.frameIndex) : Styles.emptyString, GUILayout.Width(LayoutSize.WidthColumn2));
if (leftValid || rightValid)
EditorGUILayout.LabelField(displayUnits.ToGUIContentWithTooltips(rightBarValue - leftBarValue), GUILayout.Width(LayoutSize.WidthColumn3));
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndVertical();
}
void DrawSelectedStats(MarkerData marker, ProfileDataView markerContext)
{
GUIStyle style = GUI.skin.label;
m_Columns.Draw3("", GetDisplayUnits(), "Frame");
Draw3LabelMsFrame(Styles.max, marker.msMax, marker.maxFrameIndex, markerContext);
Draw2LabelMs(Styles.upperQuartile, marker.msUpperQuartile);
Draw3LabelMsFrame(Styles.median, marker.msMedian, marker.medianFrameIndex, markerContext);
Draw2LabelMs(Styles.mean, marker.msMean);
Draw2LabelMs(Styles.lowerQuartile, marker.msLowerQuartile);
Draw3LabelMsFrame(Styles.min, marker.msMin, marker.minFrameIndex, markerContext);
GUILayout.Space(style.lineHeight);
Draw3LabelMsFrame(Styles.individualMax, marker.msMaxIndividual,
marker.maxIndividualFrameIndex, markerContext);
Draw3LabelMsFrame(Styles.individualMin, marker.msMinIndividual,
marker.minIndividualFrameIndex, markerContext);
}
void DrawSelected()
{
EditorGUILayout.BeginVertical(GUI.skin.box, GUILayout.Width(LayoutSize.WidthRHS));
bool lastMarkerSummary = m_ShowMarkerSummary;
m_ShowMarkerSummary = BoldFoldout(m_ShowMarkerSummary, Styles.markerSummary);
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
if (m_ShowMarkerSummary)
{
if (IsAnalysisValid())
{
List<MarkerData> markers = m_ProfileSingleView.analysis.GetMarkers();
if (markers != null)
{
int markerAt = m_SelectedMarker.id;
if (markerAt >= 0 && markerAt < markers.Count)
{
var marker = markers[markerAt];
m_MarkerSummaryScroll = GUILayout.BeginScrollView(m_MarkerSummaryScroll, GUIStyle.none, GUI.skin.verticalScrollbar);
Rect clipRect = new Rect(m_MarkerSummaryScroll.x, m_MarkerSummaryScroll.y, LayoutSize.WidthRHS, 500);
m_2D.SetClipRect(clipRect);
EditorGUILayout.BeginVertical();
EditorGUILayout.LabelField(marker.name,
GUILayout.MaxWidth(LayoutSize.WidthRHS -
(GUI.skin.box.padding.horizontal + GUI.skin.box.margin.horizontal)));
DrawFrameRatio(marker);
m_Columns.SetColumnSizes(LayoutSize.WidthColumn0, LayoutSize.WidthColumn1, LayoutSize.WidthColumn2, LayoutSize.WidthColumn3);
EditorGUILayout.BeginHorizontal();
m_Columns.Draw(0, Styles.firstFrame);
m_Columns.Draw(1, Styles.emptyString);
DrawFrameIndexButton(marker.firstFrameIndex, m_ProfileSingleView);
EditorGUILayout.EndHorizontal();
GUIStyle style = GUI.skin.label;
float min = marker.msMin;
float max = marker.msMax;
string fieldString = "marker time";
Units units = m_DisplayUnits.Units;
if (DisplayCount())
{
min = marker.countMin;
max = marker.countMax;
fieldString = "count";
units = Units.Count;
}
TopMarkerList topMarkerList = new TopMarkerList(m_2D, units,
LayoutSize.WidthColumn0, LayoutSize.WidthColumn1, LayoutSize.WidthColumn2, LayoutSize.WidthColumn3,
UIColor.bar, UIColor.barBackground, DrawFrameIndexButton);
m_TopNumber = topMarkerList.Draw(marker, m_ProfileSingleView, m_TopNumber, m_TopStrings, m_TopValues);
GUILayout.Space(style.lineHeight);
float plotWidth = 40 + GUI.skin.box.padding.horizontal;
float plotHeight = 100;
EditorGUILayout.BeginHorizontal();
Histogram histogram = new Histogram(m_2D, units);
DrawHistogramForMarker(histogram, marker);
BoxAndWhiskerPlot boxAndWhiskerPlot = new BoxAndWhiskerPlot(m_2D, units);
DrawBoxAndWhiskerPlotForMarker(boxAndWhiskerPlot, plotWidth, plotHeight, m_ProfileSingleView.analysis, marker,
min, max, UIColor.standardLine, UIColor.boxAndWhiskerBoxColor);
boxAndWhiskerPlot.DrawText(m_Columns.GetColumnWidth(3), plotHeight, min, max,
string.Format("Min {0} for selected frames", fieldString),
string.Format("Max {0} for selected frames", fieldString));
EditorGUILayout.EndHorizontal();
GUILayout.Space(style.lineHeight);
DrawSelectedStats(marker, m_ProfileSingleView);
EditorGUILayout.EndVertical();
m_2D.ClearClipRect();
GUILayout.EndScrollView();
}
else
{
EditorGUILayout.LabelField("Marker not in selection");
}
}
}
else
{
EditorGUILayout.LabelField("No marker data selected");
}
}
if (m_ShowMarkerSummary != lastMarkerSummary)
{
ProfileAnalyzerAnalytics.SendUIVisibilityEvent(ProfileAnalyzerAnalytics.UIVisibility.Markers, analytic.GetDurationInSeconds(), m_ShowMarkerSummary);
}
EditorGUILayout.EndVertical();
}
internal static bool FileInTempDir(string filePath)
{
return Directory.Exists(TmpDir) && Directory.GetFiles(TmpDir).Contains(filePath);
}
}
}

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,761 @@
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; }
public int MarkerNameCount => markerNames.Count;
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,172 @@
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 bool containsWaitForFPS { get; private set; }
public bool containsWaitForPresent { get; private set; }
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;
containsWaitForFPS = dataView.containsWaitForFPS;
containsWaitForPresent = dataView.containsWaitForPresent;
}
public void FindKeyMarkers()
{
containsWaitForFPS = data.GetMarkerIndex("WaitForTargetFPS") != -1;
containsWaitForPresent = data.GetMarkerIndex("Gfx.WaitForPresentOnGfxThread") != -1;
}
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,939 @@
using System;
using System.Collections.Generic;
using System.Globalization;
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;
bool m_HideRemovedMarkers;
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,
State,
Depth,
Median,
MedianBar,
Mean,
StandardDeviation,
Min,
Max,
Range,
Count,
CountBar,
CountMean,
CountMeanBar,
CountStandardDeviation,
FirstFrame,
AtMedian,
Total,
TotalBar,
Threads,
}
static int m_MaxColumns;
public enum SortOption
{
Name,
State,
Depth,
Median,
Mean,
Min,
Max,
StandardDeviation,
Range,
Count,
CountMean,
CountStandardDeviation,
FirstFrame,
AtMedian,
Total,
Threads,
}
// Sort options per column
SortOption[] m_SortOptions =
{
SortOption.Name,
SortOption.State,
SortOption.Depth,
SortOption.Median,
SortOption.Median,
SortOption.Mean,
SortOption.StandardDeviation,
SortOption.Min,
SortOption.Max,
SortOption.Range,
SortOption.Count,
SortOption.Count,
SortOption.CountMean,
SortOption.CountMean,
SortOption.CountStandardDeviation,
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 menuItemSetAsRemoveMarker = new GUIContent("Remove Marker", "");
public static readonly GUIContent menuItemCopyToClipboard = new GUIContent("Copy to Clipboard", "");
}
public ProfileTable(TreeViewState state, MultiColumnHeader multicolumnHeader, ProfileDataView dataView, bool hideRemovedMarkers, ProfileAnalyzerWindow profileAnalyzerWindow, Draw2D draw2D, Color barColor) : base(state, multicolumnHeader)
{
m_2D = draw2D;
m_DataView = dataView;
m_HideRemovedMarkers = hideRemovedMarkers;
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);
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 (!m_ProfileAnalyzerWindow.DoesMarkerPassFilter(marker.name))
continue;
if (m_HideRemovedMarkers && marker.IsFullyIgnored())
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.State:
orderedQuery = orderedQuery.ThenBy(l => State(l), 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.StandardDeviation:
orderedQuery = orderedQuery.ThenBy(l => l.data.msStandardDeviation, 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.CountStandardDeviation:
orderedQuery = orderedQuery.ThenBy(l => l.data.countStandardDeviation, 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.State:
return myTypes.Order(l => State(l), 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.StandardDeviation:
return myTypes.Order(l => l.data.msStandardDeviation, 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.CountStandardDeviation:
return myTypes.Order(l => l.data.countStandardDeviation, 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);
}
int State(ProfileTreeViewItem item)
{
if (item.data.timeRemoved > 0.0)
{
return -3;
}
if (item.data.timeIgnored > 0.0)
{
if (item.data.IsFullyIgnored())
return -2;
else
return -1;
}
return 0;
}
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("");
menu.AddItem(Styles.menuItemSetAsRemoveMarker, false, () => m_ProfileAnalyzerWindow.SetAsRemoveMarker(markerName));
menu.AddSeparator("");
if (markerName != null && !string.IsNullOrEmpty(markerName))
menu.AddItem(Styles.menuItemCopyToClipboard, false, () => CopyToClipboard(evt, markerName));
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("");
menu.AddDisabledItem(Styles.menuItemSetAsRemoveMarker);
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);
if (item.data.timeRemoved > 0.0)
{
item.cachedRowString[(int)MyColumns.Name] = new GUIContent(item.data.name + " [Modified]", item.data.name + "\n\nTime reduced by removing child marker time");
item.cachedRowString[(int)MyColumns.State] = new GUIContent("Modified", "Time reduced by removing child marker time");
}
else if (item.data.timeIgnored > 0.0)
{
if (item.data.IsFullyIgnored())
{
item.cachedRowString[(int)MyColumns.Name] = new GUIContent(item.data.name + " [Removed]", item.data.name + "\n\nAll marker time removed");
item.cachedRowString[(int)MyColumns.State] = new GUIContent("Removed", "All marker time removed");
}
else
{
item.cachedRowString[(int)MyColumns.Name] = new GUIContent(item.data.name + " [Partial Removal]", item.data.name + "\n\nSome marker time removed (some instances)");
item.cachedRowString[(int)MyColumns.State] = new GUIContent("Partial Removal", "Some marker time removed (some instances)");
}
}
else
{
item.cachedRowString[(int)MyColumns.Name] = new GUIContent(item.data.name, item.data.name);
item.cachedRowString[(int)MyColumns.State] = new GUIContent("", "");
}
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.StandardDeviation] = ToDisplayUnitsWithTooltips(item.data.msStandardDeviation, false);
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(CultureInfo.InvariantCulture, "{0:f0}", item.data.countMean), "");
item.cachedRowString[(int)MyColumns.CountMeanBar] = new GUIContent("", string.Format(CultureInfo.InvariantCulture, "{0:f0}", item.data.countMean));
item.cachedRowString[(int)MyColumns.CountStandardDeviation] = new GUIContent(string.Format(CultureInfo.InvariantCulture, "{0:f0}", item.data.countStandardDeviation), string.Format(CultureInfo.InvariantCulture, "{0}", item.data.countStandardDeviation));
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.State:
case MyColumns.Mean:
case MyColumns.Depth:
case MyColumns.Median:
case MyColumns.StandardDeviation:
case MyColumns.Min:
case MyColumns.Max:
case MyColumns.Range:
case MyColumns.Count:
case MyColumns.CountMean:
case MyColumns.CountStandardDeviation:
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("State", "Status of marker entry (if modified or removed from frame time due to 'Remove' filter)"),
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("SD", "Standard deviation in marker times"),
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("Count SD", "Standard deviation in marker per frame counts"),
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,854 @@
using UnityEditorInternal;
using System.Reflection;
using System;
using System.Collections;
using UnityEngine;
using System.Collections.Generic;
using System.IO;
using UnityEngine.Profiling;
using UnityEditor.Profiling;
#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;
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;
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 (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;
}
}
}
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();
using (RawFrameDataView frameData = ProfilerDriver.GetRawFrameDataView(frameIndex, threadIndex))
{
frame.msStartTime = frameData.frameStartTimeMs;
frame.msFrame = frameData.frameTimeMs;
}
return frame;
}
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;
}
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);
GetDataRaw(data, firstFrameIndex, lastFrameIndex);
data.Write();
return data;
}
public float GetFrameTimeRaw(int frameIndex)
{
using (RawFrameDataView frameData = ProfilerDriver.GetRawFrameDataView(frameIndex, 0))
{
if (!frameData.valid)
return 0f;
return frameData.frameTimeMs;
}
}
public float GetFrameTime(int frameIndex)
{
return GetFrameTimeRaw(frameIndex);
}
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()
{
return ProfilerDriver.enabled;
}
public void StopRecording()
{
// Stop recording first
ProfilerDriver.enabled = false;
}
public void StartRecording()
{
// Stop recording first
ProfilerDriver.enabled = true;
}
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,64 @@
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>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Compare(ThreadFrameTime x, ThreadFrameTime y)
{
if (x.frameIndex == y.frameIndex)
return 0;
return x.frameIndex > y.frameIndex ? 1 : -1;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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,959 @@
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);
this.foldoutOverride += DoFoldout;
Reload();
}
bool DoFoldout(Rect position, bool expandedstate, GUIStyle style)
{
return !(position.y < rowHeight) && GUI.Toggle(position, expandedstate, GUIContent.none, style);
}
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
{
private static HashSet<ThreadSelectionWindow> s_Instances = new HashSet<ThreadSelectionWindow>();
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()
{
return s_Instances.Count > 0;
}
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;
s_Instances.Add(this);
}
private void OnDisable()
{
s_Instances.Remove(this);
}
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,896 @@
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 readonly bool hideRemovedMarkers;
public RangeSettings(ProfileDataView dataView, int depthFilter, List<string> nameFilters, List<string> nameExcludes, TimingOptions.TimingOption timingOption, int threadSelectionCount, bool hideRemovedMarkers)
{
// 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;
this.hideRemovedMarkers = hideRemovedMarkers;
}
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();
hash = (hash * 7) + hideRemovedMarkers.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.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 (hideRemovedMarkers != b.hideRemovedMarkers)
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 menuItemSetAsRemoveMarker = new GUIContent("Remove Marker", "");
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, false), 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, bool hideRemovedMarkers)
{
m_RequestedRangeSettings = new RangeSettings(dataView, depthFilter, nameFilters, nameExcludes, timingOption, threadSelectionCount, hideRemovedMarkers);
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;
bool hideRemovedMarkers = rangeSettings.hideRemovedMarkers;
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.NameInIncludeList(marker.name, nameFilters))
continue;
}
if (nameExcludes.Count > 0)
{
if (m_ProfileAnalyzerWindow.NameInExcludeList(marker.name, nameExcludes))
continue;
}
if (hideRemovedMarkers && marker.IsFullyIgnored())
{
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;
bool hideRemovedMarkers = m_CurrentSettings.rangeSettings.hideRemovedMarkers;
// 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);
// We do this at the top so that totalMarkerTime is not increased
// This excludes the hidden markers time
if (hideRemovedMarkers && marker.IsFullyIgnored())
{
continue;
}
if (depthFilter != ProfileAnalyzer.kDepthAll && marker.minDepth != depthFilter)
{
continue;
}
if (nameFilters.Count > 0)
{
if (!m_ProfileAnalyzerWindow.NameInIncludeList(marker.name, nameFilters))
continue;
}
if (nameExcludes.Count > 0)
{
if (m_ProfileAnalyzerWindow.NameInExcludeList(marker.name, nameExcludes))
continue;
}
totalMarkerTime += msAtMedian;
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.menuItemSetAsRemoveMarker, false, () => m_ProfileAnalyzerWindow.SetAsRemoveMarker(markerName));
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.menuItemSetAsRemoveMarker);
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;
string 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)
{
if (entry.summaryType == SummaryType.Marker)
{
DrawEntryBarText(rect, width, timeRange, entry, leftAlignStyle, frameSummaryMedianFrameIndex, selectedText, centreAlignStyle, selectedPairingMarkerName);
}
else
{
// Note this only displays the tooltip and not the text itself as we assume unaccounted is small
DrawSimpleEntryBarText(rect, width, timeRange, entry, 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;
}
void DrawEntryBarText(Rect rect, float totalWidth, float timeRange, MarkerSummaryEntry entry, GUIStyle leftAlignStyle, int frameSummaryMedianFrameIndex, Color selectedText, GUIStyle centreAlignStyle, string selectedPairingMarkerName)
{
float x = entry.x * totalWidth;
float w = entry.w * totalWidth;
String name = entry.name;
float msAtMedian = entry.msAtMedian;
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();
}
}
}
void DrawSimpleEntryBarText(Rect rect, float totalWidth, float timeRange, MarkerSummaryEntry entry, GUIStyle leftAlignStyle, int frameSummaryMedianFrameIndex)
{
float x = entry.x * totalWidth;
float w = entry.w * totalWidth;
String name = entry.name;
float msAtMedian = entry.msAtMedian;
float width = rect.width;
Rect labelRect = new Rect(rect.x + x, rect.y, w, rect.height);
float percent = msAtMedian / timeRange * 100;
GUIStyle style = leftAlignStyle;
string tooltip = string.Format("{0}\n{1:f2}% ({2} on median frame {3})",
name,
percent,
ToDisplayUnits(msAtMedian, true, 0),
frameSummaryMedianFrameIndex);
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();
}
}
}
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,237 @@
using System;
using System.Globalization;
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(CultureInfo.InvariantCulture, "{0}", value);
}
else
{
string formatString = string.Concat("{0:f", numberOfDecimalPlaces, "}");
numberStr = string.Format(CultureInfo.InvariantCulture, 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: