initial commit
This commit is contained in:
parent
6715289efe
commit
788c3389af
37645 changed files with 2526849 additions and 80 deletions
|
@ -0,0 +1,391 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor.Analytics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Analytics;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
class ProfileAnalyzerAnalytics
|
||||
{
|
||||
const int k_MaxEventsPerHour = 100;
|
||||
const int k_MaxEventItems = 1000;
|
||||
const string k_VendorKey = "unity.profileanalyzer";
|
||||
const string k_EventTopicName = "usability";
|
||||
|
||||
static bool s_EnableAnalytics = false;
|
||||
|
||||
public static void EnableAnalytics()
|
||||
{
|
||||
#if UNITY_2018_1_OR_NEWER
|
||||
AnalyticsResult result = EditorAnalytics.RegisterEventWithLimit(k_EventTopicName, k_MaxEventsPerHour, k_MaxEventItems, k_VendorKey);
|
||||
if (result == AnalyticsResult.Ok)
|
||||
s_EnableAnalytics = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
public enum UIButton
|
||||
{
|
||||
Pull,
|
||||
OpenProfiler,
|
||||
CloseProfiler,
|
||||
JumpToFrame,
|
||||
ExportSingleFrames,
|
||||
ExportComparisonFrames,
|
||||
};
|
||||
|
||||
public enum UIUsageMode
|
||||
{
|
||||
Single,
|
||||
Comparison,
|
||||
};
|
||||
|
||||
public enum UIVisibility
|
||||
{
|
||||
FrameTimeContextMenu,
|
||||
Filters,
|
||||
TopTen,
|
||||
Frames,
|
||||
Threads,
|
||||
Markers,
|
||||
};
|
||||
|
||||
public enum UIResizeView
|
||||
{
|
||||
Single,
|
||||
Comparison,
|
||||
};
|
||||
|
||||
|
||||
[Serializable]
|
||||
struct ProfileAnalyzerUIButtonEventParameters
|
||||
{
|
||||
public string name;
|
||||
|
||||
public ProfileAnalyzerUIButtonEventParameters(string name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
// camelCase since these events get serialized to Json and naming convention in analytics is camelCase
|
||||
[Serializable]
|
||||
struct ProfileAnalyzerUIButtonEvent
|
||||
{
|
||||
public ProfileAnalyzerUIButtonEvent(string name, float durationInTicks)
|
||||
{
|
||||
subtype = "profileAnalyzerUIButton";
|
||||
|
||||
// ts is auto added so no need to include it here
|
||||
//ts = (int)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
|
||||
this.duration = durationInTicks;
|
||||
|
||||
parameters = new ProfileAnalyzerUIButtonEventParameters(name);
|
||||
}
|
||||
|
||||
public string subtype;
|
||||
//public int ts;
|
||||
public float duration; // Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
|
||||
public ProfileAnalyzerUIButtonEventParameters parameters;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
struct ProfileAnalyzerUIUsageEventParameters
|
||||
{
|
||||
public string name;
|
||||
|
||||
public ProfileAnalyzerUIUsageEventParameters(string name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
struct ProfileAnalyzerUIUsageEvent
|
||||
{
|
||||
public ProfileAnalyzerUIUsageEvent(string name, float durationInTicks)
|
||||
{
|
||||
subtype = "profileAnalyzerModeUsage";
|
||||
|
||||
this.duration = durationInTicks;
|
||||
|
||||
parameters = new ProfileAnalyzerUIUsageEventParameters(name);
|
||||
}
|
||||
|
||||
public string subtype;
|
||||
public float duration; // Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
|
||||
public ProfileAnalyzerUIUsageEventParameters parameters;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
struct ProfileAnalyzerUIVisibilityEventParameters
|
||||
{
|
||||
public string name;
|
||||
public bool show;
|
||||
|
||||
public ProfileAnalyzerUIVisibilityEventParameters(string name, bool show)
|
||||
{
|
||||
this.name = name;
|
||||
this.show = show;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
struct ProfileAnalyzerUIVisibilityEvent
|
||||
{
|
||||
public ProfileAnalyzerUIVisibilityEvent(string name, float durationInTicks, bool show)
|
||||
{
|
||||
subtype = "profileAnalyzerUIVisibility";
|
||||
|
||||
this.duration = durationInTicks;
|
||||
|
||||
parameters = new ProfileAnalyzerUIVisibilityEventParameters(name, show);
|
||||
}
|
||||
|
||||
public string subtype;
|
||||
public float duration; // Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
|
||||
public ProfileAnalyzerUIVisibilityEventParameters parameters;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
struct ProfileAnalyzerUIResizeEventParameters
|
||||
{
|
||||
public string name;
|
||||
public float width;
|
||||
public float height;
|
||||
public float screenWidth;
|
||||
public float screenHeight;
|
||||
public bool docked;
|
||||
|
||||
public ProfileAnalyzerUIResizeEventParameters(string name, float width, float height, float screenWidth, float screenHeight, bool isDocked)
|
||||
{
|
||||
this.name = name;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.screenWidth = screenWidth;
|
||||
this.screenHeight = screenHeight;
|
||||
docked = isDocked;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
struct ProfileAnalyzerUIResizeEvent
|
||||
{
|
||||
public ProfileAnalyzerUIResizeEvent(string name, float durationInTicks, float width, float height, float screenWidth, float screenHeight, bool isDocked)
|
||||
{
|
||||
subtype = "profileAnalyzerUIResize";
|
||||
|
||||
this.duration = durationInTicks;
|
||||
|
||||
parameters = new ProfileAnalyzerUIResizeEventParameters(name, width, height, screenWidth, screenHeight, isDocked);
|
||||
}
|
||||
|
||||
public string subtype;
|
||||
public float duration; // Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
|
||||
public ProfileAnalyzerUIResizeEventParameters parameters;
|
||||
}
|
||||
|
||||
static float SecondsToTicks(float durationInSeconds)
|
||||
{
|
||||
return durationInSeconds * 10000;
|
||||
}
|
||||
|
||||
public static bool SendUIButtonEvent(UIButton uiButton, float durationInSeconds)
|
||||
{
|
||||
if (!s_EnableAnalytics)
|
||||
return false;
|
||||
|
||||
#if UNITY_2018_1_OR_NEWER
|
||||
// Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
|
||||
float durationInTicks = SecondsToTicks(durationInSeconds);
|
||||
|
||||
ProfileAnalyzerUIButtonEvent uiButtonEvent;
|
||||
switch (uiButton)
|
||||
{
|
||||
case UIButton.Pull:
|
||||
uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerGrab", durationInTicks);
|
||||
break;
|
||||
case UIButton.OpenProfiler:
|
||||
uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerOpenProfiler", durationInTicks);
|
||||
break;
|
||||
case UIButton.CloseProfiler:
|
||||
uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerCloseProfiler", durationInTicks);
|
||||
break;
|
||||
case UIButton.JumpToFrame:
|
||||
uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerJumpToFrame", durationInTicks);
|
||||
break;
|
||||
case UIButton.ExportSingleFrames:
|
||||
uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerExportSingleFrames", durationInTicks);
|
||||
break;
|
||||
case UIButton.ExportComparisonFrames:
|
||||
uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerExportComparisonFrames", durationInTicks);
|
||||
break;
|
||||
default:
|
||||
Debug.LogFormat("SendUIButtonEvent: Unsupported button type : {0}", uiButton);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
AnalyticsResult result = EditorAnalytics.SendEventWithLimit(k_EventTopicName, uiButtonEvent);
|
||||
if (result != AnalyticsResult.Ok)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool SendUIUsageModeEvent(UIUsageMode uiUsageMode, float durationInSeconds)
|
||||
{
|
||||
if (!s_EnableAnalytics)
|
||||
return false;
|
||||
|
||||
#if UNITY_2018_1_OR_NEWER
|
||||
// Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
|
||||
float durationInTicks = SecondsToTicks(durationInSeconds);
|
||||
|
||||
ProfileAnalyzerUIUsageEvent uiUsageEvent;
|
||||
switch (uiUsageMode)
|
||||
{
|
||||
case UIUsageMode.Single:
|
||||
uiUsageEvent = new ProfileAnalyzerUIUsageEvent("profileAnalyzerSingle", durationInTicks);
|
||||
break;
|
||||
case UIUsageMode.Comparison:
|
||||
uiUsageEvent = new ProfileAnalyzerUIUsageEvent("profileAnalyzerCompare", durationInTicks);
|
||||
break;
|
||||
default:
|
||||
Debug.LogFormat("SendUsageEvent: Unsupported usage mode : {0}", uiUsageMode);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
AnalyticsResult result = EditorAnalytics.SendEventWithLimit(k_EventTopicName, uiUsageEvent);
|
||||
if (result != AnalyticsResult.Ok)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool SendUIVisibilityEvent(UIVisibility uiVisibility, float durationInSeconds, bool show)
|
||||
{
|
||||
if (!s_EnableAnalytics)
|
||||
return false;
|
||||
|
||||
#if UNITY_2018_1_OR_NEWER
|
||||
// Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
|
||||
float durationInTicks = SecondsToTicks(durationInSeconds);
|
||||
|
||||
ProfileAnalyzerUIVisibilityEvent uiUsageEvent;
|
||||
switch (uiVisibility)
|
||||
{
|
||||
case UIVisibility.FrameTimeContextMenu:
|
||||
uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerFrameTimeContextMenu", durationInTicks, show);
|
||||
break;
|
||||
case UIVisibility.Filters:
|
||||
uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerFilters", durationInTicks, show);
|
||||
break;
|
||||
case UIVisibility.TopTen:
|
||||
uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerTopTen", durationInTicks, show);
|
||||
break;
|
||||
case UIVisibility.Frames:
|
||||
uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerFrames", durationInTicks, show);
|
||||
break;
|
||||
case UIVisibility.Threads:
|
||||
uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerThreads", durationInTicks, show);
|
||||
break;
|
||||
case UIVisibility.Markers:
|
||||
uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerMarkers", durationInTicks, show);
|
||||
break;
|
||||
default:
|
||||
Debug.LogFormat("SendUIVisibilityEvent: Unsupported visibililty item : {0}", uiVisibility);
|
||||
return false;
|
||||
}
|
||||
|
||||
AnalyticsResult result = EditorAnalytics.SendEventWithLimit(k_EventTopicName, uiUsageEvent);
|
||||
if (result != AnalyticsResult.Ok)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool SendUIResizeEvent(UIResizeView uiResizeView, float durationInSeconds, float width, float height, bool isDocked)
|
||||
{
|
||||
if (!s_EnableAnalytics)
|
||||
return false;
|
||||
|
||||
#if UNITY_2018_1_OR_NEWER
|
||||
// Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
|
||||
float durationInTicks = SecondsToTicks(durationInSeconds);
|
||||
|
||||
ProfileAnalyzerUIResizeEvent uiResizeEvent;
|
||||
switch (uiResizeView)
|
||||
{
|
||||
case UIResizeView.Single:
|
||||
// Screen.width, Screen.height is game view size
|
||||
uiResizeEvent = new ProfileAnalyzerUIResizeEvent("profileAnalyzerSingle", durationInTicks, width, height, Screen.currentResolution.width, Screen.currentResolution.height, isDocked);
|
||||
break;
|
||||
case UIResizeView.Comparison:
|
||||
uiResizeEvent = new ProfileAnalyzerUIResizeEvent("profileAnalyzerCompare", durationInTicks, width, height, Screen.currentResolution.width, Screen.currentResolution.height, isDocked);
|
||||
break;
|
||||
default:
|
||||
Debug.LogFormat("SendUIResizeEvent: Unsupported view : {0}", uiResizeView);
|
||||
return false;
|
||||
}
|
||||
|
||||
AnalyticsResult result = EditorAnalytics.SendEventWithLimit(k_EventTopicName, uiResizeEvent);
|
||||
if (result != AnalyticsResult.Ok)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
internal class Analytic
|
||||
{
|
||||
double m_StartTime;
|
||||
float m_DurationInSeconds;
|
||||
|
||||
public Analytic()
|
||||
{
|
||||
m_StartTime = EditorApplication.timeSinceStartup;
|
||||
m_DurationInSeconds = 0;
|
||||
}
|
||||
|
||||
public void End()
|
||||
{
|
||||
m_DurationInSeconds = (float)(EditorApplication.timeSinceStartup - m_StartTime);
|
||||
}
|
||||
|
||||
public float GetDurationInSeconds()
|
||||
{
|
||||
return m_DurationInSeconds;
|
||||
}
|
||||
}
|
||||
|
||||
static public Analytic BeginAnalytic()
|
||||
{
|
||||
return new Analytic();
|
||||
}
|
||||
|
||||
static public void SendUIButtonEvent(UIButton uiButton, Analytic instance)
|
||||
{
|
||||
instance.End();
|
||||
SendUIButtonEvent(uiButton, instance.GetDurationInSeconds());
|
||||
}
|
||||
|
||||
static public void SendUIUsageModeEvent(UIUsageMode uiUsageMode, Analytic instance)
|
||||
{
|
||||
instance.End();
|
||||
SendUIUsageModeEvent(uiUsageMode, instance.GetDurationInSeconds());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: adf9820979228054693ce2e8224012fb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,3 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Unity.Performance.Profile-Analyzer.EditorTests")]
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: dcb585fc3518ff949912f2d4fff2e26f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ae5c414d7d406467d8ce01807c211f90
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a402cbe9b84984bd4a16cb20ca9c3bed
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 281fa44557d2f417aafc937bd5d93ba5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,130 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
public class DepthSliceDropdown : AdvancedDropdown
|
||||
{
|
||||
class DepthSliceDropdownItem : AdvancedDropdownItem
|
||||
{
|
||||
public int depthSlice;
|
||||
public int depthSliceLeft;
|
||||
public int depthSliceRight;
|
||||
public DepthSliceDropdownItem(int depthSlice)
|
||||
: base(DepthSliceUI.DepthFilterToString(depthSlice))
|
||||
{
|
||||
this.depthSlice = depthSlice;
|
||||
depthSliceLeft = depthSlice;
|
||||
depthSliceRight = depthSlice;
|
||||
}
|
||||
public DepthSliceDropdownItem(int depthSliceLeft, int depthSliceRight, bool leftIsMain)
|
||||
: base(DepthSliceUI.DepthFilterToString(depthSliceLeft, depthSliceRight, leftIsMain))
|
||||
{
|
||||
depthSlice = Math.Max(depthSliceLeft, depthSliceRight);
|
||||
this.depthSliceLeft = depthSliceLeft;
|
||||
this.depthSliceRight = depthSliceRight;
|
||||
}
|
||||
}
|
||||
|
||||
Action<int, int, int> m_Callback = null;
|
||||
int m_DepthSliceCount;
|
||||
int m_DepthSliceCountRight;
|
||||
int m_CurrentDepthSliceA;
|
||||
int m_CurrentDepthSliceB;
|
||||
int m_DepthDiff;
|
||||
|
||||
static FieldInfo m_DataSourceFieldInfo;
|
||||
static Type m_DataSourceTypeInfo;
|
||||
static PropertyInfo m_SelectedIdsFieldInfo;
|
||||
|
||||
public DepthSliceDropdown(int depthSliceCount, int currentDepthSliceA, int currentDepthSliceB, Action<int, int, int> callback, int depthDiff, int depthSliceCountRight = ProfileAnalyzer.kDepthAll) : base(new AdvancedDropdownState())
|
||||
{
|
||||
m_DepthSliceCount = depthSliceCount;
|
||||
m_DepthSliceCountRight = depthSliceCountRight;
|
||||
m_CurrentDepthSliceA = currentDepthSliceA;
|
||||
m_CurrentDepthSliceB = currentDepthSliceB;
|
||||
m_Callback = callback;
|
||||
m_DepthDiff = depthDiff;
|
||||
if (m_DataSourceFieldInfo == null || m_DataSourceFieldInfo == null || m_SelectedIdsFieldInfo == null)
|
||||
{
|
||||
Assembly assem = typeof(AdvancedDropdown).Assembly;
|
||||
var advancedDropdownTypeInfo = typeof(AdvancedDropdown);
|
||||
m_DataSourceTypeInfo = assem.GetType("UnityEditor.IMGUI.Controls.CallbackDataSource");
|
||||
m_DataSourceFieldInfo = advancedDropdownTypeInfo.GetField("m_DataSource", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedIdsFieldInfo = m_DataSourceTypeInfo.GetProperty("selectedIDs", BindingFlags.Public | BindingFlags.Instance);
|
||||
}
|
||||
}
|
||||
|
||||
protected override AdvancedDropdownItem BuildRoot()
|
||||
{
|
||||
var root = new AdvancedDropdownItem("Depth Slice");
|
||||
var allItem = new DepthSliceDropdownItem(ProfileAnalyzer.kDepthAll);
|
||||
root.AddChild(allItem);
|
||||
if (m_CurrentDepthSliceA == ProfileAnalyzer.kDepthAll && m_CurrentDepthSliceB == ProfileAnalyzer.kDepthAll)
|
||||
(m_SelectedIdsFieldInfo.GetValue(m_DataSourceFieldInfo.GetValue(this)) as List<int>).Add(allItem.id);
|
||||
var count = m_DepthSliceCountRight == ProfileAnalyzer.kDepthAll ? m_DepthSliceCount :
|
||||
Math.Max(m_DepthSliceCount + Math.Max(0, m_DepthDiff), m_DepthSliceCountRight - Math.Min(0, m_DepthDiff));
|
||||
|
||||
var leftIsMain = m_DepthDiff < 0;
|
||||
var mainThreshold = leftIsMain ? m_DepthSliceCount : m_DepthSliceCountRight;
|
||||
var secondaryMinThreshold = Math.Abs(m_DepthDiff);
|
||||
var secondaryMaxThreshold = (leftIsMain ? m_DepthSliceCountRight : m_DepthSliceCount) + secondaryMinThreshold;
|
||||
|
||||
var startIndex = 1;
|
||||
for (int i = startIndex; i <= count; i++)
|
||||
{
|
||||
var selected = false;
|
||||
AdvancedDropdownItem child;
|
||||
if (m_DepthSliceCountRight != ProfileAnalyzer.kDepthAll)
|
||||
{
|
||||
var left = Mathf.Clamp(i - Math.Max(0, m_DepthDiff), 1, m_DepthSliceCount);
|
||||
var right = Mathf.Clamp(i - Math.Max(0, -m_DepthDiff), 1, m_DepthSliceCountRight);
|
||||
|
||||
if (m_DepthSliceCount <= 0)
|
||||
left = -1;
|
||||
else if (m_DepthSliceCountRight <= 0)
|
||||
right = -1;
|
||||
else
|
||||
{
|
||||
// Separators only make sense if there is data on both sides
|
||||
|
||||
// did we pass the threshold of the main's max depth and started clamping it down?
|
||||
if (i == mainThreshold + 1
|
||||
// ... or the threshold of the secondary's negative depth when adjusted for the depth diff, and stoped clamping it up?
|
||||
|| (secondaryMinThreshold != 0 && i == secondaryMinThreshold + 1)
|
||||
// ... or the threshold of the secondary's max depth when adjusted for the depth diff, and started clamping it down?
|
||||
|| (i == secondaryMaxThreshold + 1))
|
||||
root.AddSeparator();
|
||||
}
|
||||
|
||||
child = new DepthSliceDropdownItem(left, right, leftIsMain);
|
||||
selected = m_CurrentDepthSliceA == left && m_CurrentDepthSliceB == right;
|
||||
}
|
||||
else
|
||||
{
|
||||
child = new DepthSliceDropdownItem(i);
|
||||
selected = m_CurrentDepthSliceA == i;
|
||||
}
|
||||
root.AddChild(child);
|
||||
if (selected)
|
||||
(m_SelectedIdsFieldInfo.GetValue(m_DataSourceFieldInfo.GetValue(this)) as List<int>).Add(child.id);
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
protected override void ItemSelected(AdvancedDropdownItem item)
|
||||
{
|
||||
base.ItemSelected(item);
|
||||
if (m_Callback != null)
|
||||
{
|
||||
var sliceItem = (item as DepthSliceDropdownItem);
|
||||
m_Callback(sliceItem.depthSlice, sliceItem.depthSliceLeft, sliceItem.depthSliceRight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 71b9e5c19de243c386bd048443d1c5cc
|
||||
timeCreated: 1608205585
|
|
@ -0,0 +1,692 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Video;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
[Serializable]
|
||||
public class DepthSliceUI
|
||||
{
|
||||
#if !UNITY_2019_1_OR_NEWER
|
||||
[NonSerialized]
|
||||
string[] m_DepthStrings;
|
||||
[NonSerialized]
|
||||
int[] m_DepthValues;
|
||||
[NonSerialized]
|
||||
string[] m_DepthStringsAuto;
|
||||
[NonSerialized]
|
||||
int[] m_DepthValuesAuto;
|
||||
[NonSerialized]
|
||||
int m_OldDepthRaw1;
|
||||
[NonSerialized]
|
||||
int m_OldDepthRaw2;
|
||||
[NonSerialized]
|
||||
string[] m_DepthStrings1;
|
||||
[NonSerialized]
|
||||
int[] m_DepthValues1;
|
||||
[NonSerialized]
|
||||
string[] m_DepthStrings2;
|
||||
[NonSerialized]
|
||||
int[] m_DepthValues2;
|
||||
#endif
|
||||
|
||||
[SerializeField] int m_DepthFilter = ProfileAnalyzer.kDepthAll;
|
||||
public int depthFilter {get { return m_DepthFilter; }}
|
||||
|
||||
[SerializeField] int m_DepthFilter1 = ProfileAnalyzer.kDepthAll;
|
||||
public int depthFilter1 {get { return m_DepthFilter1; }}
|
||||
|
||||
[SerializeField] int m_DepthFilter2 = ProfileAnalyzer.kDepthAll;
|
||||
public int depthFilter2 {get { return m_DepthFilter2; }}
|
||||
|
||||
[SerializeField] bool m_DepthFilterAuto = true;
|
||||
|
||||
[SerializeField] int m_MostCommonDepthDiff = 0;
|
||||
|
||||
int mostCommonDepthDiff
|
||||
{
|
||||
set
|
||||
{
|
||||
if (m_MostCommonDepthDiff != value)
|
||||
{
|
||||
m_MostCommonDepthDiff = value;
|
||||
UpdateAutoDepthTitleText();
|
||||
}
|
||||
}
|
||||
get
|
||||
{
|
||||
return m_MostCommonDepthDiff;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAutoDepthTitleText()
|
||||
{
|
||||
ProfileAnalyzerWindow.Styles.autoDepthTitle.text =
|
||||
string.Format(ProfileAnalyzerWindow.Styles.autoDepthTitleText, mostCommonDepthDiff);
|
||||
}
|
||||
|
||||
Action<bool> m_UpdateActiveTabCallback = null;
|
||||
public DepthSliceUI(Action<bool> updateActiveTabCallback)
|
||||
{
|
||||
m_UpdateActiveTabCallback = updateActiveTabCallback;
|
||||
UpdateAutoDepthTitleText();
|
||||
}
|
||||
|
||||
public void OnEnable(Action<bool> updateActiveTabCallback)
|
||||
{
|
||||
m_UpdateActiveTabCallback = updateActiveTabCallback;
|
||||
UpdateAutoDepthTitleText();
|
||||
}
|
||||
|
||||
#if !UNITY_2019_1_OR_NEWER
|
||||
void SetDepthStringsSingle(int maxDepth, out string[] strings, out int[] values)
|
||||
{
|
||||
var count = maxDepth;
|
||||
|
||||
List<string> depthStrings = new List<string>();
|
||||
List<int> depthValues = new List<int>();
|
||||
depthStrings.Add(DepthFilterToString(ProfileAnalyzer.kDepthAll));
|
||||
depthValues.Add(ProfileAnalyzer.kDepthAll);
|
||||
|
||||
var startIndex = 1;
|
||||
var depthValue = 1;
|
||||
for (int depth = startIndex; depth <= count; depth++)
|
||||
{
|
||||
depthStrings.Add(DepthFilterToString(depth));
|
||||
depthValues.Add(depthValue++);
|
||||
}
|
||||
strings = depthStrings.ToArray();
|
||||
values = depthValues.ToArray();
|
||||
}
|
||||
|
||||
void SetDepthStringsCompare(int maxDepth, out string[] strings, out int[] values, int maxDepthRight = ProfileAnalyzer.kDepthAll)
|
||||
{
|
||||
var locked = maxDepthRight != ProfileAnalyzer.kDepthAll;
|
||||
var count = locked ? Math.Max(maxDepth + Math.Max(0, mostCommonDepthDiff), maxDepthRight - Math.Min(0, mostCommonDepthDiff)) : maxDepth;
|
||||
|
||||
List<string> depthStrings = new List<string>();
|
||||
List<int> depthValues = new List<int>();
|
||||
depthStrings.Add(DepthFilterToString(ProfileAnalyzer.kDepthAll));
|
||||
depthValues.Add(ProfileAnalyzer.kDepthAll);
|
||||
var leftIsMain = mostCommonDepthDiff < 0;
|
||||
|
||||
var startIndex = 1;
|
||||
var depthValue = 1;
|
||||
if (maxDepth <= 0 && mostCommonDepthDiff < 0)
|
||||
{
|
||||
depthValue = 1;
|
||||
startIndex = -mostCommonDepthDiff + 1;
|
||||
}
|
||||
else if(maxDepthRight <= 0 && mostCommonDepthDiff > 0)
|
||||
{
|
||||
depthValue = 1;
|
||||
startIndex = mostCommonDepthDiff + 1;
|
||||
}
|
||||
for (int depth = startIndex; depth <= count; depth++)
|
||||
{
|
||||
if (locked)
|
||||
{
|
||||
var left = Mathf.Clamp(depth - Math.Max(0, mostCommonDepthDiff), 1, maxDepth);
|
||||
var right = Mathf.Clamp(depth - Math.Max(0, -mostCommonDepthDiff), 1, maxDepthRight);
|
||||
|
||||
if (maxDepth <= 0)
|
||||
left = -1;
|
||||
if (maxDepthRight <= 0)
|
||||
right = -1;
|
||||
|
||||
depthStrings.Add(DepthFilterToString(left, right, leftIsMain));
|
||||
}
|
||||
else
|
||||
depthStrings.Add(DepthFilterToString(depth));
|
||||
depthValues.Add(depthValue++);
|
||||
}
|
||||
strings = depthStrings.ToArray();
|
||||
values = depthValues.ToArray();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
enum ViewType
|
||||
{
|
||||
Single,
|
||||
Left,
|
||||
Right,
|
||||
Locked,
|
||||
}
|
||||
void DrawDepthFilterDropdown(GUIContent title, bool enabled, ProfileDataView view, Action<int, int, int> callback,
|
||||
ViewType viewType, ProfileDataView profileSingleView, ProfileDataView profileLeftView, ProfileDataView profileRightView)
|
||||
{
|
||||
if(title !=null)
|
||||
EditorGUILayout.LabelField(title, GUILayout.Width(ProfileAnalyzerWindow.LayoutSize.FilterOptionsEnumWidth));
|
||||
|
||||
int depthFilter = ProfileAnalyzer.kDepthAll;
|
||||
int depthFilterOther = ProfileAnalyzer.kDepthAll;
|
||||
var maxDepth = view.GetMaxDepth();
|
||||
var maxDepthLeft = ProfileAnalyzer.kDepthAll;
|
||||
var maxDepthRight = ProfileAnalyzer.kDepthAll;
|
||||
|
||||
var oldDepthFilter = ProfileAnalyzer.kDepthAll;
|
||||
var oldDepthFilterOtherLocked = ProfileAnalyzer.kDepthAll;
|
||||
var depthDiff = mostCommonDepthDiff;
|
||||
GUIContent content;
|
||||
switch (viewType)
|
||||
{
|
||||
case ViewType.Single:
|
||||
oldDepthFilter = m_DepthFilter;
|
||||
depthFilter = m_DepthFilter =
|
||||
m_DepthFilter == ProfileAnalyzer.kDepthAll ?
|
||||
ProfileAnalyzer.kDepthAll :
|
||||
profileSingleView.ClampToValidDepthValue(m_DepthFilter);
|
||||
content = new GUIContent(DepthFilterToString(depthFilter));
|
||||
depthFilterOther = depthFilter;
|
||||
depthDiff = 0;
|
||||
break;
|
||||
case ViewType.Left:
|
||||
oldDepthFilter = m_DepthFilter1;
|
||||
depthFilter = m_DepthFilter1 =
|
||||
m_DepthFilter1 == ProfileAnalyzer.kDepthAll ?
|
||||
ProfileAnalyzer.kDepthAll :
|
||||
profileLeftView.ClampToValidDepthValue(m_DepthFilter1);
|
||||
content = new GUIContent(DepthFilterToString(depthFilter));
|
||||
depthFilterOther = depthFilter;
|
||||
break;
|
||||
case ViewType.Right:
|
||||
oldDepthFilter = m_DepthFilter2;
|
||||
depthFilter = m_DepthFilter2 = m_DepthFilter2 == ProfileAnalyzer.kDepthAll
|
||||
? ProfileAnalyzer.kDepthAll
|
||||
: profileRightView.ClampToValidDepthValue(m_DepthFilter2);
|
||||
content = new GUIContent(DepthFilterToString(depthFilter));
|
||||
depthFilterOther = depthFilter;
|
||||
break;
|
||||
case ViewType.Locked:
|
||||
oldDepthFilter = m_DepthFilter1;
|
||||
oldDepthFilterOtherLocked = m_DepthFilter2;
|
||||
maxDepth = maxDepthLeft = profileLeftView.GetMaxDepth();
|
||||
maxDepthRight = profileRightView.GetMaxDepth();
|
||||
|
||||
ClampDepthFilterForAutoRespectingDiff(ref m_DepthFilter1, ref m_DepthFilter2, profileLeftView, profileRightView);
|
||||
|
||||
depthFilter = m_DepthFilter1;
|
||||
depthFilterOther = m_DepthFilter2;
|
||||
content = new GUIContent(DepthFilterToString(m_DepthFilter1, m_DepthFilter2, mostCommonDepthDiff < 0));
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
var lastEnabled = GUI.enabled;
|
||||
GUI.enabled = enabled;
|
||||
var rect = GUILayoutUtility.GetRect(content, EditorStyles.popup, GUILayout.MinWidth(ProfileAnalyzerWindow.LayoutSize.FilterOptionsEnumWidth));
|
||||
if (GUI.Button(rect, content, EditorStyles.popup))
|
||||
{
|
||||
var dropdown = new DepthSliceDropdown(maxDepth, depthFilter, depthFilterOther, (slice, left, right) =>
|
||||
{
|
||||
if (slice != depthFilter || (viewType == ViewType.Locked && (left != m_DepthFilter1 || right != m_DepthFilter2)))
|
||||
{
|
||||
callback(slice, left, right);
|
||||
UpdateDepthFilters(viewType == ViewType.Single, profileSingleView, profileLeftView, profileRightView);
|
||||
m_UpdateActiveTabCallback(true);
|
||||
}
|
||||
}, depthDiff, maxDepthRight);
|
||||
dropdown.Show(rect);
|
||||
EditorGUIUtility.ExitGUI();
|
||||
}
|
||||
else
|
||||
{
|
||||
// The depths can change because the data changed, not just because the user selected a different option in the dropdown
|
||||
// in that case, the depth filters need to perform a refresh
|
||||
if(oldDepthFilter != depthFilter || viewType == ViewType.Locked && oldDepthFilterOtherLocked != depthFilterOther)
|
||||
{
|
||||
UpdateDepthFilters(viewType == ViewType.Single, profileSingleView, profileLeftView, profileRightView);
|
||||
m_UpdateActiveTabCallback(true);
|
||||
}
|
||||
}
|
||||
GUI.enabled = lastEnabled;
|
||||
}
|
||||
#endif
|
||||
|
||||
int CalcSliceMenuEntryIndex(int filterDepthLeft, int filterDepthRight, int leftMax, int rightMax)
|
||||
{
|
||||
return mostCommonDepthDiff > 0 ?
|
||||
filterDepthRight + Math.Max(0, filterDepthLeft - rightMax + (rightMax > 0 ? mostCommonDepthDiff : filterDepthLeft > 0 ? 1 : 0)) :
|
||||
filterDepthLeft + Math.Max(0, filterDepthRight - leftMax - (leftMax > 0 ? mostCommonDepthDiff : filterDepthRight > 0 ? -1 :0));
|
||||
}
|
||||
|
||||
void CalcAutoSlicesFromMenuEntryIndex(int depthSlcieMenuEntryIndex, ref int filterDepthLeft, ref int filterDepthRight, int leftMax, int rightMax)
|
||||
{
|
||||
if (mostCommonDepthDiff > 0)
|
||||
{
|
||||
filterDepthRight = Mathf.Clamp(depthSlcieMenuEntryIndex, 1, rightMax);
|
||||
filterDepthLeft = Mathf.Clamp(depthSlcieMenuEntryIndex - (rightMax > 0 ? mostCommonDepthDiff : 0), 1, leftMax);
|
||||
}
|
||||
else
|
||||
{
|
||||
filterDepthLeft = Mathf.Clamp(depthSlcieMenuEntryIndex, 1, leftMax);
|
||||
filterDepthRight = Mathf.Clamp(depthSlcieMenuEntryIndex + (leftMax > 0 ? mostCommonDepthDiff : 0), 1, rightMax);
|
||||
}
|
||||
// if a side has no depth, only allow All
|
||||
if (leftMax <= 0)
|
||||
filterDepthLeft = -1;
|
||||
if (rightMax <= 0)
|
||||
filterDepthRight = -1;
|
||||
}
|
||||
|
||||
void ClampDepthFilterForAutoRespectingDiff(ref int filterDepthLeft, ref int filterDepthRight, ProfileDataView profileLeftView, ProfileDataView profileRightView)
|
||||
{
|
||||
if (filterDepthLeft == ProfileAnalyzer.kDepthAll && filterDepthRight == ProfileAnalyzer.kDepthAll)
|
||||
{
|
||||
// nothing to do here, keep showing all
|
||||
return;
|
||||
}
|
||||
|
||||
var leftMax = profileLeftView.GetMaxDepth();
|
||||
var rightMax = profileRightView.GetMaxDepth();
|
||||
|
||||
var sliceMenuEntryIndex = CalcSliceMenuEntryIndex(filterDepthLeft, filterDepthRight, leftMax, rightMax);
|
||||
CalcAutoSlicesFromMenuEntryIndex(sliceMenuEntryIndex, ref filterDepthLeft, ref filterDepthRight, leftMax, rightMax);
|
||||
}
|
||||
|
||||
internal void DrawDepthFilter(bool isAnalysisRunning, bool singleView,
|
||||
ProfileDataView profileSingleView, ProfileDataView profileLeftView, ProfileDataView profileRightView)
|
||||
{
|
||||
#if !UNITY_2019_1_OR_NEWER
|
||||
bool triggerRefresh = false;
|
||||
if (!isAnalysisRunning)
|
||||
{
|
||||
if (singleView)
|
||||
{
|
||||
var maxDepth = profileSingleView.GetMaxDepth();
|
||||
if (m_DepthStrings == null || maxDepth != m_OldDepthRaw1)
|
||||
{
|
||||
SetDepthStringsSingle(maxDepth, out m_DepthStrings, out m_DepthValues);
|
||||
m_OldDepthRaw1 = maxDepth;
|
||||
triggerRefresh = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_DepthFilterAuto)
|
||||
{
|
||||
var maxLeftRaw = profileLeftView.GetMaxDepth();
|
||||
var maxRightRaw = profileRightView.GetMaxDepth();
|
||||
if (m_DepthStringsAuto == null ||
|
||||
m_OldDepthRaw1 != maxLeftRaw || m_OldDepthRaw2 != maxRightRaw)
|
||||
{
|
||||
SetDepthStringsCompare(maxLeftRaw, out m_DepthStringsAuto, out m_DepthValuesAuto, maxRightRaw);
|
||||
m_OldDepthRaw1 = maxLeftRaw;
|
||||
m_OldDepthRaw2 = maxRightRaw;
|
||||
triggerRefresh = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var maxDepthLeft = profileLeftView.GetMaxDepth();
|
||||
if (m_DepthStrings1 == null || m_OldDepthRaw1 != maxDepthLeft)
|
||||
{
|
||||
SetDepthStringsSingle(maxDepthLeft, out m_DepthStrings1, out m_DepthValues1);
|
||||
m_OldDepthRaw1 = maxDepthLeft;
|
||||
triggerRefresh = true;
|
||||
}
|
||||
|
||||
var maxDepthRight = profileRightView.GetMaxDepth();
|
||||
if (m_DepthStrings2 == null || m_OldDepthRaw2 != maxDepthRight)
|
||||
{
|
||||
SetDepthStringsSingle(maxDepthRight, out m_DepthStrings2, out m_DepthValues2);
|
||||
m_OldDepthRaw2 = maxDepthRight;
|
||||
triggerRefresh = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool lastEnabled = GUI.enabled;
|
||||
bool enabled = !isAnalysisRunning;
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (singleView)
|
||||
{
|
||||
EditorGUILayout.LabelField(ProfileAnalyzerWindow.Styles.depthTitle, GUILayout.Width(ProfileAnalyzerWindow.LayoutSize.FilterOptionsLeftLabelWidth));
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
DrawDepthFilterDropdown(null, enabled,
|
||||
profileSingleView, (primary, left, right) => m_DepthFilter = primary,
|
||||
ViewType.Single, profileSingleView, profileLeftView, profileRightView);
|
||||
#else
|
||||
if (m_DepthStrings != null)
|
||||
{
|
||||
var lastDepthFilter = m_DepthFilter;
|
||||
m_DepthFilter = m_DepthFilter == ProfileAnalyzer.kDepthAll ? ProfileAnalyzer.kDepthAll : profileSingleView.ClampToValidDepthValue(m_DepthFilter);
|
||||
|
||||
GUI.enabled = enabled;
|
||||
m_DepthFilter = EditorGUILayout.IntPopup(m_DepthFilter, m_DepthStrings, m_DepthValues, GUILayout.Width(ProfileAnalyzerWindow.LayoutSize.FilterOptionsEnumWidth));
|
||||
GUI.enabled = lastEnabled;
|
||||
if (m_DepthFilter != lastDepthFilter)
|
||||
triggerRefresh = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField(ProfileAnalyzerWindow.Styles.depthTitle, GUILayout.Width(ProfileAnalyzerWindow.LayoutSize.FilterOptionsLeftLabelWidth));
|
||||
|
||||
if (m_DepthFilterAuto)
|
||||
{
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
DrawDepthFilterDropdown(null, enabled, profileLeftView, (primary, left, right) =>
|
||||
{
|
||||
m_DepthFilter1 = left;
|
||||
m_DepthFilter2 = right;
|
||||
ClampDepthFilterForAutoRespectingDiff(ref m_DepthFilter1, ref m_DepthFilter2, profileLeftView, profileRightView);
|
||||
},
|
||||
ViewType.Locked, profileSingleView, profileLeftView, profileRightView);
|
||||
#else
|
||||
|
||||
if (m_DepthStringsAuto != null)
|
||||
{
|
||||
var leftMax = profileLeftView.GetMaxDepth();
|
||||
var rightMax = profileRightView.GetMaxDepth();
|
||||
var lastDepthFilterDropdownIndex = CalcSliceMenuEntryIndex(m_DepthFilter1, m_DepthFilter2, leftMax, rightMax);
|
||||
ClampDepthFilterForAutoRespectingDiff(ref m_DepthFilter1, ref m_DepthFilter2,
|
||||
profileLeftView, profileRightView);
|
||||
|
||||
var layoutOptionWidth = ProfileAnalyzerWindow.LayoutSize.FilterOptionsEnumWidth;
|
||||
if(m_DepthFilter1 != m_DepthFilter2)
|
||||
layoutOptionWidth = ProfileAnalyzerWindow.LayoutSize.FilterOptionsLockedEnumWidth;
|
||||
|
||||
GUI.enabled = enabled;
|
||||
var depthFilterDropdownIndex = Mathf.Clamp(lastDepthFilterDropdownIndex, -1, m_DepthStringsAuto.Length - 1);
|
||||
depthFilterDropdownIndex = EditorGUILayout.IntPopup(depthFilterDropdownIndex, m_DepthStringsAuto, m_DepthValuesAuto, GUILayout.Width(layoutOptionWidth));
|
||||
GUI.enabled = lastEnabled;
|
||||
|
||||
if (depthFilterDropdownIndex != lastDepthFilterDropdownIndex)
|
||||
{
|
||||
if(depthFilterDropdownIndex == ProfileAnalyzer.kDepthAll)
|
||||
m_DepthFilter1 = m_DepthFilter2 = ProfileAnalyzer.kDepthAll;
|
||||
else
|
||||
CalcAutoSlicesFromMenuEntryIndex(depthFilterDropdownIndex, ref m_DepthFilter1, ref m_DepthFilter2, leftMax, rightMax);
|
||||
ClampDepthFilterForAutoRespectingDiff(ref m_DepthFilter1, ref m_DepthFilter2, profileLeftView, profileRightView);
|
||||
triggerRefresh = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
DrawDepthFilterDropdown(ProfileAnalyzerWindow.Styles.leftDepthTitle, enabled, profileLeftView,
|
||||
(primary, left, right) => m_DepthFilter1 = primary,
|
||||
ViewType.Left, profileSingleView, profileLeftView, profileRightView);
|
||||
#else
|
||||
if (m_DepthStrings1 != null)
|
||||
{
|
||||
EditorGUILayout.LabelField(ProfileAnalyzerWindow.Styles.leftDepthTitle, GUILayout.Width(ProfileAnalyzerWindow.LayoutSize.FilterOptionsEnumWidth));
|
||||
|
||||
int lastDepthFilter1 = m_DepthFilter1;
|
||||
m_DepthFilter1 = m_DepthFilter1 == ProfileAnalyzer.kDepthAll ? ProfileAnalyzer.kDepthAll : profileLeftView.ClampToValidDepthValue(m_DepthFilter1);
|
||||
|
||||
GUI.enabled = enabled;
|
||||
m_DepthFilter1 = EditorGUILayout.IntPopup(m_DepthFilter1, m_DepthStrings1, m_DepthValues1, GUILayout.Width(ProfileAnalyzerWindow.LayoutSize.FilterOptionsEnumWidth));
|
||||
GUI.enabled = lastEnabled;
|
||||
if (m_DepthFilter1 != lastDepthFilter1)
|
||||
triggerRefresh = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
DrawDepthFilterDropdown(ProfileAnalyzerWindow.Styles.rightDepthTitle, enabled && !m_DepthFilterAuto, profileRightView,
|
||||
(primary, left, right) => m_DepthFilter2 = primary,
|
||||
ViewType.Right, profileSingleView, profileLeftView, profileRightView);
|
||||
#else
|
||||
if (m_DepthStrings2 != null)
|
||||
{
|
||||
EditorGUILayout.LabelField(ProfileAnalyzerWindow.Styles.rightDepthTitle, GUILayout.Width(ProfileAnalyzerWindow.LayoutSize.FilterOptionsEnumWidth));
|
||||
|
||||
int lastDepthFilter2 = m_DepthFilter2;
|
||||
m_DepthFilter2 = m_DepthFilter2 == ProfileAnalyzer.kDepthAll ? ProfileAnalyzer.kDepthAll : profileRightView.ClampToValidDepthValue(m_DepthFilter2);
|
||||
|
||||
GUI.enabled = enabled && !m_DepthFilterAuto;
|
||||
m_DepthFilter2 = EditorGUILayout.IntPopup(m_DepthFilter2, m_DepthStrings2, m_DepthValues2, GUILayout.Width(ProfileAnalyzerWindow.LayoutSize.FilterOptionsEnumWidth));
|
||||
GUI.enabled = lastEnabled;
|
||||
if (m_DepthFilter2 != lastDepthFilter2)
|
||||
triggerRefresh = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
bool lastDepthFilterLock = m_DepthFilterAuto;
|
||||
GUI.enabled = enabled;
|
||||
m_DepthFilterAuto = EditorGUILayout.ToggleLeft(ProfileAnalyzerWindow.Styles.autoDepthTitle, m_DepthFilterAuto);
|
||||
GUI.enabled = lastEnabled;
|
||||
if (m_DepthFilterAuto != lastDepthFilterLock)
|
||||
{
|
||||
if (UpdateDepthFilters(singleView, profileSingleView, profileLeftView, profileRightView))
|
||||
m_UpdateActiveTabCallback(true);
|
||||
#if !UNITY_2019_1_OR_NEWER
|
||||
m_DepthStringsAuto = null;
|
||||
m_DepthStrings1 = null;
|
||||
m_DepthStrings2 = null;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
#if !UNITY_2019_1_OR_NEWER
|
||||
if (triggerRefresh)
|
||||
{
|
||||
UpdateDepthFilters(singleView, profileSingleView, profileLeftView, profileRightView);
|
||||
m_UpdateActiveTabCallback(true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
internal bool UpdateDepthFilters(bool singleView, ProfileDataView profileSingleView, ProfileDataView profileLeftView, ProfileDataView profileRightView)
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
if (!singleView)
|
||||
{
|
||||
// First respect the auto flag
|
||||
if (UpdateAutoDepthFilter(profileLeftView, profileRightView))
|
||||
changed = true;
|
||||
|
||||
// Make sure Single matches the updated comparison view
|
||||
if (profileLeftView.path == profileSingleView.path)
|
||||
{
|
||||
// Use same filter on single view if its the same file
|
||||
if (m_DepthFilter != m_DepthFilter1)
|
||||
{
|
||||
m_DepthFilter = m_DepthFilter1;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (profileRightView.path == profileSingleView.path)
|
||||
{
|
||||
// Use same filter on single view if its the same file
|
||||
if (m_DepthFilter != m_DepthFilter2)
|
||||
{
|
||||
m_DepthFilter = m_DepthFilter2;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make sure comparisons match updated single view
|
||||
if (profileLeftView.path == profileSingleView.path)
|
||||
{
|
||||
// Use same filter on comparison left view if its the same file
|
||||
if (m_DepthFilter1 != m_DepthFilter)
|
||||
{
|
||||
m_DepthFilter1 = m_DepthFilter;
|
||||
changed = true;
|
||||
}
|
||||
if (m_DepthFilterAuto)
|
||||
{
|
||||
var newDepthFilter2 = m_DepthFilter;
|
||||
ClampDepthFilterForAutoRespectingDiff(ref m_DepthFilter1, ref newDepthFilter2, profileLeftView, profileRightView);
|
||||
|
||||
if (m_DepthFilter2 != newDepthFilter2)
|
||||
{
|
||||
m_DepthFilter2 = newDepthFilter2;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (UpdateAutoDepthFilter(profileLeftView, profileRightView))
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (UpdateAutoDepthFilter(profileLeftView, profileRightView))
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (profileRightView.path == profileSingleView.path)
|
||||
{
|
||||
// Use same filter on comparison right view if its the same file
|
||||
if (m_DepthFilter2 != m_DepthFilter)
|
||||
{
|
||||
m_DepthFilter2 = m_DepthFilter;
|
||||
changed = true;
|
||||
}
|
||||
if (m_DepthFilterAuto)
|
||||
{
|
||||
var newDepthFilter1 = m_DepthFilter;
|
||||
ClampDepthFilterForAutoRespectingDiff(ref newDepthFilter1, ref m_DepthFilter2, profileLeftView, profileRightView);
|
||||
if (m_DepthFilter1 != newDepthFilter1)
|
||||
{
|
||||
m_DepthFilter1 = newDepthFilter1;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (UpdateAutoDepthFilter(profileLeftView, profileRightView))
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
int CalculateDepthDifference(ProfileAnalysis leftAnalysis, ProfileAnalysis rightAnalysis, List<MarkerPairing> pairings)
|
||||
{
|
||||
if (pairings.Count <= 0)
|
||||
{
|
||||
mostCommonDepthDiff = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
var leftMarkers = leftAnalysis.GetMarkers();
|
||||
var rightMarkers = rightAnalysis.GetMarkers();
|
||||
|
||||
int totalCount = 0;
|
||||
Dictionary<int, int> depthDifferences = new Dictionary<int, int>();
|
||||
foreach (var pairing in pairings)
|
||||
{
|
||||
if (pairing.leftIndex >= 0 && pairing.rightIndex >= 0)
|
||||
{
|
||||
MarkerData leftMarker = leftMarkers[pairing.leftIndex];
|
||||
MarkerData rightMarker = rightMarkers[pairing.rightIndex];
|
||||
int markerDepthDiff = rightMarker.minDepth - leftMarker.minDepth;
|
||||
|
||||
int value = 0;
|
||||
depthDifferences.TryGetValue(markerDepthDiff, out value);
|
||||
depthDifferences[markerDepthDiff] = value + 1;
|
||||
totalCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
var newDepthDiff = 0;
|
||||
|
||||
// Find most common depth difference
|
||||
int maxCount = 0;
|
||||
foreach (var diff in depthDifferences.Keys)
|
||||
{
|
||||
if (depthDifferences[diff] > maxCount)
|
||||
{
|
||||
maxCount = depthDifferences[diff];
|
||||
newDepthDiff = diff;
|
||||
}
|
||||
}
|
||||
|
||||
return mostCommonDepthDiff = newDepthDiff;
|
||||
}
|
||||
|
||||
bool UpdateAutoDepthFilter(ProfileDataView profileLeftView, ProfileDataView profileRightView)
|
||||
{
|
||||
if (m_DepthFilterAuto)
|
||||
{
|
||||
var newDepthFilter1 = m_DepthFilter1;
|
||||
var newDepthFilter2 = m_DepthFilter2;
|
||||
ClampDepthFilterForAutoRespectingDiff(ref newDepthFilter1, ref newDepthFilter2, profileLeftView, profileRightView);
|
||||
if (m_DepthFilter1 != newDepthFilter1)
|
||||
{
|
||||
m_DepthFilter1 = newDepthFilter1;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_DepthFilter2 != newDepthFilter2)
|
||||
{
|
||||
m_DepthFilter2 = newDepthFilter2;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal bool UpdateDepthForCompareSync(ProfileAnalysis leftAnalysis, ProfileAnalysis rightAnalysis, List<MarkerPairing> pairings, ProfileDataView profileLeftView, ProfileDataView profileRightView)
|
||||
{
|
||||
int originalDepthDiff = mostCommonDepthDiff;
|
||||
int newDepthDiff = CalculateDepthDifference(leftAnalysis, rightAnalysis, pairings);
|
||||
if (newDepthDiff != originalDepthDiff)
|
||||
{
|
||||
UpdateAutoDepthFilter(profileLeftView, profileRightView);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal GUIContent GetUIInfo(bool compare)
|
||||
{
|
||||
GUIContent info;
|
||||
if (compare && m_DepthFilter1 == ProfileAnalyzer.kDepthAll && m_DepthFilter2 == ProfileAnalyzer.kDepthAll ||
|
||||
!compare && depthFilter == ProfileAnalyzer.kDepthAll)
|
||||
{
|
||||
info = new GUIContent("(All depths)", string.Format("{0}\n\nSet depth 1 to get an overview of the frame", ProfileAnalyzerWindow.Styles.medianFrameTooltip));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (compare && depthFilter1 != depthFilter2)
|
||||
{
|
||||
if (m_DepthFilter1 == ProfileAnalyzer.kDepthAll)
|
||||
info = new GUIContent(string.Format("(Filtered to 'all' depths in the first data set, and depth '{0}' in the second)", m_DepthFilter2), ProfileAnalyzerWindow.Styles.medianFrameTooltip);
|
||||
else if (m_DepthFilter2 == ProfileAnalyzer.kDepthAll)
|
||||
info = new GUIContent(string.Format("(Filtered to depth '{0}' in the first data set, and 'all' depths in the second)", m_DepthFilter1), ProfileAnalyzerWindow.Styles.medianFrameTooltip);
|
||||
else
|
||||
info = new GUIContent(string.Format("(Filtered to depth '{0}' in the first data set, and depth '{1}' in the second)", m_DepthFilter1, depthFilter2), ProfileAnalyzerWindow.Styles.medianFrameTooltip);
|
||||
}
|
||||
else
|
||||
info = new GUIContent(string.Format("(Filtered to depth '{0}' only)", compare ? m_DepthFilter1 : depthFilter), ProfileAnalyzerWindow.Styles.medianFrameTooltip);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
public static string DepthFilterToString(int depthFilter)
|
||||
{
|
||||
return depthFilter == ProfileAnalyzer.kDepthAll ? "All" : depthFilter.ToString();
|
||||
}
|
||||
|
||||
public static string DepthFilterToString(int depthSliceLeft, int depthSliceRight, bool leftIsMain)
|
||||
{
|
||||
if(depthSliceLeft != depthSliceRight)
|
||||
{
|
||||
if (leftIsMain)
|
||||
return string.Format("{0} ({1}{2})", DepthFilterToString(depthSliceLeft), ProfileAnalyzerWindow.Styles.rightDepthTitle.text, DepthFilterToString(depthSliceRight));
|
||||
else
|
||||
return string.Format("{0} ({1}{2})", DepthFilterToString(depthSliceRight), ProfileAnalyzerWindow.Styles.leftDepthTitle.text, DepthFilterToString(depthSliceLeft));
|
||||
}
|
||||
return DepthFilterToString(depthSliceLeft);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6d4bf3d974bf4f74a15e72b2e0a8ffa2
|
||||
timeCreated: 1608212438
|
|
@ -0,0 +1,198 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
internal class Draw2D
|
||||
{
|
||||
public enum Origin
|
||||
{
|
||||
TopLeft,
|
||||
BottomLeft
|
||||
};
|
||||
|
||||
Origin m_Origin = Origin.TopLeft;
|
||||
GUIStyle m_GLStyle;
|
||||
string m_ShaderName;
|
||||
Material m_Material;
|
||||
Rect m_Rect;
|
||||
Vector4 m_ClipRect;
|
||||
bool m_ClipRectEnabled = false;
|
||||
|
||||
public Draw2D(string shaderName)
|
||||
{
|
||||
m_ShaderName = shaderName;
|
||||
CheckAndSetupMaterial();
|
||||
}
|
||||
|
||||
public bool CheckAndSetupMaterial()
|
||||
{
|
||||
if (m_Material == null)
|
||||
{
|
||||
m_Material = new Material(Shader.Find(m_ShaderName));
|
||||
}
|
||||
|
||||
if (m_Material == null)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsMaterialValid()
|
||||
{
|
||||
if (m_Material == null)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
if (m_GLStyle == null)
|
||||
{
|
||||
m_GLStyle = new GUIStyle(GUI.skin.box);
|
||||
m_GLStyle.padding = new RectOffset(0, 0, 0, 0);
|
||||
m_GLStyle.margin = new RectOffset(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetClipRect(Rect clipRect)
|
||||
{
|
||||
m_ClipRect = new Vector4(clipRect.x, clipRect.y, clipRect.x + clipRect.width, clipRect.y + clipRect.height);
|
||||
m_ClipRectEnabled = true;
|
||||
|
||||
CheckAndSetupMaterial();
|
||||
m_Material.SetFloat("_UseClipRect", m_ClipRectEnabled ? 1f : 0f);
|
||||
m_Material.SetVector("_ClipRect", m_ClipRect);
|
||||
}
|
||||
|
||||
public void ClearClipRect()
|
||||
{
|
||||
m_ClipRectEnabled = false;
|
||||
|
||||
CheckAndSetupMaterial();
|
||||
m_Material.SetFloat("_UseClipRect", m_ClipRectEnabled ? 1f : 0f);
|
||||
m_Material.SetVector("_ClipRect", m_ClipRect);
|
||||
}
|
||||
|
||||
public Rect GetClipRect()
|
||||
{
|
||||
return new Rect(m_ClipRect.x, m_ClipRect.y, m_ClipRect.z - m_ClipRect.x, m_ClipRect.w - m_ClipRect.y);
|
||||
}
|
||||
|
||||
public bool DrawStart(Rect r, Origin origin = Origin.TopLeft)
|
||||
{
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
return false;
|
||||
|
||||
if (!CheckAndSetupMaterial())
|
||||
return false;
|
||||
|
||||
CheckAndSetupMaterial();
|
||||
m_Material.SetPass(0);
|
||||
|
||||
m_Rect = r;
|
||||
m_Origin = origin;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool DrawStart(float w, float h, Origin origin = Origin.TopLeft, GUIStyle style = null)
|
||||
{
|
||||
Rect r = GUILayoutUtility.GetRect(w, h, style == null ? m_GLStyle : style, GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(false));
|
||||
return DrawStart(r, origin);
|
||||
}
|
||||
|
||||
public void DrawEnd()
|
||||
{
|
||||
}
|
||||
|
||||
public void Translate(ref float x, ref float y)
|
||||
{
|
||||
// Translation done CPU side so we have world space coords in the shader for clipping.
|
||||
if (m_Origin == Origin.BottomLeft)
|
||||
{
|
||||
x = m_Rect.xMin + x;
|
||||
y = m_Rect.yMax - y;
|
||||
}
|
||||
else
|
||||
{
|
||||
x = m_Rect.xMin + x;
|
||||
y = m_Rect.yMin + y;
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawFilledBox(float x, float y, float w, float h, Color col)
|
||||
{
|
||||
float x2 = x + w;
|
||||
float y2 = y + h;
|
||||
|
||||
Translate(ref x, ref y);
|
||||
Translate(ref x2, ref y2);
|
||||
|
||||
if (m_Origin == Origin.BottomLeft)
|
||||
{
|
||||
GL.Begin(GL.TRIANGLE_STRIP);
|
||||
GL.Color(col);
|
||||
GL.Vertex3(x, y, 0);
|
||||
GL.Vertex3(x, y2, 0);
|
||||
GL.Vertex3(x2, y, 0);
|
||||
GL.Vertex3(x2, y2, 0);
|
||||
GL.End();
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.Begin(GL.TRIANGLE_STRIP);
|
||||
GL.Color(col);
|
||||
GL.Vertex3(x, y, 0);
|
||||
GL.Vertex3(x2, y, 0);
|
||||
GL.Vertex3(x, y2, 0);
|
||||
GL.Vertex3(x2, y2, 0);
|
||||
GL.End();
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawFilledBox(float x, float y, float w, float h, float r, float g, float b)
|
||||
{
|
||||
DrawFilledBox(x, y, w, h, new Color(r, g, b));
|
||||
}
|
||||
|
||||
public void DrawLine(float x, float y, float x2, float y2, Color col)
|
||||
{
|
||||
Translate(ref x, ref y);
|
||||
Translate(ref x2, ref y2);
|
||||
|
||||
GL.Begin(GL.LINES);
|
||||
GL.Color(col);
|
||||
GL.Vertex3(x, y, 0);
|
||||
GL.Vertex3(x2, y2, 0);
|
||||
GL.End();
|
||||
}
|
||||
|
||||
public void DrawLine(float x, float y, float x2, float y2, float r, float g, float b)
|
||||
{
|
||||
DrawLine(x, y, x2, y2, new Color(r, g, b));
|
||||
}
|
||||
|
||||
public void DrawBox(float x, float y, float w, float h, Color col)
|
||||
{
|
||||
float x2 = x + w;
|
||||
float y2 = y + h;
|
||||
|
||||
Translate(ref x, ref y);
|
||||
Translate(ref x2, ref y2);
|
||||
|
||||
GL.Begin(GL.LINE_STRIP);
|
||||
GL.Color(col);
|
||||
GL.Vertex3(x, y, 0);
|
||||
GL.Vertex3(x2, y, 0);
|
||||
GL.Vertex3(x2, y2, 0);
|
||||
GL.Vertex3(x, y2, 0);
|
||||
GL.Vertex3(x, y, 0);
|
||||
GL.End();
|
||||
}
|
||||
|
||||
public void DrawBox(float x, float y, float w, float h, float r, float g, float b)
|
||||
{
|
||||
DrawBox(x, y, w, h, new Color(r, g, b));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c27a543a692ef4bf19459f25898a0ba9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
// Using Profiler.Begin/End calls is tricky, because any scope that could throw ExitGUI,
|
||||
// which is likely most parts of this code base, would cause Begin-End-Mismatches if such an exception is thrown in between of them.
|
||||
// On versions from 2018.3 and up this issue can be avoided by via using(ProfilerMarker.Auto(){}.
|
||||
// Faking this behavior for pre 2018.3 versions, so that construction calls Profiler.Begin and Disposal calls Profiler.End
|
||||
// won't work either and still cause Begin-End-Mismatches, especially in Deep Profiling, because the begin and end calls would be
|
||||
// outside of the scope they would be expected to occure in
|
||||
// So any version without ProfilerMarker API can use this stand in instead and just not add instrumentation.
|
||||
|
||||
// Paste this into any files that need these markers and then use ProfilerMarkerAbstracted:
|
||||
|
||||
// #if UNITY_2018_3_OR_NEWER
|
||||
// using ProfilerMarkerAbstracted = Unity.Profiling.ProfilerMarker;
|
||||
// #else
|
||||
// using ProfilerMarkerAbstracted = UnityEditor.Performance.ProfileAnalyzer.FakeScopedMarker;
|
||||
// #endif
|
||||
|
||||
#if !UNITY_2018_3_OR_NEWER
|
||||
internal struct FakeScopedMarker
|
||||
{
|
||||
public FakeScopedMarker(string markerName)
|
||||
{
|
||||
}
|
||||
public IDisposable Auto() { return null; }
|
||||
}
|
||||
#endif
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 02e22b3793b48ba42ad215edef819749
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: af62f7ba5c15e47ceb426c7745e31835
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 11b7852f3babc4a938ba8e18940df7c5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 54a4b8c3a2d924a4fb740b9044db9694
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: dbcb296fb16194f04af7827f37fb2187
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
#else
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
internal class L10n
|
||||
{
|
||||
internal static string Tr(string s)
|
||||
{
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4e01a136cd29c47488d4e77ff76e3f98
|
||||
timeCreated: 1612946517
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 814ccc2d0c490cf4b871d13ab2e4b7c3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,310 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
[Serializable]
|
||||
internal class MarkerData : IComparable<MarkerData>
|
||||
{
|
||||
public string name;
|
||||
public int nameLowerCaseHash; // lower case name hash for faster comparisons
|
||||
|
||||
public double msTotal; // total time of this marker on a frame
|
||||
public int count; // total number of marker calls in the timeline (multiple per frame)
|
||||
public int countMin; // min count per frame
|
||||
public int countMax; // max count per frame
|
||||
public float countMean; // mean over all frames
|
||||
public int countMedian; // median over all frames
|
||||
public int countLowerQuartile; // over all frames
|
||||
public int countUpperQuartile; // over all frames
|
||||
public int lastFrame;
|
||||
public int presentOnFrameCount; // number of frames containing this marker
|
||||
public int firstFrameIndex;
|
||||
public float msMean; // mean over all frames
|
||||
public float msMedian; // median over all frames
|
||||
public float msLowerQuartile; // over all frames
|
||||
public float msUpperQuartile; // over all frames
|
||||
public float msMin; // min total time per frame
|
||||
public float msMax; // max total time per frame
|
||||
public int minIndividualFrameIndex;
|
||||
public int maxIndividualFrameIndex;
|
||||
public float msMinIndividual; // min individual function call
|
||||
public float msMaxIndividual; // max individual function call
|
||||
public float msAtMedian; // time at median frame
|
||||
public int medianFrameIndex; // frame this markers median value is found on
|
||||
public int minFrameIndex;
|
||||
public int maxFrameIndex;
|
||||
public int minDepth;
|
||||
public int maxDepth;
|
||||
public List<string> threads;
|
||||
|
||||
const int bucketCount = 20;
|
||||
public int[] buckets; // Each bucket contains 'number of frames' for 'sum of markers in the frame' in that range
|
||||
public int[] countBuckets; // Each bucket contains 'number of frames' for 'count in the frame' in that range
|
||||
public List<FrameTime> frames;
|
||||
|
||||
public MarkerData(string markerName)
|
||||
{
|
||||
buckets = new int[bucketCount];
|
||||
countBuckets = new int[bucketCount];
|
||||
frames = new List<FrameTime>();
|
||||
threads = new List<string>();
|
||||
|
||||
name = markerName;
|
||||
nameLowerCaseHash = markerName.ToLower().GetHashCode();
|
||||
msTotal = 0.0;
|
||||
count = 0;
|
||||
countMin = 0;
|
||||
countMax = 0;
|
||||
countMean = 0f;
|
||||
countMedian = 0;
|
||||
countLowerQuartile = 0;
|
||||
countUpperQuartile = 0;
|
||||
lastFrame = -1;
|
||||
presentOnFrameCount = 0;
|
||||
firstFrameIndex = -1;
|
||||
msMean = 0f;
|
||||
msMedian = 0f;
|
||||
msLowerQuartile = 0f;
|
||||
msUpperQuartile = 0f;
|
||||
msMin = float.MaxValue;
|
||||
msMax = float.MinValue;
|
||||
minIndividualFrameIndex = 0;
|
||||
maxIndividualFrameIndex = 0;
|
||||
msMinIndividual = float.MaxValue;
|
||||
msMaxIndividual = float.MinValue;
|
||||
msAtMedian = 0f;
|
||||
medianFrameIndex = 0;
|
||||
minFrameIndex = 0;
|
||||
maxFrameIndex = 0;
|
||||
minDepth = 0;
|
||||
maxDepth = 0;
|
||||
|
||||
for (int b = 0; b < buckets.Length; b++)
|
||||
{
|
||||
buckets[b] = 0;
|
||||
countBuckets[b] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Compare the time duration between the marker median times. Used for sorting in descending order</summary>
|
||||
/// <param name="other"> The other MarkerData to compare </param>
|
||||
/// <returns>-1 if this is larger, 0 if the same, 1 if this is smaller</returns>
|
||||
public int CompareTo(MarkerData other)
|
||||
{
|
||||
if (msMedian == other.msMedian)
|
||||
{
|
||||
if (medianFrameIndex == other.medianFrameIndex)
|
||||
{
|
||||
// Tertiary sort by name order
|
||||
return name.CompareTo(other.name);
|
||||
}
|
||||
|
||||
// Secondary sort by frame index order
|
||||
return medianFrameIndex.CompareTo(other.medianFrameIndex);
|
||||
}
|
||||
|
||||
return -msMedian.CompareTo(other.msMedian);
|
||||
}
|
||||
|
||||
public float GetFrameMs(int frameIndex)
|
||||
{
|
||||
foreach (var frameData in frames)
|
||||
{
|
||||
if (frameData.frameIndex == frameIndex)
|
||||
return frameData.ms;
|
||||
}
|
||||
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public void ComputeBuckets(float min, float max)
|
||||
{
|
||||
float first = min;
|
||||
float last = max;
|
||||
float range = last - first;
|
||||
|
||||
int maxBucketIndex = (buckets.Length - 1);
|
||||
|
||||
for (int bucketIndex = 0; bucketIndex < buckets.Length; bucketIndex++)
|
||||
{
|
||||
buckets[bucketIndex] = 0;
|
||||
}
|
||||
|
||||
float scale = range > 0 ? buckets.Length / range : 0;
|
||||
foreach (FrameTime frameTime in frames)
|
||||
{
|
||||
var ms = frameTime.ms;
|
||||
//int frameIndex = frameTime.frameIndex;
|
||||
|
||||
int bucketIndex = (int)((ms - first) * scale);
|
||||
if (bucketIndex < 0 || bucketIndex > maxBucketIndex)
|
||||
{
|
||||
// This can happen if a single marker range is longer than the frame start end (which could occur if running on a separate thread)
|
||||
// It can also occur for the highest entry in the range (max-min/range) = 1
|
||||
// if (ms > max) // Check for the spilling case
|
||||
// Debug.Log(string.Format("Marker {0} : {1}ms exceeds range {2}-{3} on frame {4}", marker.name, ms, first, last, 1+frameIndex));
|
||||
|
||||
if (bucketIndex > maxBucketIndex)
|
||||
bucketIndex = maxBucketIndex;
|
||||
else
|
||||
bucketIndex = 0;
|
||||
}
|
||||
buckets[bucketIndex] += 1;
|
||||
}
|
||||
|
||||
if (range == 0)
|
||||
{
|
||||
// All buckets will be the same
|
||||
for (int bucketIndex = 1; bucketIndex < buckets.Length; bucketIndex++)
|
||||
{
|
||||
buckets[bucketIndex] = buckets[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ComputeCountBuckets(int min, int max)
|
||||
{
|
||||
float first = min;
|
||||
float last = max;
|
||||
float range = last - first;
|
||||
|
||||
int maxBucketIndex = (countBuckets.Length - 1);
|
||||
|
||||
for (int bucketIndex = 0; bucketIndex < countBuckets.Length; bucketIndex++)
|
||||
{
|
||||
countBuckets[bucketIndex] = 0;
|
||||
}
|
||||
|
||||
float scale = range > 0 ? countBuckets.Length / range : 0;
|
||||
foreach (FrameTime frameTime in frames)
|
||||
{
|
||||
var count = frameTime.count;
|
||||
|
||||
int bucketIndex = (int)((count - first) * scale);
|
||||
if (bucketIndex < 0 || bucketIndex > maxBucketIndex)
|
||||
{
|
||||
if (bucketIndex > maxBucketIndex)
|
||||
bucketIndex = maxBucketIndex;
|
||||
else
|
||||
bucketIndex = 0;
|
||||
}
|
||||
countBuckets[bucketIndex] += 1;
|
||||
}
|
||||
|
||||
if (range == 0)
|
||||
{
|
||||
// All buckets will be the same
|
||||
for (int bucketIndex = 1; bucketIndex < countBuckets.Length; bucketIndex++)
|
||||
{
|
||||
countBuckets[bucketIndex] = countBuckets[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetFirstThread(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.threads[0] : "";
|
||||
}
|
||||
|
||||
public static float GetMsMax(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.msMax : 0.0f;
|
||||
}
|
||||
|
||||
public static int GetMaxFrameIndex(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.maxFrameIndex : 0;
|
||||
}
|
||||
|
||||
public static float GetMsMin(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.msMin : 0.0f;
|
||||
}
|
||||
|
||||
public static int GetMinFrameIndex(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.minFrameIndex : 0;
|
||||
}
|
||||
|
||||
public static float GetMsMedian(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.msMedian : 0.0f;
|
||||
}
|
||||
|
||||
public static int GetMedianFrameIndex(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.medianFrameIndex : 0;
|
||||
}
|
||||
|
||||
public static float GetMsUpperQuartile(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.msUpperQuartile : 0.0f;
|
||||
}
|
||||
|
||||
public static float GetMsLowerQuartile(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.msLowerQuartile : 0.0f;
|
||||
}
|
||||
|
||||
public static float GetMsMean(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.msMean : 0.0f;
|
||||
}
|
||||
|
||||
public static float GetMsMinIndividual(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.msMinIndividual : 0.0f;
|
||||
}
|
||||
|
||||
public static float GetMsMaxIndividual(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.msMaxIndividual : 0.0f;
|
||||
}
|
||||
|
||||
public static int GetPresentOnFrameCount(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.presentOnFrameCount : 0;
|
||||
}
|
||||
|
||||
public static float GetMsAtMedian(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.msAtMedian : 0.0f;
|
||||
}
|
||||
|
||||
public static int GetCountMin(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.countMin : 0;
|
||||
}
|
||||
|
||||
public static int GetCountMax(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.countMax : 0;
|
||||
}
|
||||
|
||||
public static int GetCount(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.count : 0;
|
||||
}
|
||||
|
||||
public static float GetCountMean(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.countMean : 0.0f;
|
||||
}
|
||||
|
||||
public static double GetMsTotal(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.msTotal : 0.0;
|
||||
}
|
||||
|
||||
public static int GetMinDepth(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.minDepth : 0;
|
||||
}
|
||||
|
||||
public static int GetMaxDepth(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.maxDepth : 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7636f77e28bd3480f9e22fecae16594a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
[Serializable]
|
||||
internal class MarkerPairing
|
||||
{
|
||||
public string name;
|
||||
public int leftIndex;
|
||||
public int rightIndex;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 60fdacb48a6fe46529c3c01c96f7d123
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,369 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
[Serializable]
|
||||
internal class ProfileAnalysis
|
||||
{
|
||||
FrameSummary m_FrameSummary = new FrameSummary();
|
||||
List<MarkerData> m_Markers = new List<MarkerData>();
|
||||
List<ThreadData> m_Threads = new List<ThreadData>();
|
||||
|
||||
public ProfileAnalysis()
|
||||
{
|
||||
m_FrameSummary.first = 0;
|
||||
m_FrameSummary.last = 0;
|
||||
m_FrameSummary.count = 0;
|
||||
m_FrameSummary.msTotal = 0.0;
|
||||
m_FrameSummary.msMin = float.MaxValue;
|
||||
m_FrameSummary.msMax = 0.0f;
|
||||
m_FrameSummary.minFrameIndex = 0;
|
||||
m_FrameSummary.maxFrameIndex = 0;
|
||||
m_FrameSummary.maxMarkerDepth = 0;
|
||||
m_FrameSummary.totalMarkers = 0;
|
||||
m_FrameSummary.markerCountMax = 0;
|
||||
m_FrameSummary.markerCountMaxMean = 0.0f;
|
||||
for (int b = 0; b < m_FrameSummary.buckets.Length; b++)
|
||||
m_FrameSummary.buckets[b] = 0;
|
||||
|
||||
m_Markers.Clear();
|
||||
m_Threads.Clear();
|
||||
}
|
||||
|
||||
public void SetRange(int firstFrameIndex, int lastFrameIndex)
|
||||
{
|
||||
m_FrameSummary.first = firstFrameIndex;
|
||||
m_FrameSummary.last = lastFrameIndex;
|
||||
|
||||
// Ensure these are initialized to frame indices within the range
|
||||
m_FrameSummary.minFrameIndex = firstFrameIndex;
|
||||
// if this wasn't initialized, and all frames had 0 length, it wouldn't be set in the UpdateSummary step of the analysis and point out of range
|
||||
m_FrameSummary.maxFrameIndex = firstFrameIndex;
|
||||
}
|
||||
|
||||
public void AddMarker(MarkerData marker)
|
||||
{
|
||||
m_Markers.Add(marker);
|
||||
}
|
||||
|
||||
public void AddThread(ThreadData thread)
|
||||
{
|
||||
m_Threads.Add(thread);
|
||||
}
|
||||
|
||||
public void UpdateSummary(int frameIndex, float msFrame)
|
||||
{
|
||||
m_FrameSummary.msTotal += msFrame;
|
||||
m_FrameSummary.count += 1;
|
||||
if (msFrame < m_FrameSummary.msMin)
|
||||
{
|
||||
m_FrameSummary.msMin = msFrame;
|
||||
m_FrameSummary.minFrameIndex = frameIndex;
|
||||
}
|
||||
if (msFrame > m_FrameSummary.msMax)
|
||||
{
|
||||
m_FrameSummary.msMax = msFrame;
|
||||
m_FrameSummary.maxFrameIndex = frameIndex;
|
||||
}
|
||||
|
||||
m_FrameSummary.frames.Add(new FrameTime(frameIndex, msFrame, 1));
|
||||
}
|
||||
|
||||
FrameTime GetPercentageOffset(List<FrameTime> frames, float percent, out int outputFrameIndex)
|
||||
{
|
||||
int index = (int)((frames.Count - 1) * percent / 100);
|
||||
outputFrameIndex = frames[index].frameIndex;
|
||||
|
||||
// True median is half of the sum of the middle 2 frames for an even count. However this would be a value never recorded so we avoid that.
|
||||
return frames[index];
|
||||
}
|
||||
|
||||
float GetThreadPercentageOffset(List<ThreadFrameTime> frames, float percent, out int outputFrameIndex)
|
||||
{
|
||||
int index = (int)((frames.Count - 1) * percent / 100);
|
||||
outputFrameIndex = frames[index].frameIndex;
|
||||
|
||||
// True median is half of the sum of the middle 2 frames for an even count. However this would be a value never recorded so we avoid that.
|
||||
return frames[index].ms;
|
||||
}
|
||||
|
||||
public void SetupMarkers()
|
||||
{
|
||||
int countMax = 0;
|
||||
float countMaxMean = 0.0f;
|
||||
|
||||
foreach (MarkerData marker in m_Markers)
|
||||
{
|
||||
marker.msAtMedian = 0.0f;
|
||||
marker.msMin = float.MaxValue;
|
||||
marker.msMax = float.MinValue;
|
||||
marker.minFrameIndex = 0;
|
||||
marker.maxFrameIndex = 0;
|
||||
marker.countMin = int.MaxValue;
|
||||
marker.countMax = int.MinValue;
|
||||
|
||||
foreach (FrameTime frameTime in marker.frames)
|
||||
{
|
||||
var ms = frameTime.ms;
|
||||
int frameIndex = frameTime.frameIndex;
|
||||
|
||||
// Total time for marker over frame
|
||||
if (ms < marker.msMin)
|
||||
{
|
||||
marker.msMin = ms;
|
||||
marker.minFrameIndex = frameIndex;
|
||||
}
|
||||
if (ms > marker.msMax)
|
||||
{
|
||||
marker.msMax = ms;
|
||||
marker.maxFrameIndex = frameIndex;
|
||||
}
|
||||
|
||||
if (frameIndex == m_FrameSummary.medianFrameIndex)
|
||||
marker.msAtMedian = ms;
|
||||
|
||||
var count = frameTime.count;
|
||||
|
||||
// count for marker over frame
|
||||
if (count < marker.countMin)
|
||||
{
|
||||
marker.countMin = count;
|
||||
}
|
||||
if (count > marker.countMax)
|
||||
{
|
||||
marker.countMax = count;
|
||||
}
|
||||
}
|
||||
|
||||
int unusedIndex;
|
||||
|
||||
marker.msMean = marker.presentOnFrameCount > 0 ? (float)(marker.msTotal / marker.presentOnFrameCount) : 0f;
|
||||
marker.frames.Sort(FrameTime.CompareCount);
|
||||
marker.countMedian = GetPercentageOffset(marker.frames, 50, out marker.medianFrameIndex).count;
|
||||
marker.countLowerQuartile = GetPercentageOffset(marker.frames, 25, out unusedIndex).count;
|
||||
marker.countUpperQuartile = GetPercentageOffset(marker.frames, 75, out unusedIndex).count;
|
||||
|
||||
marker.countMean = marker.presentOnFrameCount > 0 ? (float)marker.count / marker.presentOnFrameCount : 0f;
|
||||
marker.frames.Sort(FrameTime.CompareMs);
|
||||
marker.msMedian = GetPercentageOffset(marker.frames, 50, out marker.medianFrameIndex).ms;
|
||||
marker.msLowerQuartile = GetPercentageOffset(marker.frames, 25, out unusedIndex).ms;
|
||||
marker.msUpperQuartile = GetPercentageOffset(marker.frames, 75, out unusedIndex).ms;
|
||||
|
||||
if (marker.countMax > countMax)
|
||||
countMax = marker.countMax;
|
||||
if (marker.countMean > countMaxMean)
|
||||
countMaxMean = marker.countMean;
|
||||
}
|
||||
|
||||
m_FrameSummary.markerCountMax = countMax;
|
||||
m_FrameSummary.markerCountMaxMean = countMaxMean;
|
||||
}
|
||||
|
||||
public void SetupMarkerBuckets()
|
||||
{
|
||||
foreach (MarkerData marker in m_Markers)
|
||||
{
|
||||
marker.ComputeBuckets(marker.msMin, marker.msMax);
|
||||
marker.ComputeCountBuckets(marker.countMin, marker.countMax);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetupFrameBuckets(float timeScaleMax)
|
||||
{
|
||||
float first = 0;
|
||||
float last = timeScaleMax;
|
||||
float range = last - first;
|
||||
|
||||
int maxBucketIndex = m_FrameSummary.buckets.Length - 1;
|
||||
|
||||
for (int bucketIndex = 0; bucketIndex < m_FrameSummary.buckets.Length; bucketIndex++)
|
||||
{
|
||||
m_FrameSummary.buckets[bucketIndex] = 0;
|
||||
}
|
||||
|
||||
float scale = range > 0 ? m_FrameSummary.buckets.Length / range : 0;
|
||||
foreach (var frameData in m_FrameSummary.frames)
|
||||
{
|
||||
var msFrame = frameData.ms;
|
||||
//var frameIndex = frameData.frameIndex;
|
||||
|
||||
int bucketIndex = (int)((msFrame - first) * scale);
|
||||
if (bucketIndex < 0 || bucketIndex > maxBucketIndex)
|
||||
{
|
||||
// It can occur for the highest entry in the range (max-min/range) = 1
|
||||
// if (ms > max) // Check for the spilling case
|
||||
// Debug.Log(string.Format("Frame {0}ms exceeds range {1}-{2} on frame {3}", msFrame, first, last, frameIndex));
|
||||
if (bucketIndex > maxBucketIndex)
|
||||
bucketIndex = maxBucketIndex;
|
||||
else
|
||||
bucketIndex = 0;
|
||||
}
|
||||
m_FrameSummary.buckets[bucketIndex] += 1;
|
||||
}
|
||||
|
||||
if (range == 0)
|
||||
{
|
||||
// All buckets will be the same
|
||||
for (int bucketIndex = 1; bucketIndex < m_FrameSummary.buckets.Length; bucketIndex++)
|
||||
{
|
||||
m_FrameSummary.buckets[bucketIndex] = m_FrameSummary.buckets[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CalculateThreadMedians()
|
||||
{
|
||||
foreach (var thread in m_Threads)
|
||||
{
|
||||
if (thread.frames.Count > 0)
|
||||
{
|
||||
thread.frames.Sort();
|
||||
int unusedIndex;
|
||||
|
||||
thread.msMin = GetThreadPercentageOffset(thread.frames, 0, out thread.minFrameIndex);
|
||||
thread.msLowerQuartile = GetThreadPercentageOffset(thread.frames, 25, out unusedIndex);
|
||||
thread.msMedian = GetThreadPercentageOffset(thread.frames, 50, out thread.medianFrameIndex);
|
||||
thread.msUpperQuartile = GetThreadPercentageOffset(thread.frames, 75, out unusedIndex);
|
||||
thread.msMax = GetThreadPercentageOffset(thread.frames, 100, out thread.maxFrameIndex);
|
||||
|
||||
// Put back in order of frames
|
||||
thread.frames.Sort((a, b) => a.frameIndex.CompareTo(b.frameIndex));
|
||||
}
|
||||
else
|
||||
{
|
||||
thread.msMin = 0f;
|
||||
thread.msLowerQuartile = 0f;
|
||||
thread.msMedian = 0f;
|
||||
thread.msUpperQuartile = 0f;
|
||||
thread.msMax = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Finalise(float timeScaleMax, int maxMarkerDepth)
|
||||
{
|
||||
if (m_FrameSummary.frames.Count > 0)
|
||||
{
|
||||
m_FrameSummary.frames.Sort();
|
||||
m_FrameSummary.msMean = (float)(m_FrameSummary.msTotal / m_FrameSummary.count);
|
||||
m_FrameSummary.msMedian = GetPercentageOffset(m_FrameSummary.frames, 50, out m_FrameSummary.medianFrameIndex).ms;
|
||||
int unusedIndex;
|
||||
m_FrameSummary.msLowerQuartile = GetPercentageOffset(m_FrameSummary.frames, 25, out unusedIndex).ms;
|
||||
m_FrameSummary.msUpperQuartile = GetPercentageOffset(m_FrameSummary.frames, 75, out unusedIndex).ms;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_FrameSummary.msMean = 0f;
|
||||
m_FrameSummary.msMedian = 0f;
|
||||
m_FrameSummary.msLowerQuartile = 0f;
|
||||
m_FrameSummary.msUpperQuartile = 0f;
|
||||
|
||||
// This started as float.MaxValue and won't have been updated
|
||||
m_FrameSummary.msMin = 0f;
|
||||
}
|
||||
// No longer need the frame time list ?
|
||||
//m_frameSummary.msFrame.Clear();
|
||||
m_FrameSummary.maxMarkerDepth = maxMarkerDepth;
|
||||
|
||||
if (timeScaleMax <= 0.0f)
|
||||
{
|
||||
// If max frame time range not specified then use the max frame value found.
|
||||
timeScaleMax = m_FrameSummary.msMax;
|
||||
}
|
||||
else if (timeScaleMax < m_FrameSummary.msMax)
|
||||
{
|
||||
Debug.Log(string.Format("Expanding timeScaleMax {0} to match max value found {1}", timeScaleMax, m_FrameSummary.msMax));
|
||||
|
||||
// If max frame time range too small we must expand it.
|
||||
timeScaleMax = m_FrameSummary.msMax;
|
||||
}
|
||||
|
||||
SetupMarkers();
|
||||
SetupMarkerBuckets();
|
||||
SetupFrameBuckets(timeScaleMax);
|
||||
|
||||
// Sort in median order (highest first)
|
||||
m_Markers.Sort(SortByAtMedian);
|
||||
|
||||
CalculateThreadMedians();
|
||||
}
|
||||
|
||||
int SortByAtMedian(MarkerData a, MarkerData b)
|
||||
{
|
||||
if (a.msAtMedian == b.msAtMedian)
|
||||
return -a.medianFrameIndex.CompareTo(b.medianFrameIndex);
|
||||
|
||||
return -a.msAtMedian.CompareTo(b.msAtMedian);
|
||||
}
|
||||
|
||||
public List<MarkerData> GetMarkers()
|
||||
{
|
||||
return m_Markers;
|
||||
}
|
||||
|
||||
public List<ThreadData> GetThreads()
|
||||
{
|
||||
return m_Threads;
|
||||
}
|
||||
|
||||
public ThreadData GetThreadByName(string threadNameWithIndex)
|
||||
{
|
||||
foreach (var thread in m_Threads)
|
||||
{
|
||||
if (thread.threadNameWithIndex == threadNameWithIndex)
|
||||
return thread;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public FrameSummary GetFrameSummary()
|
||||
{
|
||||
return m_FrameSummary;
|
||||
}
|
||||
|
||||
public MarkerData GetMarker(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_Markers.Count)
|
||||
return null;
|
||||
|
||||
return m_Markers[index];
|
||||
}
|
||||
|
||||
public int GetMarkerIndexByName(string markerName)
|
||||
{
|
||||
if (markerName == null)
|
||||
return -1;
|
||||
|
||||
for (int index = 0; index < m_Markers.Count; index++)
|
||||
{
|
||||
var marker = m_Markers[index];
|
||||
if (marker.name == markerName)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public MarkerData GetMarkerByName(string markerName)
|
||||
{
|
||||
if (markerName == null)
|
||||
return null;
|
||||
|
||||
for (int index = 0; index < m_Markers.Count; index++)
|
||||
{
|
||||
var marker = m_Markers[index];
|
||||
if (marker.name == markerName)
|
||||
{
|
||||
return marker;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f7cee1025e74448acbbb8bf2b82d9bfc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,414 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditorInternal;
|
||||
using System.Text.RegularExpressions;
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
internal class ProfileAnalyzer
|
||||
{
|
||||
public const int kDepthAll = -1;
|
||||
|
||||
int m_Progress = 0;
|
||||
ProfilerFrameDataIterator m_frameData;
|
||||
List<string> m_threadNames = new List<string>();
|
||||
ProfileAnalysis m_analysis;
|
||||
|
||||
public ProfileAnalyzer()
|
||||
{
|
||||
}
|
||||
|
||||
public void QuickScan()
|
||||
{
|
||||
var frameData = new ProfilerFrameDataIterator();
|
||||
|
||||
m_threadNames.Clear();
|
||||
int frameIndex = 0;
|
||||
int threadCount = frameData.GetThreadCount(0);
|
||||
frameData.SetRoot(frameIndex, 0);
|
||||
|
||||
Dictionary<string, int> threadNameCount = new Dictionary<string, int>();
|
||||
for (int threadIndex = 0; threadIndex < threadCount; ++threadIndex)
|
||||
{
|
||||
frameData.SetRoot(frameIndex, threadIndex);
|
||||
|
||||
var threadName = frameData.GetThreadName();
|
||||
var groupName = frameData.GetGroupName();
|
||||
threadName = ProfileData.GetThreadNameWithGroup(threadName, groupName);
|
||||
|
||||
if (!threadNameCount.ContainsKey(threadName))
|
||||
threadNameCount.Add(threadName, 1);
|
||||
else
|
||||
threadNameCount[threadName] += 1;
|
||||
|
||||
string threadNameWithIndex = ProfileData.ThreadNameWithIndex(threadNameCount[threadName], threadName);
|
||||
threadNameWithIndex = ProfileData.CorrectThreadName(threadNameWithIndex);
|
||||
|
||||
m_threadNames.Add(threadNameWithIndex);
|
||||
}
|
||||
|
||||
frameData.Dispose();
|
||||
}
|
||||
|
||||
public List<string> GetThreadNames()
|
||||
{
|
||||
return m_threadNames;
|
||||
}
|
||||
|
||||
void CalculateFrameTimeStats(ProfileData data, out float median, out float mean, out float standardDeviation)
|
||||
{
|
||||
List<float> frameTimes = new List<float>();
|
||||
for (int frameIndex = 0; frameIndex < data.GetFrameCount(); frameIndex++)
|
||||
{
|
||||
var frame = data.GetFrame(frameIndex);
|
||||
float msFrame = frame.msFrame;
|
||||
frameTimes.Add(msFrame);
|
||||
}
|
||||
frameTimes.Sort();
|
||||
median = frameTimes[frameTimes.Count / 2];
|
||||
|
||||
|
||||
double total = 0.0f;
|
||||
foreach (float msFrame in frameTimes)
|
||||
{
|
||||
total += msFrame;
|
||||
}
|
||||
mean = (float)(total / (double)frameTimes.Count);
|
||||
|
||||
|
||||
if (frameTimes.Count <= 1)
|
||||
{
|
||||
standardDeviation = 0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
total = 0.0f;
|
||||
foreach (float msFrame in frameTimes)
|
||||
{
|
||||
float d = msFrame - mean;
|
||||
total += (d * d);
|
||||
}
|
||||
total /= (frameTimes.Count - 1);
|
||||
standardDeviation = (float)Math.Sqrt(total);
|
||||
}
|
||||
}
|
||||
|
||||
int GetClampedOffsetToFrame(ProfileData profileData, int frameIndex)
|
||||
{
|
||||
int frameOffset = profileData.DisplayFrameToOffset(frameIndex);
|
||||
if (frameOffset < 0)
|
||||
{
|
||||
Debug.Log(string.Format("Frame index {0} offset {1} < 0, clamping", frameIndex, frameOffset));
|
||||
frameOffset = 0;
|
||||
}
|
||||
if (frameOffset >= profileData.GetFrameCount())
|
||||
{
|
||||
Debug.Log(string.Format("Frame index {0} offset {1} >= frame count {2}, clamping", frameIndex, frameOffset, profileData.GetFrameCount()));
|
||||
frameOffset = profileData.GetFrameCount() - 1;
|
||||
}
|
||||
|
||||
return frameOffset;
|
||||
}
|
||||
|
||||
public static bool MatchThreadFilter(string threadNameWithIndex, List<string> threadFilters)
|
||||
{
|
||||
if (threadFilters == null || threadFilters.Count == 0)
|
||||
return false;
|
||||
|
||||
if (threadFilters.Contains(threadNameWithIndex))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsNullOrWhiteSpace(string s)
|
||||
{
|
||||
// return string.IsNullOrWhiteSpace(parentMarker);
|
||||
if (s == null || Regex.IsMatch(s, @"^[\s]*$"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public ProfileAnalysis Analyze(ProfileData profileData, List<int> selectionIndices, List<string> threadFilters, int depthFilter, bool selfTimes = false, string parentMarker = null, float timeScaleMax = 0)
|
||||
{
|
||||
m_Progress = 0;
|
||||
if (profileData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (profileData.GetFrameCount() <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int frameCount = selectionIndices.Count;
|
||||
if (frameCount < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (profileData.HasFrames && !profileData.HasThreads)
|
||||
{
|
||||
if (!ProfileData.Load(profileData.FilePath, out profileData))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
bool processMarkers = (threadFilters != null);
|
||||
|
||||
ProfileAnalysis analysis = new ProfileAnalysis();
|
||||
if (selectionIndices.Count > 0)
|
||||
analysis.SetRange(selectionIndices[0], selectionIndices[selectionIndices.Count - 1]);
|
||||
else
|
||||
analysis.SetRange(0, 0);
|
||||
|
||||
m_threadNames.Clear();
|
||||
|
||||
int maxMarkerDepthFound = 0;
|
||||
var threads = new Dictionary<string, ThreadData>();
|
||||
var markers = new Dictionary<string, MarkerData>();
|
||||
var allMarkers = new Dictionary<string, int>();
|
||||
|
||||
|
||||
bool filteringByParentMarker = false;
|
||||
int parentMarkerIndex = -1;
|
||||
if (!IsNullOrWhiteSpace(parentMarker))
|
||||
{
|
||||
// Returns -1 if this marker doesn't exist in the data set
|
||||
parentMarkerIndex = profileData.GetMarkerIndex(parentMarker);
|
||||
filteringByParentMarker = true;
|
||||
}
|
||||
|
||||
int at = 0;
|
||||
foreach (int frameIndex in selectionIndices)
|
||||
{
|
||||
int frameOffset = profileData.DisplayFrameToOffset(frameIndex);
|
||||
var frameData = profileData.GetFrame(frameOffset);
|
||||
if (frameData == null)
|
||||
continue;
|
||||
var msFrame = frameData.msFrame;
|
||||
|
||||
analysis.UpdateSummary(frameIndex, msFrame);
|
||||
|
||||
if (processMarkers)
|
||||
{
|
||||
// get the file reader in case we need to rebuild the markers rather than opening
|
||||
// the file for every marker
|
||||
for (int threadIndex = 0; threadIndex < frameData.threads.Count; threadIndex++)
|
||||
{
|
||||
float msTimeOfMinDepthMarkers = 0.0f;
|
||||
float msIdleTimeOfMinDepthMarkers = 0.0f;
|
||||
|
||||
var threadData = frameData.threads[threadIndex];
|
||||
var threadNameWithIndex = profileData.GetThreadName(threadData);
|
||||
|
||||
ThreadData thread;
|
||||
if (!threads.ContainsKey(threadNameWithIndex))
|
||||
{
|
||||
m_threadNames.Add(threadNameWithIndex);
|
||||
|
||||
thread = new ThreadData(threadNameWithIndex);
|
||||
|
||||
analysis.AddThread(thread);
|
||||
threads[threadNameWithIndex] = thread;
|
||||
|
||||
// Update threadsInGroup for all thread records of the same group name
|
||||
foreach (var threadAt in threads.Values)
|
||||
{
|
||||
if (threadAt == thread)
|
||||
continue;
|
||||
|
||||
if (thread.threadGroupName == threadAt.threadGroupName)
|
||||
{
|
||||
threadAt.threadsInGroup += 1;
|
||||
thread.threadsInGroup += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
thread = threads[threadNameWithIndex];
|
||||
}
|
||||
|
||||
bool include = MatchThreadFilter(threadNameWithIndex, threadFilters);
|
||||
|
||||
int parentMarkerDepth = -1;
|
||||
|
||||
if (threadData.markers.Count != threadData.markerCount)
|
||||
{
|
||||
if (!threadData.ReadMarkers(profileData.FilePath))
|
||||
{
|
||||
Debug.LogError("failed to read markers");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (ProfileMarker markerData in threadData.markers)
|
||||
{
|
||||
string markerName = profileData.GetMarkerName(markerData);
|
||||
if (!allMarkers.ContainsKey(markerName))
|
||||
allMarkers.Add(markerName, 1);
|
||||
// No longer counting how many times we see the marker (this saves 1/3 of the analysis time).
|
||||
|
||||
float ms = markerData.msMarkerTotal - (selfTimes ? markerData.msChildren : 0);
|
||||
var markerDepth = markerData.depth;
|
||||
if (markerDepth > maxMarkerDepthFound)
|
||||
maxMarkerDepthFound = markerDepth;
|
||||
|
||||
if (markerDepth == 1)
|
||||
{
|
||||
if (markerName == "Idle")
|
||||
msIdleTimeOfMinDepthMarkers += ms;
|
||||
else
|
||||
msTimeOfMinDepthMarkers += ms;
|
||||
}
|
||||
|
||||
if (!include)
|
||||
continue;
|
||||
|
||||
if (depthFilter != kDepthAll && markerDepth != depthFilter)
|
||||
continue;
|
||||
|
||||
// If only looking for markers below the parent
|
||||
if (filteringByParentMarker)
|
||||
{
|
||||
// If found the parent marker
|
||||
if (markerData.nameIndex == parentMarkerIndex)
|
||||
{
|
||||
// And we are not already below the parent higher in the depth tree
|
||||
if (parentMarkerDepth < 0)
|
||||
{
|
||||
// record the parent marker depth
|
||||
parentMarkerDepth = markerData.depth;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we are now above or beside the parent marker then we are done for this level
|
||||
if (markerData.depth <= parentMarkerDepth)
|
||||
{
|
||||
parentMarkerDepth = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (parentMarkerDepth < 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
MarkerData marker;
|
||||
if (markers.ContainsKey(markerName))
|
||||
{
|
||||
marker = markers[markerName];
|
||||
if (!marker.threads.Contains(threadNameWithIndex))
|
||||
marker.threads.Add(threadNameWithIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
marker = new MarkerData(markerName);
|
||||
marker.firstFrameIndex = frameIndex;
|
||||
marker.minDepth = markerDepth;
|
||||
marker.maxDepth = markerDepth;
|
||||
marker.threads.Add(threadNameWithIndex);
|
||||
analysis.AddMarker(marker);
|
||||
markers.Add(markerName, marker);
|
||||
}
|
||||
|
||||
marker.count += 1;
|
||||
marker.msTotal += ms;
|
||||
|
||||
// Individual marker time (not total over frame)
|
||||
if (ms < marker.msMinIndividual)
|
||||
{
|
||||
marker.msMinIndividual = ms;
|
||||
marker.minIndividualFrameIndex = frameIndex;
|
||||
}
|
||||
if (ms > marker.msMaxIndividual)
|
||||
{
|
||||
marker.msMaxIndividual = ms;
|
||||
marker.maxIndividualFrameIndex = frameIndex;
|
||||
}
|
||||
|
||||
// Record highest depth foun
|
||||
if (markerDepth < marker.minDepth)
|
||||
marker.minDepth = markerDepth;
|
||||
if (markerDepth > marker.maxDepth)
|
||||
marker.maxDepth = markerDepth;
|
||||
|
||||
FrameTime frameTime;
|
||||
if (frameIndex != marker.lastFrame)
|
||||
{
|
||||
marker.presentOnFrameCount += 1;
|
||||
frameTime = new FrameTime(frameIndex, ms, 1);
|
||||
marker.frames.Add(frameTime);
|
||||
marker.lastFrame = frameIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
frameTime = marker.frames[marker.frames.Count - 1];
|
||||
frameTime = new FrameTime(frameTime.frameIndex, frameTime.ms + ms, frameTime.count + 1);
|
||||
marker.frames[marker.frames.Count - 1] = frameTime;
|
||||
}
|
||||
}
|
||||
|
||||
if (include)
|
||||
thread.frames.Add(new ThreadFrameTime(frameIndex, msTimeOfMinDepthMarkers, msIdleTimeOfMinDepthMarkers));
|
||||
}
|
||||
}
|
||||
|
||||
at++;
|
||||
m_Progress = (100 * at) / frameCount;
|
||||
}
|
||||
|
||||
analysis.GetFrameSummary().totalMarkers = allMarkers.Count;
|
||||
analysis.Finalise(timeScaleMax, maxMarkerDepthFound);
|
||||
|
||||
/*
|
||||
foreach (int frameIndex in selectionIndices)
|
||||
{
|
||||
int frameOffset = profileData.DisplayFrameToOffset(frameIndex);
|
||||
|
||||
var frameData = profileData.GetFrame(frameOffset);
|
||||
foreach (var threadData in frameData.threads)
|
||||
{
|
||||
var threadNameWithIndex = profileData.GetThreadName(threadData);
|
||||
|
||||
if (filterThreads && threadFilter != threadNameWithIndex)
|
||||
continue;
|
||||
|
||||
const bool enterChildren = true;
|
||||
foreach (var markerData in threadData.markers)
|
||||
{
|
||||
var markerName = markerData.name;
|
||||
var ms = markerData.msFrame;
|
||||
var markerDepth = markerData.depth;
|
||||
if (depthFilter != kDepthAll && markerDepth != depthFilter)
|
||||
continue;
|
||||
|
||||
MarkerData marker = markers[markerName];
|
||||
bucketIndex = (range > 0) ? (int)(((marker.buckets.Length-1) * (ms - first)) / range) : 0;
|
||||
if (bucketIndex<0 || bucketIndex > (marker.buckets.Length - 1))
|
||||
{
|
||||
// This can happen if a single marker range is longer than the frame start end (which could occur if running on a separate thread)
|
||||
// Debug.Log(string.Format("Marker {0} : {1}ms exceeds range {2}-{3} on frame {4}", marker.name, ms, first, last, frameIndex));
|
||||
if (bucketIndex > (marker.buckets.Length - 1))
|
||||
bucketIndex = (marker.buckets.Length - 1);
|
||||
else
|
||||
bucketIndex = 0;
|
||||
}
|
||||
marker.individualBuckets[bucketIndex] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
m_Progress = 100;
|
||||
return analysis;
|
||||
}
|
||||
|
||||
public int GetProgress()
|
||||
{
|
||||
return m_Progress;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 317d76fd107c444c7822b9c99d48bd30
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,255 @@
|
|||
using UnityEngine;
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
internal class ProfileAnalyzerExportWindow : EditorWindow
|
||||
{
|
||||
internal static class Styles
|
||||
{
|
||||
public static readonly GUIContent markerTable = new GUIContent("Marker table", "Export data from the single view marker table");
|
||||
public static readonly GUIContent singleFrameTimes = new GUIContent("Single Frame Times", "Export frame time data from the single view");
|
||||
public static readonly GUIContent comparisonFrameTimes = new GUIContent("Comparison Frame Times", "Export frame time data from the comparison view");
|
||||
}
|
||||
|
||||
ProfileAnalyzerWindow m_ProfileAnalyzerWindow;
|
||||
|
||||
ProfileDataView m_ProfileDataView;
|
||||
ProfileDataView m_LeftDataView;
|
||||
ProfileDataView m_RightDataView;
|
||||
|
||||
static public ProfileAnalyzerExportWindow FindOpenWindow()
|
||||
{
|
||||
UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(typeof(ProfileAnalyzerExportWindow));
|
||||
if (windows != null && windows.Length > 0)
|
||||
return windows[0] as ProfileAnalyzerExportWindow;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static public bool IsOpen()
|
||||
{
|
||||
if (FindOpenWindow() != null)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static public ProfileAnalyzerExportWindow Open(float screenX, float screenY, ProfileDataView profileSingleView, ProfileDataView profileLeftView, ProfileDataView profileRightView, ProfileAnalyzerWindow profileAnalyzerWindow)
|
||||
{
|
||||
ProfileAnalyzerExportWindow window = GetWindow<ProfileAnalyzerExportWindow>("Export");
|
||||
window.minSize = new Vector2(200, 140);
|
||||
window.position = new Rect(screenX, screenY, 200, 140);
|
||||
window.m_ProfileAnalyzerWindow = profileAnalyzerWindow;
|
||||
window.SetData(profileSingleView, profileLeftView, profileRightView);
|
||||
window.Show();
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
static public void CloseAll()
|
||||
{
|
||||
ProfileAnalyzerExportWindow window = GetWindow<ProfileAnalyzerExportWindow>("Export");
|
||||
window.Close();
|
||||
}
|
||||
|
||||
public void SetData(ProfileDataView profileDataView, ProfileDataView leftDataView, ProfileDataView rightDataView)
|
||||
{
|
||||
m_ProfileDataView = profileDataView;
|
||||
m_LeftDataView = leftDataView;
|
||||
m_RightDataView = rightDataView;
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true));
|
||||
GUILayout.Label("Export as CSV:");
|
||||
GUILayout.Label("");
|
||||
|
||||
GUILayout.Label("Single View");
|
||||
|
||||
bool enabled = GUI.enabled;
|
||||
if (m_ProfileDataView == null || !m_ProfileDataView.IsDataValid())
|
||||
GUI.enabled = false;
|
||||
if (GUILayout.Button(Styles.markerTable))
|
||||
SaveMarkerTableCSV();
|
||||
GUI.enabled = enabled;
|
||||
|
||||
if (m_ProfileDataView == null || m_ProfileDataView.analysis == null)
|
||||
GUI.enabled = false;
|
||||
if (GUILayout.Button(Styles.singleFrameTimes))
|
||||
SaveFrameTimesCSV();
|
||||
GUI.enabled = enabled;
|
||||
|
||||
GUILayout.Label("Comparison View");
|
||||
|
||||
if (m_LeftDataView == null || !m_LeftDataView.IsDataValid() || m_RightDataView == null || !m_RightDataView.IsDataValid())
|
||||
GUI.enabled = false;
|
||||
if (GUILayout.Button(Styles.comparisonFrameTimes))
|
||||
SaveComparisonFrameTimesCSV();
|
||||
GUI.enabled = enabled;
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
void SaveMarkerTableCSV()
|
||||
{
|
||||
if (m_ProfileDataView.analysis == null)
|
||||
return;
|
||||
|
||||
string path = EditorUtility.SaveFilePanel("Save marker table CSV data", "", "markerTable.csv", "csv");
|
||||
if (path.Length != 0)
|
||||
{
|
||||
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
|
||||
using (StreamWriter file = new StreamWriter(path))
|
||||
{
|
||||
file.Write("Name, ");
|
||||
file.Write("Median Time, Min Time, Max Time, ");
|
||||
file.Write("Median Frame Index, Min Frame Index, Max Frame Index, ");
|
||||
file.Write("Min Depth, Max Depth, ");
|
||||
file.Write("Total Time, ");
|
||||
file.Write("Mean Time, Time Lower Quartile, Time Upper Quartile, ");
|
||||
file.Write("Count Total, Count Median, Count Min, Count Max, ");
|
||||
file.Write("Number of frames containing Marker, ");
|
||||
file.Write("First Frame Index, ");
|
||||
file.Write("Time Min Individual, Time Max Individual, ");
|
||||
file.Write("Min Individual Frame, Max Individual Frame, ");
|
||||
file.WriteLine("Time at Median Frame");
|
||||
|
||||
List<MarkerData> markerData = m_ProfileDataView.analysis.GetMarkers();
|
||||
markerData.Sort();
|
||||
foreach (MarkerData marker in markerData)
|
||||
{
|
||||
var markerName = marker.name;
|
||||
if (markerName.IndexOf('\"') >= 0)
|
||||
// replace all double quotation marks with single ones to avoid this breaking the escaping with double quotation marks
|
||||
markerName = markerName.Replace('\"', '\'');
|
||||
|
||||
int medianFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(marker.medianFrameIndex, m_ProfileDataView);
|
||||
int minFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(marker.minFrameIndex, m_ProfileDataView);
|
||||
int maxFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(marker.maxFrameIndex, m_ProfileDataView);
|
||||
int firstFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(marker.firstFrameIndex, m_ProfileDataView);
|
||||
int minIndividualFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(marker.minIndividualFrameIndex, m_ProfileDataView);
|
||||
int maxIndividualFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(marker.maxIndividualFrameIndex, m_ProfileDataView);
|
||||
|
||||
// "Escape" marker names in case it has commas in it.
|
||||
file.Write("\"{0}\",", markerName);
|
||||
file.Write("{0},{1},{2},",
|
||||
marker.msMedian, marker.msMin, marker.msMax);
|
||||
file.Write("{0},{1},{2},",
|
||||
medianFrameIndex, minFrameIndex, maxFrameIndex);
|
||||
file.Write("{0},{1},",
|
||||
marker.minDepth, marker.maxDepth);
|
||||
file.Write("{0},",
|
||||
marker.msTotal);
|
||||
file.Write("{0},{1},{2},",
|
||||
marker.msMean, marker.msLowerQuartile, marker.msUpperQuartile);
|
||||
file.Write("{0},{1},{2},{3},",
|
||||
marker.count, marker.countMedian, marker.countMin, marker.countMax);
|
||||
file.Write("{0},", marker.presentOnFrameCount);
|
||||
file.Write("{0},", firstFrameIndex);
|
||||
file.Write("{0},{1},",
|
||||
marker.msMinIndividual, marker.msMaxIndividual);
|
||||
file.Write("{0},{1},",
|
||||
minIndividualFrameIndex, maxIndividualFrameIndex);
|
||||
file.WriteLine("{0}", marker.msAtMedian);
|
||||
}
|
||||
}
|
||||
ProfileAnalyzerAnalytics.SendUIButtonEvent(ProfileAnalyzerAnalytics.UIButton.ExportSingleFrames, analytic);
|
||||
}
|
||||
}
|
||||
|
||||
void SaveFrameTimesCSV()
|
||||
{
|
||||
if (m_ProfileDataView == null)
|
||||
return;
|
||||
if (!m_ProfileDataView.IsDataValid())
|
||||
return;
|
||||
|
||||
string path = EditorUtility.SaveFilePanel("Save frame time CSV data", "", "frameTime.csv", "csv");
|
||||
if (path.Length != 0)
|
||||
{
|
||||
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
|
||||
using (StreamWriter file = new StreamWriter(path))
|
||||
{
|
||||
file.WriteLine("Frame Offset, Frame Index, Frame Time (ms), Time from first frame (ms)");
|
||||
float maxFrames = m_ProfileDataView.data.GetFrameCount();
|
||||
|
||||
var frame = m_ProfileDataView.data.GetFrame(0);
|
||||
// msStartTime isn't very accurate so we don't use it
|
||||
|
||||
double msTimePassed = 0.0;
|
||||
for (int frameOffset = 0; frameOffset < maxFrames; frameOffset++)
|
||||
{
|
||||
frame = m_ProfileDataView.data.GetFrame(frameOffset);
|
||||
int frameIndex = m_ProfileDataView.data.OffsetToDisplayFrame(frameOffset);
|
||||
frameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(frameIndex, m_ProfileDataView);
|
||||
|
||||
float msFrame = frame.msFrame;
|
||||
file.WriteLine("{0},{1},{2},{3}",
|
||||
frameOffset, frameIndex, msFrame, msTimePassed);
|
||||
|
||||
msTimePassed += msFrame;
|
||||
}
|
||||
}
|
||||
ProfileAnalyzerAnalytics.SendUIButtonEvent(ProfileAnalyzerAnalytics.UIButton.ExportSingleFrames, analytic);
|
||||
}
|
||||
}
|
||||
|
||||
void SaveComparisonFrameTimesCSV()
|
||||
{
|
||||
if (m_LeftDataView == null || m_RightDataView == null)
|
||||
return;
|
||||
if (!m_LeftDataView.IsDataValid() || !m_RightDataView.IsDataValid())
|
||||
return;
|
||||
|
||||
string path = EditorUtility.SaveFilePanel("Save comparison frame time CSV data", "", "frameTimeComparison.csv", "csv");
|
||||
if (path.Length != 0)
|
||||
{
|
||||
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
|
||||
using (StreamWriter file = new StreamWriter(path))
|
||||
{
|
||||
file.Write("Frame Offset, ");
|
||||
file.Write("Left Frame Index, Right Frame Index, ");
|
||||
file.Write("Left Frame Time (ms), Left time from first frame (ms), ");
|
||||
file.Write("Right Frame Time (ms), Right time from first frame (ms), ");
|
||||
file.WriteLine("Frame Time Diff (ms)");
|
||||
float maxFrames = Math.Max(m_LeftDataView.data.GetFrameCount(), m_RightDataView.data.GetFrameCount());
|
||||
|
||||
var leftFrame = m_LeftDataView.data.GetFrame(0);
|
||||
var rightFrame = m_RightDataView.data.GetFrame(0);
|
||||
|
||||
// msStartTime isn't very accurate so we don't use it
|
||||
|
||||
double msTimePassedLeft = 0.0;
|
||||
double msTimePassedRight = 0.0;
|
||||
|
||||
for (int frameOffset = 0; frameOffset < maxFrames; frameOffset++)
|
||||
{
|
||||
leftFrame = m_LeftDataView.data.GetFrame(frameOffset);
|
||||
rightFrame = m_RightDataView.data.GetFrame(frameOffset);
|
||||
int leftFrameIndex = m_LeftDataView.data.OffsetToDisplayFrame(frameOffset);
|
||||
leftFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(leftFrameIndex, m_LeftDataView);
|
||||
int rightFrameIndex = m_RightDataView.data.OffsetToDisplayFrame(frameOffset);
|
||||
rightFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(rightFrameIndex, m_RightDataView);
|
||||
|
||||
float msFrameLeft = leftFrame != null ? leftFrame.msFrame : 0;
|
||||
float msFrameRight = rightFrame != null ? rightFrame.msFrame : 0;
|
||||
float msFrameDiff = msFrameRight - msFrameLeft;
|
||||
file.Write("{0},", frameOffset);
|
||||
file.Write("{0},{1},", leftFrameIndex, rightFrameIndex);
|
||||
file.Write("{0},{1},", msFrameLeft, msTimePassedLeft);
|
||||
file.Write("{0},{1},", msFrameRight, msTimePassedRight);
|
||||
file.WriteLine("{0}", msFrameDiff);
|
||||
|
||||
msTimePassedLeft += msFrameLeft;
|
||||
msTimePassedRight += msFrameRight;
|
||||
}
|
||||
}
|
||||
ProfileAnalyzerAnalytics.SendUIButtonEvent(ProfileAnalyzerAnalytics.UIButton.ExportComparisonFrames, analytic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ccd88eea7c8284ac18aff2e257aeb84a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 493b79ea9c9be44ed8b702014d3ac49c
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0dc59854f72ef4a1fb6dbb8d0cd6aea3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,760 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditorInternal;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
[Serializable]
|
||||
internal class ProfileData
|
||||
{
|
||||
static int latestVersion = 7;
|
||||
/*
|
||||
Version 1 - Initial version. Thread names index:threadName (Some invalid thread names count:threadName index)
|
||||
Version 2 - Added frame start time.
|
||||
Version 3 - Saved out marker children times in the data (Never needed so rapidly skipped)
|
||||
Version 4 - Removed the child times again (at this point data was saved with 1 less frame at start and end)
|
||||
Version 5 - Updated the thread names to include the thread group as a prefix (index:threadGroup.threadName, index is 1 based, original is 0 based)
|
||||
Version 6 - fixed msStartTime (previously was 'seconds')
|
||||
Version 7 - Data now only skips the frame at the end
|
||||
*/
|
||||
static Regex trailingDigit = new Regex(@"^(.*[^\s])[\s]+([\d]+)$", RegexOptions.Compiled);
|
||||
public int Version { get; private set; }
|
||||
public int FrameIndexOffset { get; private set; }
|
||||
public bool FirstFrameIncomplete;
|
||||
public bool LastFrameIncomplete;
|
||||
List<ProfileFrame> frames = new List<ProfileFrame>();
|
||||
List<string> markerNames = new List<string>();
|
||||
List<string> threadNames = new List<string>();
|
||||
Dictionary<string, int> markerNamesDict = new Dictionary<string, int>();
|
||||
Dictionary<string, int> threadNameDict = new Dictionary<string, int>();
|
||||
public string FilePath { get; private set; }
|
||||
static float s_Progress = 0;
|
||||
|
||||
public ProfileData()
|
||||
{
|
||||
FrameIndexOffset = 0;
|
||||
FilePath = string.Empty;
|
||||
Version = latestVersion;
|
||||
}
|
||||
|
||||
public ProfileData(string filename)
|
||||
{
|
||||
FrameIndexOffset = 0;
|
||||
FilePath = filename;
|
||||
Version = latestVersion;
|
||||
}
|
||||
|
||||
void Read()
|
||||
{
|
||||
if (string.IsNullOrEmpty(FilePath))
|
||||
throw new Exception("File path is invalid");
|
||||
|
||||
using (var reader = new BinaryReader(File.Open(FilePath, FileMode.Open)))
|
||||
{
|
||||
s_Progress = 0;
|
||||
Version = reader.ReadInt32();
|
||||
if (Version < 0 || Version > latestVersion)
|
||||
{
|
||||
throw new Exception(String.Format("File version unsupported: {0} != {1} expected, at path: {2}", Version, latestVersion, FilePath));
|
||||
}
|
||||
|
||||
FrameIndexOffset = reader.ReadInt32();
|
||||
int frameCount = reader.ReadInt32();
|
||||
frames.Clear();
|
||||
for (int frame = 0; frame < frameCount; frame++)
|
||||
{
|
||||
frames.Add(new ProfileFrame(reader, Version));
|
||||
s_Progress = (float)frame / frameCount;
|
||||
}
|
||||
|
||||
int markerCount = reader.ReadInt32();
|
||||
markerNames.Clear();
|
||||
for (int marker = 0; marker < markerCount; marker++)
|
||||
{
|
||||
markerNames.Add(reader.ReadString());
|
||||
s_Progress = (float)marker / markerCount;
|
||||
}
|
||||
|
||||
int threadCount = reader.ReadInt32();
|
||||
threadNames.Clear();
|
||||
for (int thread = 0; thread < threadCount; thread++)
|
||||
{
|
||||
var threadNameWithIndex = reader.ReadString();
|
||||
|
||||
threadNameWithIndex = CorrectThreadName(threadNameWithIndex);
|
||||
|
||||
threadNames.Add(threadNameWithIndex);
|
||||
s_Progress = (float)thread / threadCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void DeleteTmpFiles()
|
||||
{
|
||||
if (ProfileAnalyzerWindow.FileInTempDir(FilePath))
|
||||
File.Delete(FilePath);
|
||||
}
|
||||
|
||||
bool IsFrameSame(int frameIndex, ProfileData other)
|
||||
{
|
||||
ProfileFrame thisFrame = GetFrame(frameIndex);
|
||||
ProfileFrame otherFrame = other.GetFrame(frameIndex);
|
||||
return thisFrame.IsSame(otherFrame);
|
||||
}
|
||||
|
||||
public bool IsSame(ProfileData other)
|
||||
{
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
int frameCount = GetFrameCount();
|
||||
if (frameCount != other.GetFrameCount())
|
||||
{
|
||||
// Frame counts differ
|
||||
return false;
|
||||
}
|
||||
|
||||
if (frameCount == 0)
|
||||
{
|
||||
// Both empty
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!IsFrameSame(0, other))
|
||||
return false;
|
||||
if (!IsFrameSame(frameCount - 1, other))
|
||||
return false;
|
||||
|
||||
// Close enough if same number of frames and first/last have exactly the same frame time and time offset.
|
||||
// If we see false matches we could add a full has of the data on load/pull
|
||||
return true;
|
||||
}
|
||||
|
||||
static public string ThreadNameWithIndex(int index, string threadName)
|
||||
{
|
||||
return string.Format("{0}:{1}", index, threadName);
|
||||
}
|
||||
|
||||
public void SetFrameIndexOffset(int offset)
|
||||
{
|
||||
FrameIndexOffset = offset;
|
||||
}
|
||||
|
||||
public int GetFrameCount()
|
||||
{
|
||||
return frames.Count;
|
||||
}
|
||||
|
||||
public ProfileFrame GetFrame(int offset)
|
||||
{
|
||||
if (offset < 0 || offset >= frames.Count)
|
||||
return null;
|
||||
|
||||
return frames[offset];
|
||||
}
|
||||
|
||||
public List<string> GetMarkerNames()
|
||||
{
|
||||
return markerNames;
|
||||
}
|
||||
|
||||
public List<string> GetThreadNames()
|
||||
{
|
||||
return threadNames;
|
||||
}
|
||||
|
||||
public int GetThreadCount()
|
||||
{
|
||||
return threadNames.Count;
|
||||
}
|
||||
|
||||
public int OffsetToDisplayFrame(int offset)
|
||||
{
|
||||
return offset + (1 + FrameIndexOffset);
|
||||
}
|
||||
|
||||
public int DisplayFrameToOffset(int displayFrame)
|
||||
{
|
||||
return displayFrame - (1 + FrameIndexOffset);
|
||||
}
|
||||
|
||||
public void AddThreadName(string threadName, ProfileThread thread)
|
||||
{
|
||||
threadName = CorrectThreadName(threadName);
|
||||
|
||||
int index = -1;
|
||||
|
||||
if (!threadNameDict.TryGetValue(threadName, out index))
|
||||
{
|
||||
threadNames.Add(threadName);
|
||||
index = threadNames.Count - 1;
|
||||
|
||||
threadNameDict.Add(threadName, index);
|
||||
}
|
||||
|
||||
thread.threadIndex = index;
|
||||
}
|
||||
|
||||
public void AddMarkerName(string markerName, ProfileMarker marker)
|
||||
{
|
||||
int index = -1;
|
||||
if (!markerNamesDict.TryGetValue(markerName, out index))
|
||||
{
|
||||
markerNames.Add(markerName);
|
||||
index = markerNames.Count - 1;
|
||||
|
||||
markerNamesDict.Add(markerName, index);
|
||||
}
|
||||
|
||||
marker.nameIndex = index;
|
||||
}
|
||||
|
||||
public string GetThreadName(ProfileThread thread)
|
||||
{
|
||||
return threadNames[thread.threadIndex];
|
||||
}
|
||||
|
||||
public string GetMarkerName(ProfileMarker marker)
|
||||
{
|
||||
return markerNames[marker.nameIndex];
|
||||
}
|
||||
|
||||
public int GetMarkerIndex(string markerName)
|
||||
{
|
||||
for (int nameIndex = 0; nameIndex < markerNames.Count; ++nameIndex)
|
||||
{
|
||||
if (markerName == markerNames[nameIndex])
|
||||
return nameIndex;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void Add(ProfileFrame frame)
|
||||
{
|
||||
frames.Add(frame);
|
||||
}
|
||||
|
||||
void WriteInternal(string filepath)
|
||||
{
|
||||
using (var writer = new BinaryWriter(File.Open(filepath, FileMode.OpenOrCreate)))
|
||||
{
|
||||
Version = latestVersion;
|
||||
|
||||
writer.Write(Version);
|
||||
writer.Write(FrameIndexOffset);
|
||||
|
||||
writer.Write(frames.Count);
|
||||
foreach (var frame in frames)
|
||||
{
|
||||
frame.Write(writer);
|
||||
}
|
||||
|
||||
writer.Write(markerNames.Count);
|
||||
foreach (var markerName in markerNames)
|
||||
{
|
||||
writer.Write(markerName);
|
||||
}
|
||||
|
||||
writer.Write(threadNames.Count);
|
||||
foreach (var threadName in threadNames)
|
||||
{
|
||||
writer.Write(threadName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void Write()
|
||||
{
|
||||
//ensure that we can always write to the temp location at least
|
||||
if (string.IsNullOrEmpty(FilePath))
|
||||
FilePath = ProfileAnalyzerWindow.TmpPath;
|
||||
|
||||
WriteInternal(FilePath);
|
||||
}
|
||||
|
||||
internal void WriteTo(string path)
|
||||
{
|
||||
//no point in trying to save on top of ourselves
|
||||
if (path == FilePath)
|
||||
return;
|
||||
|
||||
if (!string.IsNullOrEmpty(FilePath) && File.Exists(FilePath))
|
||||
{
|
||||
if (File.Exists(path))
|
||||
File.Delete(path);
|
||||
|
||||
File.Copy(FilePath, path);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteInternal(path);
|
||||
}
|
||||
FilePath = path;
|
||||
}
|
||||
|
||||
public static string CorrectThreadName(string threadNameWithIndex)
|
||||
{
|
||||
var info = threadNameWithIndex.Split(':');
|
||||
if (info.Length >= 2)
|
||||
{
|
||||
string threadGroupIndexString = info[0];
|
||||
string threadName = info[1];
|
||||
if (threadName.Trim() == "")
|
||||
{
|
||||
// Scan seen with no thread name
|
||||
threadNameWithIndex = string.Format("{0}:[Unknown]", threadGroupIndexString);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Some scans have thread names such as
|
||||
// "1:Worker Thread 0"
|
||||
// "1:Worker Thread 1"
|
||||
// rather than
|
||||
// "1:Worker Thread"
|
||||
// "2:Worker Thread"
|
||||
// Update to the second format so the 'All' case is correctly determined
|
||||
Match m = trailingDigit.Match(threadName);
|
||||
if (m.Success)
|
||||
{
|
||||
string threadNamePrefix = m.Groups[1].Value;
|
||||
int threadGroupIndex = 1 + int.Parse(m.Groups[2].Value);
|
||||
|
||||
threadNameWithIndex = string.Format("{0}:{1}", threadGroupIndex, threadNamePrefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
threadNameWithIndex = threadNameWithIndex.Trim();
|
||||
|
||||
return threadNameWithIndex;
|
||||
}
|
||||
|
||||
public static string GetThreadNameWithGroup(string threadName, string groupName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(groupName))
|
||||
return threadName;
|
||||
|
||||
return string.Format("{0}.{1}", groupName, threadName);
|
||||
}
|
||||
|
||||
public static string GetThreadNameWithoutGroup(string threadNameWithGroup, out string groupName)
|
||||
{
|
||||
string[] tokens = threadNameWithGroup.Split('.');
|
||||
if (tokens.Length <= 1)
|
||||
{
|
||||
groupName = "";
|
||||
return tokens[0];
|
||||
}
|
||||
|
||||
groupName = tokens[0];
|
||||
return tokens[1].TrimStart();
|
||||
}
|
||||
|
||||
internal bool HasFrames
|
||||
{
|
||||
get
|
||||
{
|
||||
return frames != null && frames.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal bool HasThreads
|
||||
{
|
||||
get
|
||||
{
|
||||
return frames[0].threads != null && frames[0].threads.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal bool NeedsMarkerRebuild
|
||||
{
|
||||
get
|
||||
{
|
||||
if (frames.Count > 0 && frames[0].threads.Count > 0)
|
||||
return frames[0].threads[0].markers.Count != frames[0].threads[0].markerCount;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Save(string filename, ProfileData data)
|
||||
{
|
||||
if (data == null)
|
||||
return false;
|
||||
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
return false;
|
||||
|
||||
if (filename.EndsWith(".json"))
|
||||
{
|
||||
var json = JsonUtility.ToJson(data);
|
||||
File.WriteAllText(filename, json);
|
||||
}
|
||||
else if (filename.EndsWith(".padata"))
|
||||
{
|
||||
FileStream stream = File.Create(filename);
|
||||
var formatter = new BinaryFormatter();
|
||||
formatter.Serialize(stream, data);
|
||||
stream.Close();
|
||||
}
|
||||
else if (filename.EndsWith(".pdata"))
|
||||
{
|
||||
data.WriteTo(filename);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool Load(string filename, out ProfileData data)
|
||||
{
|
||||
if (filename.EndsWith(".json"))
|
||||
{
|
||||
string json = File.ReadAllText(filename);
|
||||
data = JsonUtility.FromJson<ProfileData>(json);
|
||||
}
|
||||
else if (filename.EndsWith(".padata"))
|
||||
{
|
||||
FileStream stream = File.OpenRead(filename);
|
||||
var formatter = new BinaryFormatter();
|
||||
data = (ProfileData)formatter.Deserialize(stream);
|
||||
stream.Close();
|
||||
|
||||
if (data.Version != latestVersion)
|
||||
{
|
||||
Debug.Log(String.Format("Unable to load file. Incorrect file version in {0} : (file {1} != {2} expected", filename, data.Version, latestVersion));
|
||||
data = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (filename.EndsWith(".pdata"))
|
||||
{
|
||||
if (!File.Exists(filename))
|
||||
{
|
||||
data = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
data = new ProfileData(filename);
|
||||
data.Read();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = e.Message;
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
Debug.Log(e.Message);
|
||||
data = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string errorMessage;
|
||||
if (filename.EndsWith(".data"))
|
||||
{
|
||||
errorMessage = "Unable to load file. Profiler captures (.data) should be loaded in the Profiler Window and then pulled into the Analyzer via its Pull Data button.";
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = string.Format("Unable to load file. Unsupported file format: '{0}'.", Path.GetExtension(filename));
|
||||
}
|
||||
|
||||
Debug.Log(errorMessage);
|
||||
data = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
data.Finalise();
|
||||
return true;
|
||||
}
|
||||
|
||||
void PushMarker(Stack<ProfileMarker> markerStack, ProfileMarker markerData)
|
||||
{
|
||||
Debug.Assert(markerData.depth == markerStack.Count + 1);
|
||||
markerStack.Push(markerData);
|
||||
}
|
||||
|
||||
ProfileMarker PopMarkerAndRecordTimeInParent(Stack<ProfileMarker> markerStack)
|
||||
{
|
||||
ProfileMarker child = markerStack.Pop();
|
||||
|
||||
ProfileMarker parentMarker = (markerStack.Count > 0) ? markerStack.Peek() : null;
|
||||
|
||||
// Record the last markers time in its parent
|
||||
if (parentMarker != null)
|
||||
parentMarker.msChildren += child.msMarkerTotal;
|
||||
|
||||
return parentMarker;
|
||||
}
|
||||
|
||||
public void Finalise()
|
||||
{
|
||||
CalculateMarkerChildTimes();
|
||||
markerNamesDict.Clear();
|
||||
}
|
||||
|
||||
void CalculateMarkerChildTimes()
|
||||
{
|
||||
var markerStack = new Stack<ProfileMarker>();
|
||||
|
||||
for (int frameOffset = 0; frameOffset <= frames.Count; ++frameOffset)
|
||||
{
|
||||
var frameData = GetFrame(frameOffset);
|
||||
if (frameData == null)
|
||||
continue;
|
||||
|
||||
for (int threadIndex = 0; threadIndex < frameData.threads.Count; threadIndex++)
|
||||
{
|
||||
var threadData = frameData.threads[threadIndex];
|
||||
|
||||
// The markers are in depth first order and the depth is known
|
||||
// So we can infer a parent child relationship
|
||||
// Zero them first
|
||||
foreach (ProfileMarker markerData in threadData.markers)
|
||||
{
|
||||
markerData.msChildren = 0.0f;
|
||||
}
|
||||
|
||||
// Update the child times
|
||||
markerStack.Clear();
|
||||
foreach (ProfileMarker markerData in threadData.markers)
|
||||
{
|
||||
int depth = markerData.depth;
|
||||
|
||||
// Update depth stack and record child times in the parent
|
||||
if (depth >= markerStack.Count)
|
||||
{
|
||||
// If at same level then remove the last item at this level
|
||||
if (depth == markerStack.Count)
|
||||
{
|
||||
PopMarkerAndRecordTimeInParent(markerStack);
|
||||
}
|
||||
|
||||
// Assume we can't move down depth without markers between levels.
|
||||
}
|
||||
else if (depth < markerStack.Count)
|
||||
{
|
||||
// We can move up depth several layers so need to pop off all those markers
|
||||
while (markerStack.Count >= depth)
|
||||
{
|
||||
PopMarkerAndRecordTimeInParent(markerStack);
|
||||
}
|
||||
}
|
||||
|
||||
PushMarker(markerStack, markerData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static float GetLoadingProgress()
|
||||
{
|
||||
return s_Progress;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class ProfileFrame
|
||||
{
|
||||
public List<ProfileThread> threads = new List<ProfileThread>();
|
||||
public double msStartTime;
|
||||
public float msFrame;
|
||||
|
||||
public ProfileFrame()
|
||||
{
|
||||
msStartTime = 0.0;
|
||||
msFrame = 0f;
|
||||
}
|
||||
|
||||
public bool IsSame(ProfileFrame otherFrame)
|
||||
{
|
||||
if (msStartTime != otherFrame.msStartTime)
|
||||
return false;
|
||||
if (msFrame != otherFrame.msFrame)
|
||||
return false;
|
||||
if (threads.Count != otherFrame.threads.Count)
|
||||
return false;
|
||||
|
||||
// Close enough.
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Add(ProfileThread thread)
|
||||
{
|
||||
threads.Add(thread);
|
||||
}
|
||||
|
||||
public void Write(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(msStartTime);
|
||||
writer.Write(msFrame);
|
||||
writer.Write(threads.Count);
|
||||
foreach (var thread in threads)
|
||||
{
|
||||
thread.Write(writer);
|
||||
}
|
||||
;
|
||||
}
|
||||
|
||||
public ProfileFrame(BinaryReader reader, int fileVersion)
|
||||
{
|
||||
if (fileVersion > 1)
|
||||
{
|
||||
if (fileVersion >= 6)
|
||||
{
|
||||
msStartTime = reader.ReadDouble();
|
||||
}
|
||||
else
|
||||
{
|
||||
double sStartTime = reader.ReadDouble();
|
||||
msStartTime = sStartTime * 1000.0;
|
||||
}
|
||||
}
|
||||
|
||||
msFrame = reader.ReadSingle();
|
||||
int threadCount = reader.ReadInt32();
|
||||
threads.Clear();
|
||||
for (int thread = 0; thread < threadCount; thread++)
|
||||
{
|
||||
threads.Add(new ProfileThread(reader, fileVersion));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class ProfileThread
|
||||
{
|
||||
[NonSerialized]
|
||||
public List<ProfileMarker> markers = new List<ProfileMarker>();
|
||||
public int threadIndex;
|
||||
public long streamPos;
|
||||
public int markerCount = 0;
|
||||
public int fileVersion;
|
||||
|
||||
public ProfileThread()
|
||||
{
|
||||
}
|
||||
|
||||
public void Write(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(threadIndex);
|
||||
writer.Write(markers.Count);
|
||||
foreach (var marker in markers)
|
||||
{
|
||||
marker.Write(writer);
|
||||
}
|
||||
;
|
||||
}
|
||||
|
||||
public ProfileThread(BinaryReader reader, int fileversion)
|
||||
{
|
||||
streamPos = reader.BaseStream.Position;
|
||||
fileVersion = fileversion;
|
||||
threadIndex = reader.ReadInt32();
|
||||
markerCount = reader.ReadInt32();
|
||||
markers.Clear();
|
||||
for (int marker = 0; marker < markerCount; marker++)
|
||||
{
|
||||
markers.Add(new ProfileMarker(reader, fileVersion));
|
||||
}
|
||||
}
|
||||
|
||||
public bool ReadMarkers(string path)
|
||||
{
|
||||
if (streamPos == 0)
|
||||
return false; // the stream positions havent been written yet.
|
||||
var stream = File.OpenRead(path);
|
||||
BinaryReader br = new BinaryReader(stream);
|
||||
|
||||
br.BaseStream.Position = streamPos;
|
||||
threadIndex = br.ReadInt32();
|
||||
markerCount = br.ReadInt32();
|
||||
|
||||
markers.Clear();
|
||||
for (int marker = 0; marker < markerCount; marker++)
|
||||
{
|
||||
markers.Add(new ProfileMarker(br, fileVersion));
|
||||
}
|
||||
|
||||
br.Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AddMarker(ProfileMarker markerData)
|
||||
{
|
||||
markers.Add(markerData);
|
||||
markerCount++;
|
||||
}
|
||||
|
||||
public void RebuildMarkers(string path)
|
||||
{
|
||||
if (!File.Exists(path)) return;
|
||||
FileStream stream = File.OpenRead(path);
|
||||
using (var reader = new BinaryReader(stream))
|
||||
{
|
||||
reader.BaseStream.Position = streamPos;
|
||||
threadIndex = reader.ReadInt32();
|
||||
markerCount = reader.ReadInt32();
|
||||
markers.Clear();
|
||||
for (int marker = 0; marker < markerCount; marker++)
|
||||
{
|
||||
markers.Add(new ProfileMarker(reader, fileVersion));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class ProfileMarker
|
||||
{
|
||||
public int nameIndex;
|
||||
public float msMarkerTotal;
|
||||
public int depth;
|
||||
[NonSerialized]
|
||||
public float msChildren; // Recalculated on load so not saved in file
|
||||
|
||||
public ProfileMarker()
|
||||
{
|
||||
}
|
||||
|
||||
public static ProfileMarker Create(float durationMS, int depth)
|
||||
{
|
||||
var item = new ProfileMarker
|
||||
{
|
||||
msMarkerTotal = durationMS,
|
||||
depth = depth,
|
||||
msChildren = 0.0f
|
||||
};
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public static ProfileMarker Create(ProfilerFrameDataIterator frameData)
|
||||
{
|
||||
return Create(frameData.durationMS, frameData.depth);
|
||||
}
|
||||
|
||||
public void Write(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(nameIndex);
|
||||
writer.Write(msMarkerTotal);
|
||||
writer.Write(depth);
|
||||
}
|
||||
|
||||
public ProfileMarker(BinaryReader reader, int fileVersion)
|
||||
{
|
||||
nameIndex = reader.ReadInt32();
|
||||
msMarkerTotal = reader.ReadSingle();
|
||||
depth = reader.ReadInt32();
|
||||
if (fileVersion == 3) // In this version we saved the msChildren value but we don't need to as we now recalculate on load
|
||||
msChildren = reader.ReadSingle();
|
||||
else
|
||||
msChildren = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5cea2082789f54c25b65fd1fef416863
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,161 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
[Serializable]
|
||||
internal class ProfileDataView
|
||||
{
|
||||
public string path;
|
||||
public ProfileData data;
|
||||
public ProfileAnalysis analysisFullNew;
|
||||
public ProfileAnalysis analysisFull;
|
||||
public ProfileAnalysis analysisNew;
|
||||
public ProfileAnalysis analysis;
|
||||
public List<int> selectedIndices = new List<int> { 0, 0 };
|
||||
[NonSerialized]
|
||||
public bool inSyncWithProfilerData;
|
||||
|
||||
public ProfileDataView()
|
||||
{
|
||||
}
|
||||
|
||||
public ProfileDataView(ProfileDataView dataView)
|
||||
{
|
||||
path = dataView.path;
|
||||
data = dataView.data;
|
||||
analysisFullNew = dataView.analysisFullNew;
|
||||
analysisFull = dataView.analysisFull;
|
||||
analysisNew = dataView.analysisNew;
|
||||
analysis = dataView.analysis;
|
||||
selectedIndices = new List<int>(dataView.selectedIndices);
|
||||
inSyncWithProfilerData = dataView.inSyncWithProfilerData;
|
||||
}
|
||||
public bool IsDataValid()
|
||||
{
|
||||
if (data == null)
|
||||
return false;
|
||||
|
||||
if (data.GetFrameCount() == 0)
|
||||
return false;
|
||||
|
||||
if (data.NeedsMarkerRebuild)
|
||||
{
|
||||
if (!ProfileData.Load(data.FilePath, out data))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool HasValidSelection()
|
||||
{
|
||||
if (selectedIndices.Count == 2 && selectedIndices[0] == 0 && selectedIndices[1] == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool HasSelection()
|
||||
{
|
||||
if (selectedIndices.Count == 0)
|
||||
return false;
|
||||
if (selectedIndices.Count == data.GetFrameCount())
|
||||
return false;
|
||||
|
||||
return HasValidSelection();
|
||||
}
|
||||
|
||||
public bool AllSelected()
|
||||
{
|
||||
if (selectedIndices.Count != data.GetFrameCount())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int GetMaxDepth()
|
||||
{
|
||||
return (analysis == null) ? 1 : analysis.GetFrameSummary().maxMarkerDepth;
|
||||
}
|
||||
|
||||
int Clamp(int value, int min, int max)
|
||||
{
|
||||
if (value < min)
|
||||
value = min;
|
||||
else if (value > max)
|
||||
value = max;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public int ClampToValidDepthValue(int depthFilter)
|
||||
{
|
||||
// ProfileAnalyzer.kDepthAll is special case that we don't test for here
|
||||
|
||||
// If we have no depth values then return -1 for all (as clamp expects min<max)
|
||||
int maxDepth = GetMaxDepth();
|
||||
if (maxDepth < 1)
|
||||
return ProfileAnalyzer.kDepthAll;
|
||||
|
||||
return Clamp(depthFilter, 1, maxDepth);
|
||||
}
|
||||
|
||||
bool SelectAllFramesContainingMarker(string markerName, ProfileAnalysis inAnalysis)
|
||||
{
|
||||
if (inAnalysis == null)
|
||||
return false;
|
||||
|
||||
selectedIndices.Clear();
|
||||
|
||||
MarkerData markerData = inAnalysis.GetMarkerByName(markerName);
|
||||
if (markerData == null)
|
||||
return true;
|
||||
|
||||
foreach (var frameTime in markerData.frames)
|
||||
{
|
||||
selectedIndices.Add(frameTime.frameIndex);
|
||||
}
|
||||
|
||||
// Order from lowest to highest so the start/end frame display makes sense
|
||||
selectedIndices.Sort();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SelectAllFramesContainingMarker(string markerName, bool inSelection)
|
||||
{
|
||||
return SelectAllFramesContainingMarker(markerName, inSelection ? analysis : analysisFull);
|
||||
}
|
||||
|
||||
int ClampToRange(int value, int min, int max)
|
||||
{
|
||||
if (value < min)
|
||||
value = min;
|
||||
if (value > max)
|
||||
value = max;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public void ClearSelection()
|
||||
{
|
||||
selectedIndices.Clear();
|
||||
}
|
||||
|
||||
public void SelectFullRange()
|
||||
{
|
||||
selectedIndices.Clear();
|
||||
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
for (int offset = 0; offset < data.GetFrameCount(); offset++)
|
||||
{
|
||||
selectedIndices.Add(data.OffsetToDisplayFrame(offset));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 59e8a7346ec034a4387b6ca1ab20b83e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,868 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
class ProfileTreeViewItem : TreeViewItem
|
||||
{
|
||||
public MarkerData data { get; set; }
|
||||
public GUIContent[] cachedRowString;
|
||||
|
||||
public ProfileTreeViewItem(int id, int depth, string displayName, MarkerData data) : base(id, depth, displayName)
|
||||
{
|
||||
this.data = data;
|
||||
cachedRowString = null;
|
||||
}
|
||||
}
|
||||
|
||||
class ProfileTable : TreeView
|
||||
{
|
||||
Draw2D m_2D;
|
||||
ProfileDataView m_DataView;
|
||||
ProfileAnalyzerWindow m_ProfileAnalyzerWindow;
|
||||
Color m_BarColor;
|
||||
float m_MaxMedian;
|
||||
int m_MaxCount;
|
||||
float m_MaxCountMean;
|
||||
double m_MaxTotal;
|
||||
|
||||
const float kRowHeights = 20f;
|
||||
readonly List<TreeViewItem> m_Rows = new List<TreeViewItem>(100);
|
||||
|
||||
// All columns
|
||||
public enum MyColumns
|
||||
{
|
||||
Name,
|
||||
Depth,
|
||||
Median,
|
||||
MedianBar,
|
||||
Mean,
|
||||
Min,
|
||||
Max,
|
||||
Range,
|
||||
Count,
|
||||
CountBar,
|
||||
CountMean,
|
||||
CountMeanBar,
|
||||
FirstFrame,
|
||||
AtMedian,
|
||||
Total,
|
||||
TotalBar,
|
||||
Threads,
|
||||
}
|
||||
|
||||
static int m_MaxColumns;
|
||||
|
||||
public enum SortOption
|
||||
{
|
||||
Name,
|
||||
Depth,
|
||||
Median,
|
||||
Mean,
|
||||
Min,
|
||||
Max,
|
||||
Range,
|
||||
Count,
|
||||
CountMean,
|
||||
FirstFrame,
|
||||
AtMedian,
|
||||
Total,
|
||||
Threads,
|
||||
}
|
||||
|
||||
// Sort options per column
|
||||
SortOption[] m_SortOptions =
|
||||
{
|
||||
SortOption.Name,
|
||||
SortOption.Depth,
|
||||
SortOption.Median,
|
||||
SortOption.Median,
|
||||
SortOption.Mean,
|
||||
SortOption.Min,
|
||||
SortOption.Max,
|
||||
SortOption.Range,
|
||||
SortOption.Count,
|
||||
SortOption.Count,
|
||||
SortOption.CountMean,
|
||||
SortOption.CountMean,
|
||||
SortOption.FirstFrame,
|
||||
SortOption.AtMedian,
|
||||
SortOption.Total,
|
||||
SortOption.Total,
|
||||
SortOption.Threads,
|
||||
};
|
||||
|
||||
internal static class Styles
|
||||
{
|
||||
public static readonly GUIContent menuItemSelectFramesInAll = new GUIContent("Select Frames that contain this marker (within whole data set)", "");
|
||||
public static readonly GUIContent menuItemSelectFramesInCurrent = new GUIContent("Select Frames that contain this marker (within current selection)", "");
|
||||
//public static readonly GUIContent menuItemClearSelection = new GUIContent("Clear Selection");
|
||||
public static readonly GUIContent menuItemSelectFramesAll = new GUIContent("Select All");
|
||||
public static readonly GUIContent menuItemAddToIncludeFilter = new GUIContent("Add to Include Filter", "");
|
||||
public static readonly GUIContent menuItemAddToExcludeFilter = new GUIContent("Add to Exclude Filter", "");
|
||||
public static readonly GUIContent menuItemRemoveFromIncludeFilter = new GUIContent("Remove from Include Filter", "");
|
||||
public static readonly GUIContent menuItemRemoveFromExcludeFilter = new GUIContent("Remove from Exclude Filter", "");
|
||||
public static readonly GUIContent menuItemSetAsParentMarkerFilter = new GUIContent("Set as Parent Marker Filter", "");
|
||||
public static readonly GUIContent menuItemClearParentMarkerFilter = new GUIContent("Clear Parent Marker Filter", "");
|
||||
public static readonly GUIContent menuItemCopyToClipboard = new GUIContent("Copy to Clipboard", "");
|
||||
}
|
||||
|
||||
public ProfileTable(TreeViewState state, MultiColumnHeader multicolumnHeader, ProfileDataView dataView, ProfileAnalyzerWindow profileAnalyzerWindow, Draw2D draw2D, Color barColor) : base(state, multicolumnHeader)
|
||||
{
|
||||
m_2D = draw2D;
|
||||
m_DataView = dataView;
|
||||
m_ProfileAnalyzerWindow = profileAnalyzerWindow;
|
||||
m_BarColor = barColor;
|
||||
|
||||
m_MaxColumns = Enum.GetValues(typeof(MyColumns)).Length;
|
||||
Assert.AreEqual(m_SortOptions.Length, m_MaxColumns, "Ensure number of sort options are in sync with number of MyColumns enum values");
|
||||
|
||||
// Custom setup
|
||||
rowHeight = kRowHeights;
|
||||
showAlternatingRowBackgrounds = true;
|
||||
showBorder = true;
|
||||
customFoldoutYOffset = (kRowHeights - EditorGUIUtility.singleLineHeight) * 0.5f; // center foldout in the row since we also center content. See RowGUI
|
||||
// extraSpaceBeforeIconAndLabel = 0;
|
||||
multicolumnHeader.sortingChanged += OnSortingChanged;
|
||||
multicolumnHeader.visibleColumnsChanged += OnVisibleColumnsChanged;
|
||||
|
||||
Reload();
|
||||
}
|
||||
|
||||
protected override TreeViewItem BuildRoot()
|
||||
{
|
||||
int idForhiddenRoot = -1;
|
||||
int depthForHiddenRoot = -1;
|
||||
ProfileTreeViewItem root = new ProfileTreeViewItem(idForhiddenRoot, depthForHiddenRoot, "root", null);
|
||||
|
||||
List<string> nameFilters = m_ProfileAnalyzerWindow.GetNameFilters();
|
||||
List<string> nameExcludes = m_ProfileAnalyzerWindow.GetNameExcludes();
|
||||
|
||||
m_MaxMedian = 0.0f;
|
||||
m_MaxTotal = 0.0;
|
||||
m_MaxCount = 0;
|
||||
m_MaxCountMean = 0.0f;
|
||||
var markers = m_DataView.analysis.GetMarkers();
|
||||
for (int index = 0; index < markers.Count; ++index)
|
||||
{
|
||||
var marker = markers[index];
|
||||
if (nameFilters.Count > 0)
|
||||
{
|
||||
if (!m_ProfileAnalyzerWindow.NameInFilterList(marker.name, nameFilters))
|
||||
continue;
|
||||
}
|
||||
if (nameExcludes.Count > 0)
|
||||
{
|
||||
if (m_ProfileAnalyzerWindow.NameInExcludeList(marker.name, nameExcludes))
|
||||
continue;
|
||||
}
|
||||
|
||||
var item = new ProfileTreeViewItem(index, 0, marker.name, marker);
|
||||
root.AddChild(item);
|
||||
float ms = item.data.msMedian;
|
||||
if (ms > m_MaxMedian)
|
||||
m_MaxMedian = ms;
|
||||
|
||||
double msTotal = item.data.msTotal;
|
||||
if (msTotal > m_MaxTotal)
|
||||
m_MaxTotal = msTotal;
|
||||
|
||||
int count = item.data.count;
|
||||
if (count > m_MaxCount)
|
||||
m_MaxCount = count;
|
||||
|
||||
float countMean = item.data.countMean;
|
||||
if (countMean > m_MaxCountMean)
|
||||
m_MaxCountMean = countMean;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
|
||||
{
|
||||
m_Rows.Clear();
|
||||
|
||||
if (rootItem != null && rootItem.children != null)
|
||||
{
|
||||
foreach (ProfileTreeViewItem node in rootItem.children)
|
||||
{
|
||||
m_Rows.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
SortIfNeeded(m_Rows);
|
||||
|
||||
return m_Rows;
|
||||
}
|
||||
|
||||
void OnSortingChanged(MultiColumnHeader _multiColumnHeader)
|
||||
{
|
||||
SortIfNeeded(GetRows());
|
||||
}
|
||||
|
||||
protected virtual void OnVisibleColumnsChanged(MultiColumnHeader multiColumnHeader)
|
||||
{
|
||||
m_ProfileAnalyzerWindow.SetSingleModeColumns(multiColumnHeader.state.visibleColumns);
|
||||
multiColumnHeader.ResizeToFit();
|
||||
}
|
||||
|
||||
void SortIfNeeded(IList<TreeViewItem> rows)
|
||||
{
|
||||
if (rows.Count <= 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (multiColumnHeader.sortedColumnIndex == -1)
|
||||
{
|
||||
return; // No column to sort for (just use the order the data are in)
|
||||
}
|
||||
|
||||
// Sort the roots of the existing tree items
|
||||
SortByMultipleColumns();
|
||||
|
||||
// Update the data with the sorted content
|
||||
rows.Clear();
|
||||
foreach (ProfileTreeViewItem node in rootItem.children)
|
||||
{
|
||||
rows.Add(node);
|
||||
}
|
||||
|
||||
Repaint();
|
||||
}
|
||||
|
||||
string GetThreadName(ProfileTreeViewItem item)
|
||||
{
|
||||
return m_ProfileAnalyzerWindow.GetUIThreadName(item.data.threads[0]);
|
||||
}
|
||||
|
||||
string GetThreadNames(ProfileTreeViewItem item)
|
||||
{
|
||||
var uiNames = new List<string>();
|
||||
foreach (string threadNameWithIndex in item.data.threads)
|
||||
{
|
||||
string uiName = m_ProfileAnalyzerWindow.GetUIThreadName(threadNameWithIndex);
|
||||
|
||||
uiNames.Add(uiName);
|
||||
}
|
||||
uiNames.Sort(m_ProfileAnalyzerWindow.CompareUINames);
|
||||
|
||||
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
||||
bool first = true;
|
||||
foreach (var uiName in uiNames)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
sb.Append(", ");
|
||||
sb.Append(uiName);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
void SortByMultipleColumns()
|
||||
{
|
||||
var sortedColumns = multiColumnHeader.state.sortedColumns;
|
||||
|
||||
if (sortedColumns.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var myTypes = rootItem.children.Cast<ProfileTreeViewItem>();
|
||||
var orderedQuery = InitialOrder(myTypes, sortedColumns);
|
||||
for (int i = 1; i < sortedColumns.Length; i++)
|
||||
{
|
||||
SortOption sortOption = m_SortOptions[sortedColumns[i]];
|
||||
bool ascending = multiColumnHeader.IsSortedAscending(sortedColumns[i]);
|
||||
|
||||
switch (sortOption)
|
||||
{
|
||||
case SortOption.Name:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.name, ascending);
|
||||
break;
|
||||
case SortOption.Depth:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.minDepth, ascending);
|
||||
break;
|
||||
case SortOption.Mean:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.msMean, ascending);
|
||||
break;
|
||||
case SortOption.Median:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.msMedian, ascending);
|
||||
break;
|
||||
case SortOption.Min:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.msMin, ascending);
|
||||
break;
|
||||
case SortOption.Max:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.msMax, ascending);
|
||||
break;
|
||||
case SortOption.Range:
|
||||
orderedQuery = orderedQuery.ThenBy(l => (l.data.msMax - l.data.msMin), ascending);
|
||||
break;
|
||||
case SortOption.Count:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.count, ascending);
|
||||
break;
|
||||
case SortOption.CountMean:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.countMean, ascending);
|
||||
break;
|
||||
case SortOption.FirstFrame:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.firstFrameIndex, ascending);
|
||||
break;
|
||||
case SortOption.AtMedian:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.msAtMedian, ascending);
|
||||
break;
|
||||
case SortOption.Total:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.msTotal, ascending);
|
||||
break;
|
||||
case SortOption.Threads:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.cachedRowString != null ? l.cachedRowString[(int)MyColumns.Threads].text : GetThreadNames(l), ascending);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rootItem.children = orderedQuery.Cast<TreeViewItem>().ToList();
|
||||
}
|
||||
|
||||
IOrderedEnumerable<ProfileTreeViewItem> InitialOrder(IEnumerable<ProfileTreeViewItem> myTypes, int[] history)
|
||||
{
|
||||
SortOption sortOption = m_SortOptions[history[0]];
|
||||
bool ascending = multiColumnHeader.IsSortedAscending(history[0]);
|
||||
switch (sortOption)
|
||||
{
|
||||
case SortOption.Name:
|
||||
return myTypes.Order(l => l.data.name, ascending);
|
||||
case SortOption.Depth:
|
||||
return myTypes.Order(l => l.data.minDepth, ascending);
|
||||
case SortOption.Mean:
|
||||
return myTypes.Order(l => l.data.msMean, ascending);
|
||||
case SortOption.Median:
|
||||
return myTypes.Order(l => l.data.msMedian, ascending);
|
||||
case SortOption.Min:
|
||||
return myTypes.Order(l => l.data.msMin, ascending);
|
||||
case SortOption.Max:
|
||||
return myTypes.Order(l => l.data.msMax, ascending);
|
||||
case SortOption.Range:
|
||||
return myTypes.Order(l => (l.data.msMax - l.data.msMin), ascending);
|
||||
case SortOption.Count:
|
||||
return myTypes.Order(l => l.data.count, ascending);
|
||||
case SortOption.CountMean:
|
||||
return myTypes.Order(l => l.data.countMean, ascending);
|
||||
case SortOption.FirstFrame:
|
||||
return myTypes.Order(l => l.data.firstFrameIndex, ascending);
|
||||
case SortOption.AtMedian:
|
||||
return myTypes.Order(l => l.data.msAtMedian, ascending);
|
||||
case SortOption.Total:
|
||||
return myTypes.Order(l => l.data.msTotal, ascending);
|
||||
case SortOption.Threads:
|
||||
return myTypes.Order(l => l.cachedRowString != null ? l.cachedRowString[(int)MyColumns.Threads].text : GetThreadNames(l), ascending);
|
||||
default:
|
||||
Assert.IsTrue(false, "Unhandled enum");
|
||||
break;
|
||||
}
|
||||
|
||||
// default
|
||||
return myTypes.Order(l => l.data.name, ascending);
|
||||
}
|
||||
|
||||
public bool ShowingHorizontalScroll
|
||||
{
|
||||
get
|
||||
{
|
||||
return showingHorizontalScrollBar;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RowGUI(RowGUIArgs args)
|
||||
{
|
||||
var item = (ProfileTreeViewItem)args.item;
|
||||
|
||||
var clipRect = m_2D.GetClipRect();
|
||||
clipRect.y = state.scrollPos.y;
|
||||
clipRect.x = state.scrollPos.x;
|
||||
m_2D.SetClipRect(clipRect);
|
||||
|
||||
if (item.cachedRowString == null)
|
||||
{
|
||||
GenerateStrings(item);
|
||||
}
|
||||
|
||||
for (int i = 0; i < args.GetNumVisibleColumns(); ++i)
|
||||
{
|
||||
CellGUI(args.GetCellRect(i), item, (MyColumns)args.GetColumn(i), ref args);
|
||||
}
|
||||
m_2D.ClearClipRect();
|
||||
}
|
||||
|
||||
string ToDisplayUnits(float ms, bool showUnits = false, bool showFullValueWhenBelowZero = false)
|
||||
{
|
||||
return m_ProfileAnalyzerWindow.ToDisplayUnits(ms, showUnits, 0, showFullValueWhenBelowZero);
|
||||
}
|
||||
|
||||
string ToDisplayUnits(double ms, bool showUnits = false, bool showFullValueWhenBelowZero = false)
|
||||
{
|
||||
return m_ProfileAnalyzerWindow.ToDisplayUnits(ms, showUnits, 0, showFullValueWhenBelowZero);
|
||||
}
|
||||
|
||||
string ToTooltipDisplayUnits(float ms, bool showUnits = false, int onFrame = -1)
|
||||
{
|
||||
return m_ProfileAnalyzerWindow.ToTooltipDisplayUnits(ms, showUnits, onFrame);
|
||||
}
|
||||
|
||||
string ToTooltipDisplayUnits(double ms, bool showUnits = false, int onFrame = -1)
|
||||
{
|
||||
return ToTooltipDisplayUnits((float)ms, showUnits, onFrame);
|
||||
}
|
||||
|
||||
GUIContent ToDisplayUnitsWithTooltips(float ms, bool showUnits = false, int onFrame = -1)
|
||||
{
|
||||
return m_ProfileAnalyzerWindow.ToDisplayUnitsWithTooltips(ms, showUnits, onFrame);
|
||||
}
|
||||
|
||||
GUIContent ToDisplayUnitsWithTooltips(double ms, bool showUnits = false, int onFrame = -1)
|
||||
{
|
||||
return ToDisplayUnitsWithTooltips((float)ms, showUnits, onFrame);
|
||||
}
|
||||
|
||||
void CopyToClipboard(Event current, string text)
|
||||
{
|
||||
EditorGUIUtility.systemCopyBuffer = text;
|
||||
}
|
||||
|
||||
GenericMenu GenerateActiveContextMenu(string markerName, Event evt, GUIContent content)
|
||||
{
|
||||
GenericMenu menu = new GenericMenu();
|
||||
|
||||
menu.AddItem(Styles.menuItemSelectFramesInAll, false, () => m_ProfileAnalyzerWindow.SelectFramesContainingMarker(markerName, false));
|
||||
menu.AddItem(Styles.menuItemSelectFramesInCurrent, false, () => m_ProfileAnalyzerWindow.SelectFramesContainingMarker(markerName, true));
|
||||
|
||||
if (m_ProfileAnalyzerWindow.AllSelected())
|
||||
menu.AddDisabledItem(Styles.menuItemSelectFramesAll);
|
||||
else
|
||||
menu.AddItem(Styles.menuItemSelectFramesAll, false, () => m_ProfileAnalyzerWindow.SelectAllFrames());
|
||||
|
||||
menu.AddSeparator("");
|
||||
if (!m_ProfileAnalyzerWindow.GetNameFilters().Contains(markerName, StringComparer.OrdinalIgnoreCase))
|
||||
menu.AddItem(Styles.menuItemAddToIncludeFilter, false, () => m_ProfileAnalyzerWindow.AddToIncludeFilter(markerName));
|
||||
else
|
||||
menu.AddItem(Styles.menuItemRemoveFromIncludeFilter, false, () => m_ProfileAnalyzerWindow.RemoveFromIncludeFilter(markerName));
|
||||
if (!m_ProfileAnalyzerWindow.GetNameExcludes().Contains(markerName, StringComparer.OrdinalIgnoreCase))
|
||||
menu.AddItem(Styles.menuItemAddToExcludeFilter, false, () => m_ProfileAnalyzerWindow.AddToExcludeFilter(markerName));
|
||||
else
|
||||
menu.AddItem(Styles.menuItemRemoveFromExcludeFilter, false, () => m_ProfileAnalyzerWindow.RemoveFromExcludeFilter(markerName));
|
||||
menu.AddSeparator("");
|
||||
menu.AddItem(Styles.menuItemSetAsParentMarkerFilter, false, () => m_ProfileAnalyzerWindow.SetAsParentMarkerFilter(markerName));
|
||||
menu.AddItem(Styles.menuItemClearParentMarkerFilter, false, () => m_ProfileAnalyzerWindow.SetAsParentMarkerFilter(""));
|
||||
menu.AddSeparator("");
|
||||
if (content != null && !string.IsNullOrEmpty(content.text))
|
||||
menu.AddItem(Styles.menuItemCopyToClipboard, false, () => CopyToClipboard(evt, content.text));
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
GenericMenu GenerateDisabledContextMenu(string markerName, GUIContent content)
|
||||
{
|
||||
GenericMenu menu = new GenericMenu();
|
||||
|
||||
menu.AddDisabledItem(Styles.menuItemSelectFramesInAll);
|
||||
menu.AddDisabledItem(Styles.menuItemSelectFramesInCurrent);
|
||||
menu.AddDisabledItem(Styles.menuItemSelectFramesAll);
|
||||
|
||||
menu.AddSeparator("");
|
||||
if (!m_ProfileAnalyzerWindow.GetNameFilters().Contains(markerName, StringComparer.OrdinalIgnoreCase))
|
||||
menu.AddDisabledItem(Styles.menuItemAddToIncludeFilter);
|
||||
else
|
||||
menu.AddDisabledItem(Styles.menuItemRemoveFromIncludeFilter);
|
||||
if (!m_ProfileAnalyzerWindow.GetNameExcludes().Contains(markerName, StringComparer.OrdinalIgnoreCase))
|
||||
menu.AddDisabledItem(Styles.menuItemAddToExcludeFilter);
|
||||
else
|
||||
menu.AddDisabledItem(Styles.menuItemRemoveFromExcludeFilter);
|
||||
menu.AddSeparator("");
|
||||
menu.AddDisabledItem(Styles.menuItemSetAsParentMarkerFilter);
|
||||
menu.AddDisabledItem(Styles.menuItemClearParentMarkerFilter);
|
||||
menu.AddSeparator("");
|
||||
if (content != null && !string.IsNullOrEmpty(content.text))
|
||||
menu.AddDisabledItem(Styles.menuItemCopyToClipboard);
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
|
||||
void ShowContextMenu(Rect cellRect, string markerName, GUIContent content)
|
||||
{
|
||||
Event current = Event.current;
|
||||
if (cellRect.Contains(current.mousePosition) && current.type == EventType.ContextClick)
|
||||
{
|
||||
GenericMenu menu;
|
||||
if (!m_ProfileAnalyzerWindow.IsAnalysisRunning())
|
||||
menu = GenerateActiveContextMenu(markerName, current, content);
|
||||
else
|
||||
menu = GenerateDisabledContextMenu(markerName, content);
|
||||
|
||||
menu.ShowAsContext();
|
||||
|
||||
current.Use();
|
||||
}
|
||||
}
|
||||
|
||||
void ShowText(Rect rect, string text)
|
||||
{
|
||||
EditorGUI.LabelField(rect, text);
|
||||
//EditorGUI.TextArea(rect, text);
|
||||
}
|
||||
|
||||
void ShowText(Rect rect, GUIContent content)
|
||||
{
|
||||
EditorGUI.LabelField(rect, content);
|
||||
//ShowText(rect, content.text);
|
||||
}
|
||||
|
||||
void GenerateStrings(ProfileTreeViewItem item)
|
||||
{
|
||||
item.cachedRowString = new GUIContent[m_MaxColumns];
|
||||
|
||||
int medianFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(item.data.medianFrameIndex, m_DataView);
|
||||
int minFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(item.data.minFrameIndex, m_DataView);
|
||||
int maxFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(item.data.maxFrameIndex, m_DataView);
|
||||
int firstFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(item.data.firstFrameIndex, m_DataView);
|
||||
int frameSummaryMedianFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(m_DataView.analysis.GetFrameSummary().medianFrameIndex, m_DataView);
|
||||
|
||||
item.cachedRowString[(int)MyColumns.Name] = new GUIContent(item.data.name, item.data.name);
|
||||
item.cachedRowString[(int)MyColumns.Mean] = ToDisplayUnitsWithTooltips(item.data.msMean, false);
|
||||
item.cachedRowString[(int)MyColumns.Depth] = (item.data.minDepth == item.data.maxDepth) ? new GUIContent(string.Format("{0}", item.data.minDepth), "") : new GUIContent(string.Format("{0}-{1}", item.data.minDepth, item.data.maxDepth), "");
|
||||
item.cachedRowString[(int)MyColumns.Median] = ToDisplayUnitsWithTooltips(item.data.msMedian, false, medianFrameIndex);
|
||||
string tooltip = ToTooltipDisplayUnits(item.data.msMedian, true, medianFrameIndex);
|
||||
item.cachedRowString[(int)MyColumns.MedianBar] = new GUIContent("", tooltip);
|
||||
item.cachedRowString[(int)MyColumns.Min] = ToDisplayUnitsWithTooltips(item.data.msMin, false, minFrameIndex);
|
||||
item.cachedRowString[(int)MyColumns.Max] = ToDisplayUnitsWithTooltips(item.data.msMax, false, maxFrameIndex);
|
||||
item.cachedRowString[(int)MyColumns.Range] = ToDisplayUnitsWithTooltips(item.data.msMax - item.data.msMin);
|
||||
item.cachedRowString[(int)MyColumns.Count] = new GUIContent(string.Format("{0}", item.data.count), "");
|
||||
item.cachedRowString[(int)MyColumns.CountBar] = new GUIContent("", string.Format("{0}", item.data.count));
|
||||
item.cachedRowString[(int)MyColumns.CountMean] = new GUIContent(string.Format("{0:f0}", item.data.countMean), "");
|
||||
item.cachedRowString[(int)MyColumns.CountMeanBar] = new GUIContent("", string.Format("{0:f0}", item.data.countMean));
|
||||
item.cachedRowString[(int)MyColumns.FirstFrame] = new GUIContent(firstFrameIndex.ToString());
|
||||
item.cachedRowString[(int)MyColumns.AtMedian] = ToDisplayUnitsWithTooltips(item.data.msAtMedian, false, frameSummaryMedianFrameIndex);
|
||||
item.cachedRowString[(int)MyColumns.Total] = ToDisplayUnitsWithTooltips(item.data.msTotal);
|
||||
tooltip = ToTooltipDisplayUnits(item.data.msTotal, true, medianFrameIndex);
|
||||
item.cachedRowString[(int)MyColumns.TotalBar] = new GUIContent("", tooltip);
|
||||
|
||||
string threadNames = GetThreadNames(item);
|
||||
item.cachedRowString[(int)MyColumns.Threads] = new GUIContent(threadNames, threadNames);
|
||||
}
|
||||
|
||||
void ShowBar(Rect rect, float ms, float range, GUIContent content)
|
||||
{
|
||||
if (ms > 0.0f)
|
||||
{
|
||||
if (m_2D.DrawStart(rect))
|
||||
{
|
||||
float w = Math.Max(1.0f, rect.width * ms / range);
|
||||
m_2D.DrawFilledBox(0, 1, w, rect.height - 1, m_BarColor);
|
||||
m_2D.DrawEnd();
|
||||
}
|
||||
}
|
||||
GUI.Label(rect, content);
|
||||
}
|
||||
|
||||
void CellGUI(Rect cellRect, ProfileTreeViewItem item, MyColumns column, ref RowGUIArgs args)
|
||||
{
|
||||
// Center cell rect vertically (makes it easier to place controls, icons etc in the cells)
|
||||
CenterRectUsingSingleLineHeight(ref cellRect);
|
||||
|
||||
GUIContent content = item.cachedRowString[(int)column];
|
||||
switch (column)
|
||||
{
|
||||
case MyColumns.Name:
|
||||
{
|
||||
args.rowRect = cellRect;
|
||||
//base.RowGUI(args);
|
||||
//content = new GUIContent(item.data.name, item.data.name);
|
||||
ShowText(cellRect, content);
|
||||
}
|
||||
break;
|
||||
|
||||
case MyColumns.Mean:
|
||||
case MyColumns.Depth:
|
||||
case MyColumns.Median:
|
||||
case MyColumns.Min:
|
||||
case MyColumns.Max:
|
||||
case MyColumns.Range:
|
||||
case MyColumns.Count:
|
||||
case MyColumns.CountMean:
|
||||
case MyColumns.AtMedian:
|
||||
case MyColumns.Total:
|
||||
case MyColumns.Threads:
|
||||
ShowText(cellRect, content);
|
||||
break;
|
||||
case MyColumns.MedianBar:
|
||||
ShowBar(cellRect, item.data.msMedian, m_MaxMedian, content);
|
||||
break;
|
||||
case MyColumns.TotalBar:
|
||||
ShowBar(cellRect, (float)item.data.msTotal, (float)m_MaxTotal, content);
|
||||
break;
|
||||
case MyColumns.CountBar:
|
||||
ShowBar(cellRect, item.data.count, m_MaxCount, content);
|
||||
break;
|
||||
case MyColumns.CountMeanBar:
|
||||
ShowBar(cellRect, item.data.countMean, m_MaxCountMean, content);
|
||||
break;
|
||||
case MyColumns.FirstFrame:
|
||||
if (!m_ProfileAnalyzerWindow.IsProfilerWindowOpen() || !m_DataView.inSyncWithProfilerData)
|
||||
GUI.enabled = false;
|
||||
if (GUI.Button(cellRect, content))
|
||||
{
|
||||
m_ProfileAnalyzerWindow.SelectMarkerByIndex(item.id);
|
||||
m_ProfileAnalyzerWindow.JumpToFrame(item.data.firstFrameIndex, m_DataView.data);
|
||||
}
|
||||
|
||||
GUI.enabled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
ShowContextMenu(cellRect, item.data.name, content);
|
||||
}
|
||||
|
||||
// Misc
|
||||
//--------
|
||||
|
||||
protected override bool CanMultiSelect(TreeViewItem item)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
struct HeaderData
|
||||
{
|
||||
public readonly GUIContent content;
|
||||
public readonly float width;
|
||||
public readonly float minWidth;
|
||||
public readonly bool autoResize;
|
||||
public readonly bool allowToggleVisibility;
|
||||
public readonly bool ascending;
|
||||
|
||||
public HeaderData(string name, string tooltip = "", float width = 50, float minWidth = 30, bool autoResize = true, bool allowToggleVisibility = true, bool ascending = false)
|
||||
{
|
||||
content = new GUIContent(name, tooltip);
|
||||
this.width = width;
|
||||
this.minWidth = minWidth;
|
||||
this.autoResize = autoResize;
|
||||
this.allowToggleVisibility = allowToggleVisibility;
|
||||
this.ascending = ascending;
|
||||
}
|
||||
}
|
||||
|
||||
public static MultiColumnHeaderState CreateDefaultMultiColumnHeaderState(MarkerColumnFilter modeFilter)
|
||||
{
|
||||
var columnList = new List<MultiColumnHeaderState.Column>();
|
||||
HeaderData[] headerData = new HeaderData[]
|
||||
{
|
||||
new HeaderData("Marker Name", "Marker Name\n\nFrame marker time is total of all instances in frame", width: 300, minWidth: 100, autoResize: false, allowToggleVisibility: false, ascending: true),
|
||||
new HeaderData("Depth", "Marker depth in marker hierarchy\n\nMay appear at multiple levels"),
|
||||
new HeaderData("Median", "Central marker time over all selected frames\n\nAlways present in data set\n1st of 2 central values for even frame count"),
|
||||
new HeaderData("Median Bar", "Central marker time over all selected frames", width: 50),
|
||||
new HeaderData("Mean", "Per frame marker time / number of non zero frames"),
|
||||
new HeaderData("Min", "Minimum marker time"),
|
||||
new HeaderData("Max", "Maximum marker time"),
|
||||
new HeaderData("Range", "Difference between maximum and minimum"),
|
||||
new HeaderData("Count", "Marker count over all selected frames\n\nMultiple can occur per frame"),
|
||||
new HeaderData("Count Bar", "Marker count over all selected frames\n\nMultiple can occur per frame"),
|
||||
new HeaderData("Count Frame", "Average number of markers per frame\n\ntotal count / number of non zero frames", width: 70, minWidth: 50),
|
||||
new HeaderData("Count Frame Bar", "Average number of markers per frame\n\ntotal count / number of non zero frames", width: 70, minWidth: 50),
|
||||
new HeaderData("1st", "First frame index that the marker appears on"),
|
||||
new HeaderData("At Median Frame", "Marker time on the median frame\n\nI.e. Marker total duration on the average frame", width: 90, minWidth: 50),
|
||||
new HeaderData("Total", "Marker total time over all selected frames"),
|
||||
new HeaderData("Total Bar", "Marker total time over all selected frames"),
|
||||
new HeaderData("Threads", "Threads the marker occurs on (with filtering applied)"),
|
||||
};
|
||||
foreach (var header in headerData)
|
||||
{
|
||||
columnList.Add(new MultiColumnHeaderState.Column
|
||||
{
|
||||
headerContent = header.content,
|
||||
headerTextAlignment = TextAlignment.Left,
|
||||
sortedAscending = header.ascending,
|
||||
sortingArrowAlignment = TextAlignment.Left,
|
||||
width = header.width,
|
||||
minWidth = header.minWidth,
|
||||
autoResize = header.autoResize,
|
||||
allowToggleVisibility = header.allowToggleVisibility
|
||||
});
|
||||
}
|
||||
;
|
||||
var columns = columnList.ToArray();
|
||||
|
||||
m_MaxColumns = Enum.GetValues(typeof(MyColumns)).Length;
|
||||
Assert.AreEqual(columns.Length, m_MaxColumns, "Number of columns should match number of enum values: You probably forgot to update one of them.");
|
||||
|
||||
var state = new MultiColumnHeaderState(columns);
|
||||
SetMode(modeFilter, state);
|
||||
return state;
|
||||
}
|
||||
|
||||
protected override void SelectionChanged(IList<int> selectedIds)
|
||||
{
|
||||
base.SelectionChanged(selectedIds);
|
||||
|
||||
if (selectedIds.Count > 0)
|
||||
{
|
||||
m_ProfileAnalyzerWindow.SelectMarkerByIndex(selectedIds[0]);
|
||||
// A newly selected marker changes the marker summary's GUI content, conflicting with the previous layout pass. We need to exit GUI and re-layout.
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
}
|
||||
|
||||
static int[] GetDefaultVisibleColumns(MarkerColumnFilter.Mode mode)
|
||||
{
|
||||
int[] visibleColumns;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
default:
|
||||
case MarkerColumnFilter.Mode.Custom:
|
||||
case MarkerColumnFilter.Mode.TimeAndCount:
|
||||
visibleColumns = new int[]
|
||||
{
|
||||
(int)MyColumns.Name,
|
||||
(int)MyColumns.Depth,
|
||||
(int)MyColumns.Median,
|
||||
(int)MyColumns.MedianBar,
|
||||
(int)MyColumns.Mean,
|
||||
(int)MyColumns.Min,
|
||||
(int)MyColumns.Max,
|
||||
(int)MyColumns.Range,
|
||||
(int)MyColumns.Count,
|
||||
(int)MyColumns.CountMean,
|
||||
(int)MyColumns.AtMedian,
|
||||
};
|
||||
break;
|
||||
case MarkerColumnFilter.Mode.Time:
|
||||
visibleColumns = new int[]
|
||||
{
|
||||
(int)MyColumns.Name,
|
||||
(int)MyColumns.Depth,
|
||||
(int)MyColumns.Median,
|
||||
(int)MyColumns.MedianBar,
|
||||
(int)MyColumns.Min,
|
||||
(int)MyColumns.Max,
|
||||
(int)MyColumns.Range,
|
||||
(int)MyColumns.AtMedian,
|
||||
};
|
||||
break;
|
||||
case MarkerColumnFilter.Mode.Totals:
|
||||
visibleColumns = new int[]
|
||||
{
|
||||
(int)MyColumns.Name,
|
||||
(int)MyColumns.Depth,
|
||||
(int)MyColumns.Total,
|
||||
(int)MyColumns.TotalBar,
|
||||
};
|
||||
break;
|
||||
case MarkerColumnFilter.Mode.TimeWithTotals:
|
||||
visibleColumns = new int[]
|
||||
{
|
||||
(int)MyColumns.Name,
|
||||
(int)MyColumns.Depth,
|
||||
(int)MyColumns.Median,
|
||||
(int)MyColumns.MedianBar,
|
||||
(int)MyColumns.Min,
|
||||
(int)MyColumns.Max,
|
||||
(int)MyColumns.Range,
|
||||
(int)MyColumns.AtMedian,
|
||||
(int)MyColumns.Total,
|
||||
(int)MyColumns.TotalBar,
|
||||
};
|
||||
break;
|
||||
case MarkerColumnFilter.Mode.CountTotals:
|
||||
visibleColumns = new int[]
|
||||
{
|
||||
(int)MyColumns.Name,
|
||||
(int)MyColumns.Depth,
|
||||
(int)MyColumns.Count,
|
||||
(int)MyColumns.CountBar,
|
||||
};
|
||||
break;
|
||||
case MarkerColumnFilter.Mode.CountPerFrame:
|
||||
visibleColumns = new int[]
|
||||
{
|
||||
(int)MyColumns.Name,
|
||||
(int)MyColumns.Depth,
|
||||
(int)MyColumns.CountMean,
|
||||
(int)MyColumns.CountMeanBar,
|
||||
};
|
||||
break;
|
||||
case MarkerColumnFilter.Mode.Depth:
|
||||
visibleColumns = new int[]
|
||||
{
|
||||
(int)MyColumns.Name,
|
||||
(int)MyColumns.Depth,
|
||||
};
|
||||
break;
|
||||
case MarkerColumnFilter.Mode.Threads:
|
||||
visibleColumns = new int[]
|
||||
{
|
||||
(int)MyColumns.Name,
|
||||
(int)MyColumns.Threads,
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
return visibleColumns;
|
||||
}
|
||||
|
||||
static void SetMode(MarkerColumnFilter modeFilter, MultiColumnHeaderState state)
|
||||
{
|
||||
switch (modeFilter.mode)
|
||||
{
|
||||
case MarkerColumnFilter.Mode.Custom:
|
||||
if (modeFilter.visibleColumns == null)
|
||||
state.visibleColumns = GetDefaultVisibleColumns(modeFilter.mode);
|
||||
else
|
||||
state.visibleColumns = modeFilter.visibleColumns;
|
||||
break;
|
||||
default:
|
||||
state.visibleColumns = GetDefaultVisibleColumns(modeFilter.mode);
|
||||
break;
|
||||
}
|
||||
|
||||
if (modeFilter.visibleColumns == null)
|
||||
modeFilter.visibleColumns = state.visibleColumns;
|
||||
}
|
||||
|
||||
public void SetMode(MarkerColumnFilter modeFilter)
|
||||
{
|
||||
SetMode(modeFilter, multiColumnHeader.state);
|
||||
multiColumnHeader.ResizeToFit();
|
||||
}
|
||||
}
|
||||
|
||||
static class MyExtensionMethods
|
||||
{
|
||||
public static IOrderedEnumerable<T> Order<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector, bool ascending)
|
||||
{
|
||||
if (ascending)
|
||||
{
|
||||
return source.OrderBy(selector);
|
||||
}
|
||||
else
|
||||
{
|
||||
return source.OrderByDescending(selector);
|
||||
}
|
||||
}
|
||||
|
||||
public static IOrderedEnumerable<T> ThenBy<T, TKey>(this IOrderedEnumerable<T> source, Func<T, TKey> selector, bool ascending)
|
||||
{
|
||||
if (ascending)
|
||||
{
|
||||
return source.ThenBy(selector);
|
||||
}
|
||||
else
|
||||
{
|
||||
return source.ThenByDescending(selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 981ca102f47f145c1977153854cce718
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,903 @@
|
|||
using UnityEditorInternal;
|
||||
using System.Reflection;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine.Profiling;
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
using UnityEditor.Profiling;
|
||||
#endif
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
using Unity.Profiling.Editor;
|
||||
// stub so that ProfilerWindow can be moved to this namespace in trunk without a need to change PA
|
||||
namespace Unity.Profiling.Editor {}
|
||||
#endif
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
internal class ProfilerWindowInterface
|
||||
{
|
||||
bool m_ProfilerWindowInitialized = false;
|
||||
const float k_NsToMs = 1000000;
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
bool s_UseRawIterator = true;
|
||||
#endif
|
||||
ProgressBarDisplay m_progressBar;
|
||||
|
||||
[NonSerialized] bool m_SendingSelectionEventToProfilerWindowInProgress = false;
|
||||
[NonSerialized] int m_LastSelectedFrameInProfilerWindow = 0;
|
||||
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
[NonSerialized] ProfilerWindow m_ProfilerWindow;
|
||||
[NonSerialized] IProfilerFrameTimeViewSampleSelectionController m_CpuProfilerModule;
|
||||
#else
|
||||
Type m_ProfilerWindowType;
|
||||
EditorWindow m_ProfilerWindow;
|
||||
FieldInfo m_CurrentFrameFieldInfo;
|
||||
FieldInfo m_TimeLineGUIFieldInfo;
|
||||
FieldInfo m_SelectedEntryFieldInfo;
|
||||
FieldInfo m_SelectedNameFieldInfo;
|
||||
FieldInfo m_SelectedTimeFieldInfo;
|
||||
FieldInfo m_SelectedDurationFieldInfo;
|
||||
FieldInfo m_SelectedInstanceIdFieldInfo;
|
||||
FieldInfo m_SelectedFrameIdFieldInfo;
|
||||
FieldInfo m_SelectedThreadIndexFieldInfo;
|
||||
FieldInfo m_SelectedNativeIndexFieldInfo;
|
||||
FieldInfo m_SelectedInstanceCountFieldInfo;
|
||||
FieldInfo m_SelectedInstanceCountForThreadFieldInfo;
|
||||
FieldInfo m_SelectedInstanceCountForFrameFieldInfo;
|
||||
FieldInfo m_SelectedMetaDataFieldInfo;
|
||||
FieldInfo m_SelectedThreadCountFieldInfo;
|
||||
FieldInfo m_SelectedCallstackInfoFieldInfo;
|
||||
|
||||
MethodInfo m_GetProfilerModuleInfo;
|
||||
Type m_CPUProfilerModuleType;
|
||||
#endif
|
||||
|
||||
public ProfilerWindowInterface(ProgressBarDisplay progressBar)
|
||||
{
|
||||
m_progressBar = progressBar;
|
||||
|
||||
#if !UNITY_2021_1_OR_NEWER
|
||||
Assembly assem = typeof(Editor).Assembly;
|
||||
m_ProfilerWindowType = assem.GetType("UnityEditor.ProfilerWindow");
|
||||
m_CurrentFrameFieldInfo = m_ProfilerWindowType.GetField("m_CurrentFrame", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
m_TimeLineGUIFieldInfo = m_ProfilerWindowType.GetField("m_CPUTimelineGUI", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (m_TimeLineGUIFieldInfo == null)
|
||||
{
|
||||
// m_CPUTimelineGUI isn't present in 2019.3.0a8 onward
|
||||
m_GetProfilerModuleInfo = m_ProfilerWindowType.GetMethod("GetProfilerModule", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (m_GetProfilerModuleInfo == null)
|
||||
{
|
||||
Debug.Log("Unable to initialise link to Profiler Timeline, no GetProfilerModule found");
|
||||
}
|
||||
|
||||
m_CPUProfilerModuleType = assem.GetType("UnityEditorInternal.Profiling.CPUProfilerModule");
|
||||
m_TimeLineGUIFieldInfo = m_CPUProfilerModuleType.GetField("m_TimelineGUI", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (m_TimeLineGUIFieldInfo == null)
|
||||
{
|
||||
Debug.Log("Unable to initialise link to Profiler Timeline");
|
||||
}
|
||||
}
|
||||
|
||||
if (m_TimeLineGUIFieldInfo != null)
|
||||
m_SelectedEntryFieldInfo = m_TimeLineGUIFieldInfo.FieldType.GetField("m_SelectedEntry", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (m_SelectedEntryFieldInfo != null)
|
||||
{
|
||||
m_SelectedNameFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("name", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedTimeFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("time", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedDurationFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("duration", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedInstanceIdFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("instanceId", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedFrameIdFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("frameId", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
// confusingly this is called threadId but is the thread _index_
|
||||
m_SelectedThreadIndexFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("threadId", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedNativeIndexFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("nativeIndex", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedInstanceCountFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("instanceCount", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedInstanceCountForThreadFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("instanceCountForThread", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedInstanceCountForFrameFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("instanceCountForFrame", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedThreadCountFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("threadCount", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedMetaDataFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("metaData", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedCallstackInfoFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("callstackInfo", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public bool IsReady()
|
||||
{
|
||||
return m_ProfilerWindow != null && m_ProfilerWindowInitialized;
|
||||
}
|
||||
|
||||
public void GetProfilerWindowHandle()
|
||||
{
|
||||
Profiler.BeginSample("GetProfilerWindowHandle");
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
if (m_CpuProfilerModule != null)
|
||||
{
|
||||
m_CpuProfilerModule.selectionChanged -= OnSelectionChangedInCpuProfilerModule;
|
||||
m_CpuProfilerModule = null;
|
||||
}
|
||||
|
||||
var windows = Resources.FindObjectsOfTypeAll<ProfilerWindow>();
|
||||
if (windows != null && windows.Length > 0)
|
||||
m_ProfilerWindow = windows[0];
|
||||
if (m_ProfilerWindow != null)
|
||||
{
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
var cpuModuleIdentifier = ProfilerWindow.cpuModuleIdentifier;
|
||||
#else
|
||||
var cpuModuleIdentifier = ProfilerWindow.cpuModuleName;
|
||||
#endif
|
||||
m_CpuProfilerModule =
|
||||
m_ProfilerWindow.GetFrameTimeViewSampleSelectionController(cpuModuleIdentifier);
|
||||
m_CpuProfilerModule.selectionChanged -= OnSelectionChangedInCpuProfilerModule;
|
||||
m_CpuProfilerModule.selectionChanged += OnSelectionChangedInCpuProfilerModule;
|
||||
|
||||
m_ProfilerWindow.Repaint();
|
||||
m_ProfilerWindowInitialized = false;
|
||||
// wait a frame for the Profiler to get Repainted
|
||||
EditorApplication.delayCall += () => m_ProfilerWindowInitialized = true;
|
||||
}
|
||||
#else
|
||||
UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(m_ProfilerWindowType);
|
||||
if (windows != null && windows.Length > 0)
|
||||
m_ProfilerWindow = (EditorWindow)windows[0];
|
||||
m_ProfilerWindowInitialized = true;
|
||||
#endif
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
public void OpenProfilerOrUseExisting()
|
||||
{
|
||||
// Note we use existing if possible to fix a bug after domain reload
|
||||
// Where calling EditorWindow.GetWindow directly causes a second window to open
|
||||
if (m_ProfilerWindow == null)
|
||||
{
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
m_ProfilerWindow = EditorWindow.GetWindow<ProfilerWindow>();
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
var cpuModuleIdentifier = ProfilerWindow.cpuModuleIdentifier;
|
||||
#else
|
||||
var cpuModuleIdentifier = ProfilerWindow.cpuModuleName;
|
||||
#endif
|
||||
m_CpuProfilerModule = m_ProfilerWindow.GetFrameTimeViewSampleSelectionController(cpuModuleIdentifier);
|
||||
m_CpuProfilerModule.selectionChanged -= OnSelectionChangedInCpuProfilerModule;
|
||||
m_CpuProfilerModule.selectionChanged += OnSelectionChangedInCpuProfilerModule;
|
||||
#else
|
||||
// Create new
|
||||
m_ProfilerWindow = EditorWindow.GetWindow(m_ProfilerWindowType);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public bool GetFrameRangeFromProfiler(out int first, out int last)
|
||||
{
|
||||
if (m_ProfilerWindow != null)
|
||||
{
|
||||
first = 1 + ProfilerDriver.firstFrameIndex;
|
||||
last = 1 + ProfilerDriver.lastFrameIndex;
|
||||
#if !UNITY_2018_4_OR_NEWER
|
||||
//Prior to 18.4 we need to clip to the visible frames in the profile which indents 1 in from end
|
||||
//as the last frame is not visible and sometimes still being processed
|
||||
if (first < last)
|
||||
last--;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
first = 1;
|
||||
last = 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void CloseProfiler()
|
||||
{
|
||||
if (m_ProfilerWindow != null)
|
||||
m_ProfilerWindow.Close();
|
||||
}
|
||||
|
||||
#if !UNITY_2021_1_OR_NEWER
|
||||
object GetTimeLineGUI()
|
||||
{
|
||||
object timeLineGUI = null;
|
||||
|
||||
if (m_CPUProfilerModuleType != null)
|
||||
{
|
||||
object[] parametersArray = new object[] { ProfilerArea.CPU };
|
||||
var getCPUProfilerModuleInfo = m_GetProfilerModuleInfo.MakeGenericMethod(m_CPUProfilerModuleType);
|
||||
var cpuModule = getCPUProfilerModuleInfo.Invoke(m_ProfilerWindow, parametersArray);
|
||||
|
||||
timeLineGUI = m_TimeLineGUIFieldInfo.GetValue(cpuModule);
|
||||
}
|
||||
else if (m_TimeLineGUIFieldInfo != null)
|
||||
{
|
||||
timeLineGUI = m_TimeLineGUIFieldInfo.GetValue(m_ProfilerWindow);
|
||||
}
|
||||
|
||||
return timeLineGUI;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
private void OnSelectionChangedInCpuProfilerModule(IProfilerFrameTimeViewSampleSelectionController controller, ProfilerTimeSampleSelection selection)
|
||||
{
|
||||
if (controller == m_CpuProfilerModule && !m_SendingSelectionEventToProfilerWindowInProgress)
|
||||
{
|
||||
if (selection != null && selection.markerNamePath != null && selection.markerNamePath.Count > 0)
|
||||
{
|
||||
selectedMarkerChanged(selection.markerNamePath[selection.markerNamePath.Count - 1], selection.threadGroupName, selection.threadName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public event Action<string, string, string> selectedMarkerChanged = delegate {};
|
||||
|
||||
public void PollProfilerWindowMarkerName()
|
||||
{
|
||||
#if !UNITY_2021_1_OR_NEWER
|
||||
if (m_ProfilerWindow != null)
|
||||
{
|
||||
var timeLineGUI = GetTimeLineGUI();
|
||||
if (timeLineGUI != null && m_SelectedEntryFieldInfo != null)
|
||||
{
|
||||
var selectedEntry = m_SelectedEntryFieldInfo.GetValue(timeLineGUI);
|
||||
if (selectedEntry != null && m_SelectedNameFieldInfo != null)
|
||||
{
|
||||
string threadGroupName = null;
|
||||
string threadName = null;
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
if (m_SelectedFrameIdFieldInfo != null && m_SelectedThreadIndexFieldInfo != null)
|
||||
{
|
||||
using (RawFrameDataView frameData = ProfilerDriver.GetRawFrameDataView((int)m_SelectedFrameIdFieldInfo.GetValue(selectedEntry), (int)m_SelectedThreadIndexFieldInfo.GetValue(selectedEntry)))
|
||||
{
|
||||
if (frameData != null && frameData.valid)
|
||||
{
|
||||
threadGroupName = frameData.threadGroupName;
|
||||
threadName = frameData.threadName;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
selectedMarkerChanged(m_SelectedNameFieldInfo.GetValue(selectedEntry).ToString(), threadGroupName, threadName);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public ProfileData PullFromProfiler(int firstFrameDisplayIndex, int lastFrameDisplayIndex)
|
||||
{
|
||||
Profiler.BeginSample("ProfilerWindowInterface.PullFromProfiler");
|
||||
|
||||
bool recording = IsRecording();
|
||||
if (recording)
|
||||
StopRecording();
|
||||
|
||||
int firstFrameIndex = Mathf.Max(firstFrameDisplayIndex - 1, 0);
|
||||
int lastFrameIndex = lastFrameDisplayIndex - 1;
|
||||
ProfileData profileData = GetData(firstFrameIndex, lastFrameIndex);
|
||||
|
||||
if (recording)
|
||||
StartRecording();
|
||||
|
||||
Profiler.EndSample();
|
||||
return profileData;
|
||||
}
|
||||
|
||||
public int GetThreadCountForFrame(int frameIndex)
|
||||
{
|
||||
if (!IsReady())
|
||||
return 0;
|
||||
|
||||
ProfilerFrameDataIterator frameData = new ProfilerFrameDataIterator();
|
||||
frameData.SetRoot(frameIndex, 0);
|
||||
return frameData.GetThreadCount(frameIndex);
|
||||
}
|
||||
|
||||
public ProfileFrame GetProfileFrameForThread(int frameIndex, int threadIndex)
|
||||
{
|
||||
if (!IsReady())
|
||||
return null;
|
||||
|
||||
var frame = new ProfileFrame();
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
using (RawFrameDataView frameData = ProfilerDriver.GetRawFrameDataView(frameIndex, threadIndex))
|
||||
{
|
||||
frame.msStartTime = frameData.frameStartTimeMs;
|
||||
frame.msFrame = frameData.frameTimeMs;
|
||||
}
|
||||
#else
|
||||
ProfilerFrameDataIterator frameData = new ProfilerFrameDataIterator();
|
||||
frameData.SetRoot(frameIndex, threadIndex);
|
||||
frame.msStartTime = 1000.0 * frameData.GetFrameStartS(frameIndex);
|
||||
frame.msFrame = frameData.frameTimeMS;
|
||||
#endif
|
||||
return frame;
|
||||
}
|
||||
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
ProfileData GetDataRaw(ProfileData data, int firstFrameIndex, int lastFrameIndex)
|
||||
{
|
||||
bool firstError = true;
|
||||
|
||||
data.SetFrameIndexOffset(firstFrameIndex);
|
||||
|
||||
var depthStack = new Stack<int>();
|
||||
|
||||
var threadNameCount = new Dictionary<string, int>();
|
||||
var markerIdToNameIndex = new Dictionary<int, int>();
|
||||
|
||||
for (int frameIndex = firstFrameIndex; frameIndex <= lastFrameIndex; ++frameIndex)
|
||||
{
|
||||
m_progressBar.AdvanceProgressBar();
|
||||
|
||||
int threadIndex = 0;
|
||||
|
||||
threadNameCount.Clear();
|
||||
ProfileFrame frame = null;
|
||||
while (true)
|
||||
{
|
||||
using (RawFrameDataView frameData = ProfilerDriver.GetRawFrameDataView(frameIndex, threadIndex))
|
||||
{
|
||||
if (threadIndex == 0)
|
||||
{
|
||||
if ((frameIndex == firstFrameIndex || frameIndex == lastFrameIndex)
|
||||
&& firstFrameIndex != lastFrameIndex && (!frameData.valid || frameData.frameTimeNs == 0))
|
||||
{
|
||||
// skip incomplete frames when they are at the beginning or end of the capture
|
||||
if (++frameIndex <= lastFrameIndex)
|
||||
{
|
||||
data.FirstFrameIncomplete = true;
|
||||
data.SetFrameIndexOffset(frameIndex);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// break out entirely if this is the last frame
|
||||
data.LastFrameIncomplete = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
frame = new ProfileFrame();
|
||||
if (frameData.valid)
|
||||
{
|
||||
frame.msStartTime = frameData.frameStartTimeMs;
|
||||
frame.msFrame = frameData.frameTimeMs;
|
||||
}
|
||||
data.Add(frame);
|
||||
}
|
||||
|
||||
if (!frameData.valid)
|
||||
break;
|
||||
|
||||
string threadNameWithIndex = null;
|
||||
string threadName = frameData.threadName;
|
||||
if (threadName.Trim() == "")
|
||||
{
|
||||
Debug.Log(string.Format("Warning: Unnamed thread found on frame {0}. Corrupted data suspected, ignoring frame", frameIndex));
|
||||
threadIndex++;
|
||||
continue;
|
||||
}
|
||||
var groupName = frameData.threadGroupName;
|
||||
threadName = ProfileData.GetThreadNameWithGroup(threadName, groupName);
|
||||
|
||||
int nameCount = 0;
|
||||
threadNameCount.TryGetValue(threadName, out nameCount);
|
||||
threadNameCount[threadName] = nameCount + 1;
|
||||
|
||||
threadNameWithIndex = ProfileData.ThreadNameWithIndex(threadNameCount[threadName], threadName);
|
||||
|
||||
var thread = new ProfileThread();
|
||||
data.AddThreadName(threadNameWithIndex, thread);
|
||||
|
||||
frame.Add(thread);
|
||||
|
||||
// The markers are in depth first order
|
||||
depthStack.Clear();
|
||||
// first sample is the thread name
|
||||
for (int i = 1; i < frameData.sampleCount; i++)
|
||||
{
|
||||
float durationMS = frameData.GetSampleTimeMs(i);
|
||||
int markerId = frameData.GetSampleMarkerId(i);
|
||||
if (durationMS < 0)
|
||||
{
|
||||
if (firstError)
|
||||
{
|
||||
int displayIndex = data.OffsetToDisplayFrame(frameIndex);
|
||||
|
||||
string name = frameData.GetSampleName(i);
|
||||
Debug.LogFormat("Ignoring Invalid marker time found for {0} on frame {1} on thread {2} ({3} < 0)",
|
||||
name, displayIndex, threadName, durationMS);
|
||||
|
||||
firstError = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int depth = 1 + depthStack.Count;
|
||||
var markerData = ProfileMarker.Create(durationMS, depth);
|
||||
|
||||
// Use name index directly if we have already stored this named marker before
|
||||
int nameIndex;
|
||||
if (markerIdToNameIndex.TryGetValue(markerId, out nameIndex))
|
||||
{
|
||||
markerData.nameIndex = nameIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
string name = frameData.GetSampleName(i);
|
||||
data.AddMarkerName(name, markerData);
|
||||
markerIdToNameIndex[markerId] = markerData.nameIndex;
|
||||
}
|
||||
|
||||
thread.AddMarker(markerData);
|
||||
}
|
||||
|
||||
int childrenCount = frameData.GetSampleChildrenCount(i);
|
||||
if (childrenCount > 0)
|
||||
{
|
||||
depthStack.Push(childrenCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (depthStack.Count > 0)
|
||||
{
|
||||
int remainingChildren = depthStack.Pop();
|
||||
if (remainingChildren > 1)
|
||||
{
|
||||
depthStack.Push(remainingChildren - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
threadIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
data.Finalise();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
ProfileData GetDataOriginal(ProfileData data, int firstFrameIndex, int lastFrameIndex)
|
||||
{
|
||||
ProfilerFrameDataIterator frameData = new ProfilerFrameDataIterator();
|
||||
bool firstError = true;
|
||||
|
||||
data.SetFrameIndexOffset(firstFrameIndex);
|
||||
|
||||
Dictionary<string, int> threadNameCount = new Dictionary<string, int>();
|
||||
for (int frameIndex = firstFrameIndex; frameIndex <= lastFrameIndex; ++frameIndex)
|
||||
{
|
||||
m_progressBar.AdvanceProgressBar();
|
||||
|
||||
int threadCount = frameData.GetThreadCount(frameIndex);
|
||||
frameData.SetRoot(frameIndex, 0);
|
||||
|
||||
var msFrame = frameData.frameTimeMS;
|
||||
|
||||
if ((frameIndex == firstFrameIndex || frameIndex == lastFrameIndex)
|
||||
&& firstFrameIndex != lastFrameIndex && msFrame == 0)
|
||||
{
|
||||
var nextFrame = frameIndex + 1;
|
||||
// skip incomplete frames when they are at the beginning or end of the capture
|
||||
if (nextFrame <= lastFrameIndex)
|
||||
{
|
||||
data.FirstFrameIncomplete = true;
|
||||
data.SetFrameIndexOffset(nextFrame);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// break out entirely if this is the last frame
|
||||
data.LastFrameIncomplete = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if (frameIndex == lastFrameIndex)
|
||||
{
|
||||
// Check if last frame appears to be invalid data
|
||||
float median;
|
||||
float mean;
|
||||
float standardDeviation;
|
||||
CalculateFrameTimeStats(data, out median, out mean, out standardDeviation);
|
||||
float execessiveDeviation = (3f * standardDeviation);
|
||||
if (msFrame > (median + execessiveDeviation))
|
||||
{
|
||||
Debug.LogFormat("Dropping last frame as it is significantly larger than the median of the rest of the data set {0} > {1} (median {2} + 3 * standard deviation {3})", msFrame, median + execessiveDeviation, median, standardDeviation);
|
||||
break;
|
||||
}
|
||||
if (msFrame < (median - execessiveDeviation))
|
||||
{
|
||||
Debug.LogFormat("Dropping last frame as it is significantly smaller than the median of the rest of the data set {0} < {1} (median {2} - 3 * standard deviation {3})", msFrame, median - execessiveDeviation, median, standardDeviation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
ProfileFrame frame = new ProfileFrame();
|
||||
frame.msStartTime = 1000.0 * frameData.GetFrameStartS(frameIndex);
|
||||
frame.msFrame = msFrame;
|
||||
data.Add(frame);
|
||||
|
||||
threadNameCount.Clear();
|
||||
for (int threadIndex = 0; threadIndex < threadCount; ++threadIndex)
|
||||
{
|
||||
frameData.SetRoot(frameIndex, threadIndex);
|
||||
|
||||
var threadName = frameData.GetThreadName();
|
||||
if (threadName.Trim() == "")
|
||||
{
|
||||
Debug.Log(string.Format("Warning: Unnamed thread found on frame {0}. Corrupted data suspected, ignoring frame", frameIndex));
|
||||
continue;
|
||||
}
|
||||
|
||||
var groupName = frameData.GetGroupName();
|
||||
threadName = ProfileData.GetThreadNameWithGroup(threadName, groupName);
|
||||
|
||||
ProfileThread thread = new ProfileThread();
|
||||
frame.Add(thread);
|
||||
|
||||
int nameCount = 0;
|
||||
threadNameCount.TryGetValue(threadName, out nameCount);
|
||||
threadNameCount[threadName] = nameCount + 1;
|
||||
|
||||
data.AddThreadName(ProfileData.ThreadNameWithIndex(threadNameCount[threadName], threadName), thread);
|
||||
|
||||
const bool enterChildren = true;
|
||||
// The markers are in depth first order and the depth is known
|
||||
// So we can infer a parent child relationship
|
||||
while (frameData.Next(enterChildren))
|
||||
{
|
||||
if (frameData.durationMS < 0)
|
||||
{
|
||||
if (firstError)
|
||||
{
|
||||
int displayIndex = data.OffsetToDisplayFrame(frameIndex);
|
||||
|
||||
Debug.LogFormat("Ignoring Invalid marker time found for {0} on frame {1} on thread {2} ({3} < 0) : Instance id : {4}",
|
||||
frameData.name, displayIndex, threadName, frameData.durationMS, frameData.instanceId);
|
||||
|
||||
firstError = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
var markerData = ProfileMarker.Create(frameData);
|
||||
|
||||
data.AddMarkerName(frameData.name, markerData);
|
||||
thread.AddMarker(markerData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.Finalise();
|
||||
|
||||
frameData.Dispose();
|
||||
return data;
|
||||
}
|
||||
|
||||
ProfileData GetData(int firstFrameIndex, int lastFrameIndex)
|
||||
{
|
||||
ProfileData data = new ProfileData(ProfileAnalyzerWindow.TmpPath);
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
GetDataRaw(data, firstFrameIndex, lastFrameIndex);
|
||||
#else
|
||||
GetDataOriginal(data, firstFrameIndex, lastFrameIndex);
|
||||
#endif
|
||||
data.Write();
|
||||
return data;
|
||||
}
|
||||
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
public float GetFrameTimeRaw(int frameIndex)
|
||||
{
|
||||
using (RawFrameDataView frameData = ProfilerDriver.GetRawFrameDataView(frameIndex, 0))
|
||||
{
|
||||
if (!frameData.valid)
|
||||
return 0f;
|
||||
|
||||
return frameData.frameTimeMs;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
public float GetFrameTime(int frameIndex)
|
||||
{
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
if (s_UseRawIterator)
|
||||
return GetFrameTimeRaw(frameIndex);
|
||||
#endif
|
||||
ProfilerFrameDataIterator frameData = new ProfilerFrameDataIterator();
|
||||
|
||||
frameData.SetRoot(frameIndex, 0);
|
||||
float ms = frameData.frameTimeMS;
|
||||
frameData.Dispose();
|
||||
|
||||
return ms;
|
||||
}
|
||||
|
||||
struct ThreadIndexIterator
|
||||
{
|
||||
public ProfilerFrameDataIterator frameData;
|
||||
public int threadIndex;
|
||||
}
|
||||
|
||||
IEnumerator<ThreadIndexIterator> GetNextThreadIndexFittingThreadFilters(int frameIndex, List<string> threadFilters)
|
||||
{
|
||||
ProfilerFrameDataIterator frameData = new ProfilerFrameDataIterator();
|
||||
|
||||
int threadCount = frameData.GetThreadCount(frameIndex);
|
||||
Dictionary<string, int> threadNameCount = new Dictionary<string, int>();
|
||||
for (int threadIndex = 0; threadIndex < threadCount; ++threadIndex)
|
||||
{
|
||||
frameData.SetRoot(frameIndex, threadIndex);
|
||||
|
||||
var threadName = frameData.GetThreadName();
|
||||
// Name here could be "Worker Thread 1"
|
||||
|
||||
var groupName = frameData.GetGroupName();
|
||||
threadName = ProfileData.GetThreadNameWithGroup(threadName, groupName);
|
||||
|
||||
int nameCount = 0;
|
||||
threadNameCount.TryGetValue(threadName, out nameCount);
|
||||
threadNameCount[threadName] = nameCount + 1;
|
||||
|
||||
var threadNameWithIndex = ProfileData.ThreadNameWithIndex(threadNameCount[threadName], threadName);
|
||||
|
||||
// To compare on the filter we need to remove the postfix on the thread name
|
||||
// "3:Worker Thread 0" -> "1:Worker Thread"
|
||||
// The index of the thread (0) is used +1 as a prefix
|
||||
// The preceding number (3) is the count of number of threads with this name
|
||||
// Unfortunately multiple threads can have the same name
|
||||
threadNameWithIndex = ProfileData.CorrectThreadName(threadNameWithIndex);
|
||||
|
||||
if (threadFilters.Contains(threadNameWithIndex))
|
||||
{
|
||||
yield return new ThreadIndexIterator {frameData = frameData, threadIndex = threadIndex};
|
||||
}
|
||||
}
|
||||
frameData.Dispose();
|
||||
}
|
||||
|
||||
bool GetMarkerInfo(string markerName, int frameIndex, List<string> threadFilters, out int outThreadIndex, out int outNativeIndex, out float time, out float duration, out int instanceId)
|
||||
{
|
||||
outThreadIndex = 0;
|
||||
outNativeIndex = 0;
|
||||
time = 0.0f;
|
||||
duration = 0.0f;
|
||||
instanceId = 0;
|
||||
bool found = false;
|
||||
|
||||
var iterator = GetNextThreadIndexFittingThreadFilters(frameIndex, threadFilters);
|
||||
while (iterator.MoveNext())
|
||||
{
|
||||
const bool enterChildren = true;
|
||||
while (iterator.Current.frameData.Next(enterChildren))
|
||||
{
|
||||
if (iterator.Current.frameData.name == markerName)
|
||||
{
|
||||
time = iterator.Current.frameData.startTimeMS;
|
||||
duration = iterator.Current.frameData.durationMS;
|
||||
instanceId = iterator.Current.frameData.instanceId;
|
||||
outNativeIndex = iterator.Current.frameData.sampleId;
|
||||
outThreadIndex = iterator.Current.threadIndex;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
public bool SetProfilerWindowMarkerName(string markerName, List<string> threadFilters)
|
||||
{
|
||||
m_SendingSelectionEventToProfilerWindowInProgress = true;
|
||||
if (m_ProfilerWindow == null)
|
||||
return false;
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
var cpuModuleIdentifier = ProfilerWindow.cpuModuleIdentifier;
|
||||
var selectedModuleIdentifier = m_ProfilerWindow.selectedModuleIdentifier;
|
||||
#else
|
||||
var cpuModuleIdentifier = ProfilerWindow.cpuModuleName;
|
||||
var selectedModuleIdentifier = m_ProfilerWindow.selectedModuleName;
|
||||
#endif
|
||||
m_CpuProfilerModule = m_ProfilerWindow.GetFrameTimeViewSampleSelectionController(cpuModuleIdentifier);
|
||||
if (m_CpuProfilerModule != null && selectedModuleIdentifier == cpuModuleIdentifier
|
||||
&& m_ProfilerWindow.firstAvailableFrameIndex >= 0)
|
||||
{
|
||||
// Read profiler data direct from profile to find time/duration
|
||||
int currentFrameIndex = (int)m_ProfilerWindow.selectedFrameIndex;
|
||||
|
||||
var iterator = GetNextThreadIndexFittingThreadFilters(currentFrameIndex, threadFilters);
|
||||
while (iterator.MoveNext())
|
||||
{
|
||||
using (var rawFrameDataView = ProfilerDriver.GetRawFrameDataView(currentFrameIndex, iterator.Current.threadIndex))
|
||||
{
|
||||
if (m_CpuProfilerModule.SetSelection(currentFrameIndex,
|
||||
rawFrameDataView.threadGroupName, rawFrameDataView.threadName, markerName,
|
||||
threadId: rawFrameDataView.threadId))
|
||||
{
|
||||
m_ProfilerWindow.Repaint();
|
||||
m_SendingSelectionEventToProfilerWindowInProgress = false;
|
||||
return true; // setting the selection was successful, nothing more to do here.
|
||||
}
|
||||
}
|
||||
}
|
||||
// selection couldn't be found, so clear the current one to avoid confusion
|
||||
m_CpuProfilerModule.ClearSelection();
|
||||
m_ProfilerWindow.Repaint();
|
||||
}
|
||||
#else
|
||||
var timeLineGUI = GetTimeLineGUI();
|
||||
if (timeLineGUI == null)
|
||||
{
|
||||
m_SendingSelectionEventToProfilerWindowInProgress = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_SelectedEntryFieldInfo != null)
|
||||
{
|
||||
var selectedEntry = m_SelectedEntryFieldInfo.GetValue(timeLineGUI);
|
||||
if (selectedEntry != null)
|
||||
{
|
||||
// Read profiler data direct from profile to find time/duration
|
||||
int currentFrameIndex = (int)m_CurrentFrameFieldInfo.GetValue(m_ProfilerWindow);
|
||||
float time;
|
||||
float duration;
|
||||
int instanceId;
|
||||
int nativeIndex;
|
||||
int threadIndex;
|
||||
if (GetMarkerInfo(markerName, currentFrameIndex, threadFilters, out threadIndex, out nativeIndex, out time, out duration, out instanceId))
|
||||
{
|
||||
/*
|
||||
Debug.Log(string.Format("Setting profiler to {0} on {1} at frame {2} at {3}ms for {4}ms ({5})",
|
||||
markerName, currentFrameIndex, threadFilter, time, duration, instanceId));
|
||||
*/
|
||||
|
||||
if (m_SelectedNameFieldInfo != null)
|
||||
m_SelectedNameFieldInfo.SetValue(selectedEntry, markerName);
|
||||
if (m_SelectedTimeFieldInfo != null)
|
||||
m_SelectedTimeFieldInfo.SetValue(selectedEntry, time);
|
||||
if (m_SelectedDurationFieldInfo != null)
|
||||
m_SelectedDurationFieldInfo.SetValue(selectedEntry, duration);
|
||||
if (m_SelectedInstanceIdFieldInfo != null)
|
||||
m_SelectedInstanceIdFieldInfo.SetValue(selectedEntry, instanceId);
|
||||
if (m_SelectedFrameIdFieldInfo != null)
|
||||
m_SelectedFrameIdFieldInfo.SetValue(selectedEntry, currentFrameIndex);
|
||||
if (m_SelectedNativeIndexFieldInfo != null)
|
||||
m_SelectedNativeIndexFieldInfo.SetValue(selectedEntry, nativeIndex);
|
||||
if (m_SelectedThreadIndexFieldInfo != null)
|
||||
m_SelectedThreadIndexFieldInfo.SetValue(selectedEntry, threadIndex);
|
||||
|
||||
// TODO : Update to fill in the total and number of instances.
|
||||
// For now we force Instance count to 1 to avoid the incorrect info showing.
|
||||
if (m_SelectedInstanceCountFieldInfo != null)
|
||||
m_SelectedInstanceCountFieldInfo.SetValue(selectedEntry, 1);
|
||||
if (m_SelectedInstanceCountForThreadFieldInfo != null)
|
||||
m_SelectedInstanceCountForThreadFieldInfo.SetValue(selectedEntry, 1);
|
||||
if (m_SelectedInstanceCountForFrameFieldInfo != null)
|
||||
m_SelectedInstanceCountForFrameFieldInfo.SetValue(selectedEntry, 1);
|
||||
if (m_SelectedThreadCountFieldInfo != null)
|
||||
m_SelectedThreadCountFieldInfo.SetValue(selectedEntry, 1);
|
||||
if (m_SelectedMetaDataFieldInfo != null)
|
||||
m_SelectedMetaDataFieldInfo.SetValue(selectedEntry, "");
|
||||
if (m_SelectedCallstackInfoFieldInfo != null)
|
||||
m_SelectedCallstackInfoFieldInfo.SetValue(selectedEntry, "");
|
||||
|
||||
m_ProfilerWindow.Repaint();
|
||||
m_SendingSelectionEventToProfilerWindowInProgress = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
m_SendingSelectionEventToProfilerWindowInProgress = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool JumpToFrame(int index)
|
||||
{
|
||||
if (m_ProfilerWindow == null)
|
||||
return false;
|
||||
|
||||
if (index - 1 < ProfilerDriver.firstFrameIndex)
|
||||
return false;
|
||||
if (index - 1 > ProfilerDriver.lastFrameIndex)
|
||||
return false;
|
||||
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
m_ProfilerWindow.selectedFrameIndex = index - 1;
|
||||
#else
|
||||
m_CurrentFrameFieldInfo.SetValue(m_ProfilerWindow, index - 1);
|
||||
#endif
|
||||
m_ProfilerWindow.Repaint();
|
||||
return true;
|
||||
}
|
||||
|
||||
public int selectedFrame
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_ProfilerWindow == null)
|
||||
return 0;
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
return (int)m_ProfilerWindow.selectedFrameIndex + 1;
|
||||
#else
|
||||
return (int)m_CurrentFrameFieldInfo.GetValue(m_ProfilerWindow) + 1;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<int> selectedFrameChanged = delegate {};
|
||||
|
||||
public void PollSelectedFrameChanges()
|
||||
{
|
||||
var currentlySelectedFrame = selectedFrame;
|
||||
if (m_LastSelectedFrameInProfilerWindow != currentlySelectedFrame && !m_SendingSelectionEventToProfilerWindowInProgress)
|
||||
{
|
||||
m_LastSelectedFrameInProfilerWindow = currentlySelectedFrame;
|
||||
selectedFrameChanged(currentlySelectedFrame);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRecording()
|
||||
{
|
||||
#if UNITY_2017_4_OR_NEWER
|
||||
return ProfilerDriver.enabled;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void StopRecording()
|
||||
{
|
||||
#if UNITY_2017_4_OR_NEWER
|
||||
// Stop recording first
|
||||
ProfilerDriver.enabled = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void StartRecording()
|
||||
{
|
||||
#if UNITY_2017_4_OR_NEWER
|
||||
// Stop recording first
|
||||
ProfilerDriver.enabled = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void OnDisable()
|
||||
{
|
||||
if (m_ProfilerWindow != null)
|
||||
{
|
||||
m_ProfilerWindow = null;
|
||||
}
|
||||
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
if (m_CpuProfilerModule != null)
|
||||
{
|
||||
m_CpuProfilerModule.selectionChanged -= OnSelectionChangedInCpuProfilerModule;
|
||||
m_CpuProfilerModule = null;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d8e7ffaacab264ab6ba5aa70f432785f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c27dfe7bd1055403b9dac63caf69c135
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,72 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
[Serializable]
|
||||
internal class ThreadData
|
||||
{
|
||||
public string threadNameWithIndex;
|
||||
public int threadGroupIndex;
|
||||
public string threadGroupName;
|
||||
public int threadsInGroup;
|
||||
public List<ThreadFrameTime> frames = new List<ThreadFrameTime>();
|
||||
|
||||
public float msMedian;
|
||||
public float msLowerQuartile;
|
||||
public float msUpperQuartile;
|
||||
public float msMin;
|
||||
public float msMax;
|
||||
|
||||
public int medianFrameIndex;
|
||||
public int minFrameIndex;
|
||||
public int maxFrameIndex;
|
||||
|
||||
public ThreadData(string _threadName)
|
||||
{
|
||||
threadNameWithIndex = _threadName;
|
||||
|
||||
var info = threadNameWithIndex.Split(':');
|
||||
threadGroupIndex = int.Parse(info[0]);
|
||||
threadGroupName = info[1];
|
||||
threadsInGroup = 1;
|
||||
|
||||
msMedian = 0.0f;
|
||||
msLowerQuartile = 0.0f;
|
||||
msUpperQuartile = 0.0f;
|
||||
msMin = 0.0f;
|
||||
msMax = 0.0f;
|
||||
|
||||
medianFrameIndex = -1;
|
||||
minFrameIndex = -1;
|
||||
maxFrameIndex = -1;
|
||||
}
|
||||
|
||||
struct ThreadFrameTimeFrameComparer : IComparer<ThreadFrameTime>
|
||||
{
|
||||
#if UNITY_2017_OR_NEWER
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#else
|
||||
[MethodImpl(256)]
|
||||
#endif
|
||||
public int Compare(ThreadFrameTime x, ThreadFrameTime y)
|
||||
{
|
||||
if (x.frameIndex == y.frameIndex)
|
||||
return 0;
|
||||
return x.frameIndex > y.frameIndex ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_2017_OR_NEWER
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#else
|
||||
[MethodImpl(256)]
|
||||
#endif
|
||||
public ThreadFrameTime? GetFrame(int frameIndex)
|
||||
{
|
||||
var index = frames.BinarySearch(new ThreadFrameTime(frameIndex, 0, 0), new ThreadFrameTimeFrameComparer());
|
||||
return index >= 0 ? (ThreadFrameTime?)frames[index] : null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b0c2bd11491424d188d23f3ae3e16b79
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fb9ea80c859e94311bdc05e75704cede
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 977c7fa7681d742d591608f4dbb8b6af
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: edb0539be3883af4b90ff66c35074ae0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,965 @@
|
|||
using UnityEngine;
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEngine.Assertions;
|
||||
using System.Linq;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
class ThreadTreeViewItem : TreeViewItem
|
||||
{
|
||||
public readonly ThreadIdentifier threadIdentifier;
|
||||
|
||||
public ThreadTreeViewItem(int id, int depth, string displayName, ThreadIdentifier threadIdentifier) : base(id, depth, displayName)
|
||||
{
|
||||
this.threadIdentifier = threadIdentifier;
|
||||
}
|
||||
}
|
||||
|
||||
class ThreadTable : TreeView
|
||||
{
|
||||
const float kRowHeights = 20f;
|
||||
readonly List<TreeViewItem> m_Rows = new List<TreeViewItem>(100);
|
||||
|
||||
List<string> m_ThreadNames;
|
||||
List<string> m_ThreadUINames;
|
||||
ThreadIdentifier m_AllThreadIdentifier;
|
||||
ThreadSelection m_ThreadSelection;
|
||||
|
||||
GUIStyle activeLineStyle;
|
||||
|
||||
public bool StateChanged { get; private set; }
|
||||
|
||||
// All columns
|
||||
public enum MyColumns
|
||||
{
|
||||
GroupName,
|
||||
ThreadName,
|
||||
State,
|
||||
}
|
||||
|
||||
public enum SortOption
|
||||
{
|
||||
GroupName,
|
||||
ThreadName,
|
||||
State,
|
||||
}
|
||||
|
||||
// Sort options per column
|
||||
SortOption[] m_SortOptions =
|
||||
{
|
||||
SortOption.GroupName,
|
||||
SortOption.ThreadName,
|
||||
SortOption.State,
|
||||
};
|
||||
|
||||
public enum ThreadSelected
|
||||
{
|
||||
Selected,
|
||||
Partial,
|
||||
NotSelected
|
||||
};
|
||||
|
||||
public ThreadTable(TreeViewState state, MultiColumnHeader multicolumnHeader, List<string> threadNames, List<string> threadUINames, ThreadSelection threadSelection) : base(state, multicolumnHeader)
|
||||
{
|
||||
StateChanged = false;
|
||||
|
||||
m_AllThreadIdentifier = new ThreadIdentifier();
|
||||
m_AllThreadIdentifier.SetName("All");
|
||||
m_AllThreadIdentifier.SetAll();
|
||||
|
||||
Assert.AreEqual(m_SortOptions.Length, Enum.GetValues(typeof(MyColumns)).Length, "Ensure number of sort options are in sync with number of MyColumns enum values");
|
||||
|
||||
// Custom setup
|
||||
rowHeight = kRowHeights;
|
||||
showAlternatingRowBackgrounds = true;
|
||||
columnIndexForTreeFoldouts = (int)(MyColumns.GroupName);
|
||||
showBorder = true;
|
||||
customFoldoutYOffset = (kRowHeights - EditorGUIUtility.singleLineHeight) * 0.5f; // center foldout in the row since we also center content. See RowGUI
|
||||
// extraSpaceBeforeIconAndLabel = 0;
|
||||
multicolumnHeader.sortingChanged += OnSortingChanged;
|
||||
multicolumnHeader.visibleColumnsChanged += OnVisibleColumnsChanged;
|
||||
|
||||
m_ThreadNames = threadNames;
|
||||
m_ThreadUINames = threadUINames;
|
||||
m_ThreadSelection = new ThreadSelection(threadSelection);
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
this.foldoutOverride += DoFoldout;
|
||||
#endif
|
||||
Reload();
|
||||
}
|
||||
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
bool DoFoldout(Rect position, bool expandedstate, GUIStyle style)
|
||||
{
|
||||
return !(position.y < rowHeight) && GUI.Toggle(position, expandedstate, GUIContent.none, style);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public void ClearThreadSelection()
|
||||
{
|
||||
m_ThreadSelection.selection.Clear();
|
||||
m_ThreadSelection.groups.Clear();
|
||||
|
||||
StateChanged = true;
|
||||
Reload();
|
||||
}
|
||||
|
||||
public void SelectMain()
|
||||
{
|
||||
m_ThreadSelection.selection.Clear();
|
||||
m_ThreadSelection.groups.Clear();
|
||||
|
||||
foreach (var threadName in m_ThreadNames)
|
||||
{
|
||||
if (threadName.StartsWith("All:"))
|
||||
continue;
|
||||
|
||||
if (threadName.EndsWith(":Main Thread")) // Usually just starts with 1:
|
||||
m_ThreadSelection.selection.Add(threadName);
|
||||
}
|
||||
|
||||
StateChanged = true;
|
||||
Reload();
|
||||
}
|
||||
|
||||
public void SelectCommon()
|
||||
{
|
||||
m_ThreadSelection.selection.Clear();
|
||||
m_ThreadSelection.groups.Clear();
|
||||
|
||||
foreach (var threadName in m_ThreadNames)
|
||||
{
|
||||
if (threadName.StartsWith("All:"))
|
||||
continue;
|
||||
|
||||
if (threadName.EndsWith(":Render Thread")) // Usually just starts with 1:
|
||||
m_ThreadSelection.selection.Add(threadName);
|
||||
if (threadName.EndsWith(":Main Thread")) // Usually just starts with 1:
|
||||
m_ThreadSelection.selection.Add(threadName);
|
||||
if (threadName.EndsWith(":Job.Worker")) // Mulitple jobs, number depends on processor setup
|
||||
m_ThreadSelection.selection.Add(threadName);
|
||||
}
|
||||
|
||||
StateChanged = true;
|
||||
Reload();
|
||||
}
|
||||
|
||||
public ThreadSelection GetThreadSelection()
|
||||
{
|
||||
return m_ThreadSelection;
|
||||
}
|
||||
|
||||
protected int GetChildCount(ThreadIdentifier selectedThreadIdentifier, out int selected)
|
||||
{
|
||||
int count = 0;
|
||||
int selectedCount = 0;
|
||||
|
||||
if (selectedThreadIdentifier.index == ThreadIdentifier.kAll)
|
||||
{
|
||||
if (selectedThreadIdentifier.name == "All")
|
||||
{
|
||||
for (int index = 0; index < m_ThreadNames.Count; ++index)
|
||||
{
|
||||
var threadNameWithIndex = m_ThreadNames[index];
|
||||
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
|
||||
|
||||
if (threadIdentifier.index != ThreadIdentifier.kAll)
|
||||
{
|
||||
count++;
|
||||
if (m_ThreadSelection.selection.Contains(threadNameWithIndex))
|
||||
selectedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int index = 0; index < m_ThreadNames.Count; ++index)
|
||||
{
|
||||
var threadNameWithIndex = m_ThreadNames[index];
|
||||
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
|
||||
|
||||
if (selectedThreadIdentifier.name == threadIdentifier.name &&
|
||||
threadIdentifier.index != ThreadIdentifier.kAll)
|
||||
{
|
||||
count++;
|
||||
if (m_ThreadSelection.selection.Contains(threadNameWithIndex))
|
||||
selectedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selected = selectedCount;
|
||||
return count;
|
||||
}
|
||||
|
||||
protected override TreeViewItem BuildRoot()
|
||||
{
|
||||
int idForHiddenRoot = -1;
|
||||
int depthForHiddenRoot = -1;
|
||||
ProfileTreeViewItem root = new ProfileTreeViewItem(idForHiddenRoot, depthForHiddenRoot, "root", null);
|
||||
|
||||
int depth = 0;
|
||||
|
||||
var top = new ThreadTreeViewItem(-1, depth, m_AllThreadIdentifier.name, m_AllThreadIdentifier);
|
||||
root.AddChild(top);
|
||||
|
||||
var expandList = new List<int>() {-1};
|
||||
string lastThreadName = "";
|
||||
TreeViewItem node = root;
|
||||
|
||||
for (int index = 0; index < m_ThreadNames.Count; ++index)
|
||||
{
|
||||
var threadNameWithIndex = m_ThreadNames[index];
|
||||
if (threadNameWithIndex == m_AllThreadIdentifier.threadNameWithIndex)
|
||||
continue;
|
||||
|
||||
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
|
||||
var item = new ThreadTreeViewItem(index, depth, m_ThreadUINames[index], threadIdentifier);
|
||||
|
||||
if (threadIdentifier.name != lastThreadName)
|
||||
{
|
||||
// New threads at root
|
||||
node = top;
|
||||
depth = 0;
|
||||
}
|
||||
|
||||
node.AddChild(item);
|
||||
|
||||
|
||||
if (threadIdentifier.name != lastThreadName)
|
||||
{
|
||||
// Extra instances hang of the parent
|
||||
lastThreadName = threadIdentifier.name;
|
||||
node = item;
|
||||
depth = 1;
|
||||
}
|
||||
}
|
||||
|
||||
SetExpanded(expandList);
|
||||
|
||||
SetupDepthsFromParentsAndChildren(root);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
void BuildRowRecursive(IList<TreeViewItem> rows, TreeViewItem item)
|
||||
{
|
||||
//if (item.children == null)
|
||||
// return;
|
||||
|
||||
if (!IsExpanded(item.id))
|
||||
return;
|
||||
|
||||
foreach (ThreadTreeViewItem subNode in item.children)
|
||||
{
|
||||
rows.Add(subNode);
|
||||
|
||||
if (subNode.children != null)
|
||||
BuildRowRecursive(rows, subNode);
|
||||
}
|
||||
}
|
||||
|
||||
void BuildAllRows(IList<TreeViewItem> rows, TreeViewItem rootItem)
|
||||
{
|
||||
rows.Clear();
|
||||
if (rootItem == null)
|
||||
return;
|
||||
|
||||
if (rootItem.children == null)
|
||||
return;
|
||||
|
||||
foreach (ThreadTreeViewItem node in rootItem.children)
|
||||
{
|
||||
rows.Add(node);
|
||||
|
||||
if (node.children != null)
|
||||
BuildRowRecursive(rows, node);
|
||||
}
|
||||
}
|
||||
|
||||
protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
|
||||
{
|
||||
BuildAllRows(m_Rows, root);
|
||||
|
||||
SortIfNeeded(m_Rows);
|
||||
|
||||
return m_Rows;
|
||||
}
|
||||
|
||||
void OnSortingChanged(MultiColumnHeader _multiColumnHeader)
|
||||
{
|
||||
SortIfNeeded(GetRows());
|
||||
}
|
||||
|
||||
void OnVisibleColumnsChanged(MultiColumnHeader _multiColumnHeader)
|
||||
{
|
||||
multiColumnHeader.ResizeToFit();
|
||||
}
|
||||
|
||||
void SortIfNeeded(IList<TreeViewItem> rows)
|
||||
{
|
||||
if (rows.Count <= 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (multiColumnHeader.sortedColumnIndex == -1)
|
||||
{
|
||||
return; // No column to sort for (just use the order the data are in)
|
||||
}
|
||||
|
||||
SortByMultipleColumns();
|
||||
|
||||
BuildAllRows(rows, rootItem);
|
||||
|
||||
Repaint();
|
||||
}
|
||||
|
||||
string GetItemGroupName(ThreadTreeViewItem item)
|
||||
{
|
||||
string groupName;
|
||||
string threadName = item.threadIdentifier.name;
|
||||
threadName = ProfileData.GetThreadNameWithoutGroup(item.threadIdentifier.name, out groupName);
|
||||
|
||||
return groupName;
|
||||
}
|
||||
|
||||
List<TreeViewItem> SortChildrenByMultipleColumns(List<TreeViewItem> children)
|
||||
{
|
||||
int[] sortedColumns = multiColumnHeader.state.sortedColumns;
|
||||
|
||||
if (sortedColumns.Length == 0)
|
||||
{
|
||||
return children;
|
||||
}
|
||||
|
||||
var myTypes = children.Cast<ThreadTreeViewItem>();
|
||||
var orderedQuery = InitialOrder(myTypes, sortedColumns);
|
||||
for (int i = 0; i < sortedColumns.Length; i++)
|
||||
{
|
||||
SortOption sortOption = m_SortOptions[sortedColumns[i]];
|
||||
bool ascending = multiColumnHeader.IsSortedAscending(sortedColumns[i]);
|
||||
|
||||
switch (sortOption)
|
||||
{
|
||||
case SortOption.GroupName:
|
||||
orderedQuery = orderedQuery.ThenBy(l => GetItemGroupName(l), ascending);
|
||||
break;
|
||||
case SortOption.ThreadName:
|
||||
orderedQuery = orderedQuery.ThenBy(l => GetItemDisplayText(l), ascending);
|
||||
break;
|
||||
case SortOption.State:
|
||||
orderedQuery = orderedQuery.ThenBy(l => GetStateSort(l), ascending);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return orderedQuery.Cast<TreeViewItem>().ToList();
|
||||
}
|
||||
|
||||
void SortByMultipleColumns()
|
||||
{
|
||||
rootItem.children = SortChildrenByMultipleColumns(rootItem.children);
|
||||
|
||||
// Sort all the next level children too (As 'All' is the only item at the top)
|
||||
for (int i = 0; i < rootItem.children.Count; i++)
|
||||
{
|
||||
var child = rootItem.children[0];
|
||||
child.children = SortChildrenByMultipleColumns(child.children);
|
||||
}
|
||||
}
|
||||
|
||||
IOrderedEnumerable<ThreadTreeViewItem> InitialOrder(IEnumerable<ThreadTreeViewItem> myTypes, int[] history)
|
||||
{
|
||||
SortOption sortOption = m_SortOptions[history[0]];
|
||||
bool ascending = multiColumnHeader.IsSortedAscending(history[0]);
|
||||
switch (sortOption)
|
||||
{
|
||||
case SortOption.GroupName:
|
||||
return myTypes.Order(l => GetItemGroupName(l), ascending);
|
||||
case SortOption.ThreadName:
|
||||
return myTypes.Order(l => GetItemDisplayText(l), ascending);
|
||||
case SortOption.State:
|
||||
return myTypes.Order(l => GetStateSort(l), ascending);
|
||||
default:
|
||||
Assert.IsTrue(false, "Unhandled enum");
|
||||
break;
|
||||
}
|
||||
|
||||
// default
|
||||
return myTypes.Order(l => GetItemDisplayText(l), ascending);
|
||||
}
|
||||
|
||||
protected override void RowGUI(RowGUIArgs args)
|
||||
{
|
||||
ThreadTreeViewItem item = (ThreadTreeViewItem)args.item;
|
||||
|
||||
for (int i = 0; i < args.GetNumVisibleColumns(); ++i)
|
||||
{
|
||||
CellGUI(args.GetCellRect(i), item, (MyColumns)args.GetColumn(i), ref args);
|
||||
}
|
||||
}
|
||||
|
||||
string GetStateSort(ThreadTreeViewItem item)
|
||||
{
|
||||
ThreadSelected threadSelected = GetThreadSelectedState(item.threadIdentifier);
|
||||
|
||||
string sortString = ((int)threadSelected).ToString() + GetItemDisplayText(item);
|
||||
|
||||
return sortString;
|
||||
}
|
||||
|
||||
ThreadSelected GetThreadSelectedState(ThreadIdentifier selectedThreadIdentifier)
|
||||
{
|
||||
if (ProfileAnalyzer.MatchThreadFilter(selectedThreadIdentifier.threadNameWithIndex, m_ThreadSelection.selection))
|
||||
return ThreadSelected.Selected;
|
||||
|
||||
// If querying the 'All' filter then check if all selected
|
||||
if (selectedThreadIdentifier.threadNameWithIndex == m_AllThreadIdentifier.threadNameWithIndex)
|
||||
{
|
||||
// Check all threads without All in the name are selected
|
||||
int count = 0;
|
||||
int selectedCount = 0;
|
||||
foreach (var threadNameWithIndex in m_ThreadNames)
|
||||
{
|
||||
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
|
||||
if (threadIdentifier.index == ThreadIdentifier.kAll || threadIdentifier.index == ThreadIdentifier.kSingle)
|
||||
continue;
|
||||
|
||||
if (m_ThreadSelection.selection.Contains(threadNameWithIndex))
|
||||
selectedCount++;
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
if (selectedCount == count)
|
||||
return ThreadSelected.Selected;
|
||||
|
||||
if (selectedCount > 0)
|
||||
return ThreadSelected.Partial;
|
||||
|
||||
return ThreadSelected.NotSelected;
|
||||
}
|
||||
|
||||
// Need to check 'All' and thread group All.
|
||||
if (selectedThreadIdentifier.index == ThreadIdentifier.kAll)
|
||||
{
|
||||
// Count all threads that match this thread group
|
||||
int count = 0;
|
||||
foreach (var threadNameWithIndex in m_ThreadNames)
|
||||
{
|
||||
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
|
||||
if (threadIdentifier.index == ThreadIdentifier.kAll || threadIdentifier.index == ThreadIdentifier.kSingle)
|
||||
continue;
|
||||
|
||||
if (selectedThreadIdentifier.name != threadIdentifier.name)
|
||||
continue;
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
// Count all the threads we have selected that match this thread group
|
||||
int selectedCount = 0;
|
||||
foreach (var threadNameWithIndex in m_ThreadSelection.selection)
|
||||
{
|
||||
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
|
||||
if (selectedThreadIdentifier.name != threadIdentifier.name)
|
||||
continue;
|
||||
if (threadIdentifier.index > count)
|
||||
continue;
|
||||
|
||||
selectedCount++;
|
||||
}
|
||||
|
||||
if (selectedCount == count)
|
||||
return ThreadSelected.Selected;
|
||||
|
||||
if (selectedCount > 0)
|
||||
return ThreadSelected.Partial;
|
||||
}
|
||||
|
||||
return ThreadSelected.NotSelected;
|
||||
}
|
||||
|
||||
string GetItemDisplayText(ThreadTreeViewItem item)
|
||||
{
|
||||
int selectedChildren;
|
||||
int childCount = GetChildCount(item.threadIdentifier, out selectedChildren);
|
||||
|
||||
string fullThreadName = item.threadIdentifier.name;
|
||||
string groupName;
|
||||
string threadName = ProfileData.GetThreadNameWithoutGroup(fullThreadName, out groupName);
|
||||
|
||||
string displayThreadName = multiColumnHeader.IsColumnVisible((int)MyColumns.GroupName) ? threadName : fullThreadName;
|
||||
|
||||
string text;
|
||||
if (childCount <= 1)
|
||||
{
|
||||
text = item.displayName;
|
||||
}
|
||||
else if (selectedChildren != childCount)
|
||||
{
|
||||
text = string.Format("{0} ({1} of {2})", displayThreadName, selectedChildren, childCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
text = string.Format("{0} (All)", displayThreadName);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
void GetThreadTreeViewItemInfo(ThreadTreeViewItem item, out string text, out string tooltip)
|
||||
{
|
||||
text = GetItemDisplayText(item);
|
||||
int selectedChildren;
|
||||
int childCount = GetChildCount(item.threadIdentifier, out selectedChildren);
|
||||
|
||||
string groupName = GetItemGroupName(item);
|
||||
|
||||
if (childCount <= 1)
|
||||
{
|
||||
tooltip = (groupName == "") ? text : string.Format("{0}\n{1}", text, groupName);
|
||||
}
|
||||
else if (selectedChildren != childCount)
|
||||
{
|
||||
tooltip = (groupName == "") ? text : string.Format("{0}\n{1}", text, groupName);
|
||||
}
|
||||
else
|
||||
{
|
||||
tooltip = (groupName == "") ? text : string.Format("{0}\n{1}", text, groupName);
|
||||
}
|
||||
}
|
||||
|
||||
Rect DrawIndent(Rect rect, ThreadTreeViewItem item, ref RowGUIArgs args)
|
||||
{
|
||||
// The rect is assumed indented and sized after the content when pinging
|
||||
float indent = GetContentIndent(item) + extraSpaceBeforeIconAndLabel;
|
||||
rect.xMin += indent;
|
||||
|
||||
int iconRectWidth = 16;
|
||||
int kSpaceBetweenIconAndText = 2;
|
||||
|
||||
// Draw icon
|
||||
Rect iconRect = rect;
|
||||
iconRect.width = iconRectWidth;
|
||||
// iconRect.x += 7f;
|
||||
|
||||
Texture icon = args.item.icon;
|
||||
if (icon != null)
|
||||
GUI.DrawTexture(iconRect, icon, ScaleMode.ScaleToFit);
|
||||
|
||||
rect.xMin += icon == null ? 0 : iconRectWidth + kSpaceBetweenIconAndText;
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
void CellGUI(Rect cellRect, ThreadTreeViewItem item, MyColumns column, ref RowGUIArgs args)
|
||||
{
|
||||
// Center cell rect vertically (makes it easier to place controls, icons etc in the cells)
|
||||
CenterRectUsingSingleLineHeight(ref cellRect);
|
||||
|
||||
switch (column)
|
||||
{
|
||||
case MyColumns.ThreadName:
|
||||
{
|
||||
args.rowRect = cellRect;
|
||||
// base.RowGUI(args); // Required to show tree indenting
|
||||
|
||||
// Draw manually to keep indenting while add a tooltip
|
||||
Rect rect = cellRect;
|
||||
if (Event.current.rawType == EventType.Repaint)
|
||||
{
|
||||
string text;
|
||||
string tooltip;
|
||||
GetThreadTreeViewItemInfo(item, out text, out tooltip);
|
||||
var content = new GUIContent(text, tooltip);
|
||||
|
||||
if (activeLineStyle == null)
|
||||
{
|
||||
// activeLineStyle = DefaultStyles.boldLabel;
|
||||
activeLineStyle = new GUIStyle(DefaultStyles.label);
|
||||
activeLineStyle.normal.textColor = DefaultStyles.boldLabel.onActive.textColor;
|
||||
}
|
||||
|
||||
// rect = DrawIndent(rect, item, ref args);
|
||||
|
||||
//bool mouseOver = rect.Contains(Event.current.mousePosition);
|
||||
//DefaultStyles.label.Draw(rect, content, mouseOver, false, args.selected, args.focused);
|
||||
|
||||
// Must use this call to draw tooltip
|
||||
EditorGUI.LabelField(rect, content, args.selected ? activeLineStyle : DefaultStyles.label);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MyColumns.GroupName:
|
||||
{
|
||||
Rect rect = cellRect;
|
||||
if (Event.current.rawType == EventType.Repaint)
|
||||
{
|
||||
rect = DrawIndent(rect, item, ref args);
|
||||
|
||||
string groupName = GetItemGroupName(item);
|
||||
var content = new GUIContent(groupName, groupName);
|
||||
EditorGUI.LabelField(rect, content);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MyColumns.State:
|
||||
bool oldState = GetThreadSelectedState(item.threadIdentifier) == ThreadSelected.Selected;
|
||||
bool newState = EditorGUI.Toggle(cellRect, oldState);
|
||||
if (newState != oldState)
|
||||
{
|
||||
if (item.threadIdentifier.threadNameWithIndex == m_AllThreadIdentifier.threadNameWithIndex)
|
||||
{
|
||||
// Record active groups
|
||||
m_ThreadSelection.groups.Clear();
|
||||
if (newState)
|
||||
{
|
||||
if (!m_ThreadSelection.groups.Contains(item.threadIdentifier.threadNameWithIndex))
|
||||
m_ThreadSelection.groups.Add(item.threadIdentifier.threadNameWithIndex);
|
||||
}
|
||||
|
||||
// Update selection
|
||||
m_ThreadSelection.selection.Clear();
|
||||
if (newState)
|
||||
{
|
||||
foreach (string threadNameWithIndex in m_ThreadNames)
|
||||
{
|
||||
if (threadNameWithIndex != m_AllThreadIdentifier.threadNameWithIndex)
|
||||
{
|
||||
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
|
||||
if (threadIdentifier.index != ThreadIdentifier.kAll)
|
||||
{
|
||||
m_ThreadSelection.selection.Add(threadNameWithIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (item.threadIdentifier.index == ThreadIdentifier.kAll)
|
||||
{
|
||||
// Record active groups
|
||||
if (newState)
|
||||
{
|
||||
if (!m_ThreadSelection.groups.Contains(item.threadIdentifier.threadNameWithIndex))
|
||||
m_ThreadSelection.groups.Add(item.threadIdentifier.threadNameWithIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ThreadSelection.groups.Remove(item.threadIdentifier.threadNameWithIndex);
|
||||
// When turning off a sub group, turn of the 'all' group too
|
||||
m_ThreadSelection.groups.Remove(m_AllThreadIdentifier.threadNameWithIndex);
|
||||
}
|
||||
|
||||
// Update selection
|
||||
if (newState)
|
||||
{
|
||||
foreach (string threadNameWithIndex in m_ThreadNames)
|
||||
{
|
||||
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
|
||||
if (threadIdentifier.name == item.threadIdentifier.name &&
|
||||
threadIdentifier.index != ThreadIdentifier.kAll)
|
||||
{
|
||||
if (!m_ThreadSelection.selection.Contains(threadNameWithIndex))
|
||||
m_ThreadSelection.selection.Add(threadNameWithIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var removeSelection = new List<string>();
|
||||
foreach (string threadNameWithIndex in m_ThreadSelection.selection)
|
||||
{
|
||||
var threadIdentifier = new ThreadIdentifier(threadNameWithIndex);
|
||||
if (threadIdentifier.name == item.threadIdentifier.name &&
|
||||
threadIdentifier.index != ThreadIdentifier.kAll)
|
||||
{
|
||||
removeSelection.Add(threadNameWithIndex);
|
||||
}
|
||||
}
|
||||
foreach (string threadNameWithIndex in removeSelection)
|
||||
{
|
||||
m_ThreadSelection.selection.Remove(threadNameWithIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (newState)
|
||||
{
|
||||
m_ThreadSelection.selection.Add(item.threadIdentifier.threadNameWithIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ThreadSelection.selection.Remove(item.threadIdentifier.threadNameWithIndex);
|
||||
|
||||
// Turn off any group its in too
|
||||
var groupIdentifier = new ThreadIdentifier(item.threadIdentifier);
|
||||
groupIdentifier.SetAll();
|
||||
m_ThreadSelection.groups.Remove(groupIdentifier.threadNameWithIndex);
|
||||
|
||||
// Turn of the 'all' group too
|
||||
m_ThreadSelection.groups.Remove(m_AllThreadIdentifier.threadNameWithIndex);
|
||||
}
|
||||
}
|
||||
|
||||
StateChanged = true;
|
||||
|
||||
// Re-sort
|
||||
SortIfNeeded(GetRows());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Misc
|
||||
//--------
|
||||
|
||||
protected override bool CanMultiSelect(TreeViewItem item)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
struct HeaderData
|
||||
{
|
||||
public GUIContent content;
|
||||
public float width;
|
||||
public float minWidth;
|
||||
public bool autoResize;
|
||||
public bool allowToggleVisibility;
|
||||
|
||||
public HeaderData(string name, string tooltip = "", float _width = 50, float _minWidth = 30, bool _autoResize = true, bool _allowToggleVisibility = true)
|
||||
{
|
||||
content = new GUIContent(name, tooltip);
|
||||
width = _width;
|
||||
minWidth = _minWidth;
|
||||
autoResize = _autoResize;
|
||||
allowToggleVisibility = _allowToggleVisibility;
|
||||
}
|
||||
}
|
||||
|
||||
public static MultiColumnHeaderState CreateDefaultMultiColumnHeaderState(float treeViewWidth)
|
||||
{
|
||||
var columnList = new List<MultiColumnHeaderState.Column>();
|
||||
HeaderData[] headerData = new HeaderData[]
|
||||
{
|
||||
new HeaderData("Group", "Thread Group", 200, 100, true, false),
|
||||
new HeaderData("Thread", "Thread Name", 350, 100, true, false),
|
||||
new HeaderData("Show", "Check to show this thread in the analysis views", 40, 100, false, false),
|
||||
};
|
||||
foreach (var header in headerData)
|
||||
{
|
||||
columnList.Add(new MultiColumnHeaderState.Column
|
||||
{
|
||||
headerContent = header.content,
|
||||
headerTextAlignment = TextAlignment.Left,
|
||||
sortedAscending = true,
|
||||
sortingArrowAlignment = TextAlignment.Left,
|
||||
width = header.width,
|
||||
minWidth = header.minWidth,
|
||||
autoResize = header.autoResize,
|
||||
allowToggleVisibility = header.allowToggleVisibility
|
||||
});
|
||||
}
|
||||
;
|
||||
var columns = columnList.ToArray();
|
||||
|
||||
Assert.AreEqual(columns.Length, Enum.GetValues(typeof(MyColumns)).Length, "Number of columns should match number of enum values: You probably forgot to update one of them.");
|
||||
|
||||
var state = new MultiColumnHeaderState(columns);
|
||||
state.visibleColumns = new int[]
|
||||
{
|
||||
(int)MyColumns.GroupName,
|
||||
(int)MyColumns.ThreadName,
|
||||
(int)MyColumns.State,
|
||||
};
|
||||
return state;
|
||||
}
|
||||
|
||||
protected override void SelectionChanged(IList<int> selectedIds)
|
||||
{
|
||||
base.SelectionChanged(selectedIds);
|
||||
|
||||
if (selectedIds.Count > 0)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class ThreadSelectionWindow : EditorWindow
|
||||
{
|
||||
ProfileAnalyzerWindow m_ProfileAnalyzerWindow;
|
||||
TreeViewState m_ThreadTreeViewState;
|
||||
//Static to store state between open/close
|
||||
static MultiColumnHeaderState m_ThreadMulticolumnHeaderState;
|
||||
ThreadTable m_ThreadTable;
|
||||
|
||||
List<string> m_ThreadNames;
|
||||
List<string> m_ThreadUINames;
|
||||
ThreadSelection m_OriginalThreadSelection;
|
||||
bool m_EnableApplyButton = false;
|
||||
bool m_EnableResetButton = false;
|
||||
bool m_RequestClose;
|
||||
|
||||
internal static class Styles
|
||||
{
|
||||
public static readonly GUIContent reset = new GUIContent("Reset", "Reset selection to previous set");
|
||||
public static readonly GUIContent clear = new GUIContent("Clear", "Clear selection below");
|
||||
public static readonly GUIContent main = new GUIContent("Main Only", "Select Main Thread only");
|
||||
public static readonly GUIContent common = new GUIContent("Common Set", "Select Common threads : Main, Render and Jobs");
|
||||
public static readonly GUIContent apply = new GUIContent("Apply", "");
|
||||
}
|
||||
|
||||
static public bool IsOpen()
|
||||
{
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
return HasOpenInstances<ThreadSelectionWindow>();
|
||||
#else
|
||||
UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(typeof(ThreadSelectionWindow));
|
||||
if (windows != null && windows.Length > 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
#endif // UNITY_2019_3_OR_NEWER
|
||||
}
|
||||
|
||||
static public ThreadSelectionWindow Open(float screenX, float screenY, ProfileAnalyzerWindow profileAnalyzerWindow, ThreadSelection threadSelection, List<string> threadNames, List<string> threadUINames)
|
||||
{
|
||||
ThreadSelectionWindow window = GetWindow<ThreadSelectionWindow>(true, "Threads");
|
||||
window.minSize = new Vector2(380, 200);
|
||||
window.position = new Rect(screenX, screenY, 500, 500);
|
||||
window.SetData(profileAnalyzerWindow, threadSelection, threadNames, threadUINames);
|
||||
window.Show();
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
m_RequestClose = false;
|
||||
}
|
||||
|
||||
void CreateTable(ProfileAnalyzerWindow profileAnalyzerWindow, List<string> threadNames, List<string> threadUINames, ThreadSelection threadSelection)
|
||||
{
|
||||
if (m_ThreadTreeViewState == null)
|
||||
m_ThreadTreeViewState = new TreeViewState();
|
||||
|
||||
int sortedColumn;
|
||||
bool sortAscending;
|
||||
if (m_ThreadMulticolumnHeaderState == null)
|
||||
{
|
||||
m_ThreadMulticolumnHeaderState = ThreadTable.CreateDefaultMultiColumnHeaderState(700);
|
||||
sortedColumn = (int)ThreadTable.MyColumns.GroupName;
|
||||
sortAscending = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remember last sort key
|
||||
sortedColumn = m_ThreadMulticolumnHeaderState.sortedColumnIndex;
|
||||
sortAscending = m_ThreadMulticolumnHeaderState.columns[sortedColumn].sortedAscending;
|
||||
}
|
||||
|
||||
var multiColumnHeader = new MultiColumnHeader(m_ThreadMulticolumnHeaderState);
|
||||
multiColumnHeader.SetSorting(sortedColumn, sortAscending);
|
||||
multiColumnHeader.ResizeToFit();
|
||||
m_ThreadTable = new ThreadTable(m_ThreadTreeViewState, multiColumnHeader, threadNames, threadUINames, threadSelection);
|
||||
}
|
||||
|
||||
void SetData(ProfileAnalyzerWindow profileAnalyzerWindow, ThreadSelection threadSelection, List<string> threadNames, List<string> threadUINames)
|
||||
{
|
||||
m_ProfileAnalyzerWindow = profileAnalyzerWindow;
|
||||
m_OriginalThreadSelection = threadSelection;
|
||||
m_ThreadNames = threadNames;
|
||||
m_ThreadUINames = threadUINames;
|
||||
CreateTable(profileAnalyzerWindow, threadNames, threadUINames, threadSelection);
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
// By design we now no longer apply the thread settings when closing the dialog.
|
||||
// Apply must be clicked to set them.
|
||||
// m_ProfileAnalyzerWindow.SetThreadSelection(m_ThreadTable.GetThreadSelection());
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true));
|
||||
GUIStyle style = new GUIStyle(GUI.skin.label);
|
||||
style.alignment = TextAnchor.MiddleLeft;
|
||||
GUILayout.Label("Select Thread : ", style);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
bool lastEnabled = GUI.enabled;
|
||||
|
||||
GUI.enabled = m_EnableResetButton;
|
||||
if (GUILayout.Button(Styles.reset, GUILayout.Width(50)))
|
||||
{
|
||||
// Reset the thread window contents only
|
||||
CreateTable(m_ProfileAnalyzerWindow, m_ThreadNames, m_ThreadUINames, m_OriginalThreadSelection);
|
||||
m_EnableApplyButton = true;
|
||||
m_EnableResetButton = false;
|
||||
}
|
||||
GUI.enabled = lastEnabled;
|
||||
|
||||
if (GUILayout.Button(Styles.clear, GUILayout.Width(50)))
|
||||
{
|
||||
m_ThreadTable.ClearThreadSelection();
|
||||
}
|
||||
|
||||
if (GUILayout.Button(Styles.main, GUILayout.Width(100)))
|
||||
{
|
||||
m_ThreadTable.SelectMain();
|
||||
}
|
||||
|
||||
if (GUILayout.Button(Styles.common, GUILayout.Width(100)))
|
||||
{
|
||||
m_ThreadTable.SelectCommon();
|
||||
}
|
||||
|
||||
GUI.enabled = m_EnableApplyButton && !m_ProfileAnalyzerWindow.IsAnalysisRunning();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (GUILayout.Button(Styles.apply, GUILayout.Width(50)))
|
||||
{
|
||||
m_ProfileAnalyzerWindow.SetThreadSelection(m_ThreadTable.GetThreadSelection());
|
||||
m_EnableApplyButton = false;
|
||||
m_EnableResetButton = true;
|
||||
}
|
||||
GUI.enabled = lastEnabled;
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (m_ThreadTable != null)
|
||||
{
|
||||
Rect r = EditorGUILayout.GetControlRect(GUILayout.ExpandHeight(true));
|
||||
m_ThreadTable.OnGUI(r);
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (m_ThreadTable != null && m_ThreadTable.StateChanged)
|
||||
{
|
||||
m_EnableApplyButton = true;
|
||||
m_EnableResetButton = true;
|
||||
}
|
||||
|
||||
if (m_RequestClose)
|
||||
Close();
|
||||
}
|
||||
|
||||
void OnLostFocus()
|
||||
{
|
||||
m_RequestClose = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b643b14ae5b894e10b5d393e31f5c347
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 60eefcdcc882eb14f933f363fc2faef6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4e7e7da63135a4cda9b821c5d96c7b4a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,863 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
internal class TopMarkers
|
||||
{
|
||||
internal class RangeSettings
|
||||
{
|
||||
public readonly ProfileDataView dataView;
|
||||
|
||||
public readonly int depthFilter;
|
||||
public readonly List<string> nameFilters;
|
||||
public readonly List<string> nameExcludes;
|
||||
public readonly TimingOptions.TimingOption timingOption;
|
||||
public readonly int threadSelectionCount;
|
||||
|
||||
public RangeSettings(ProfileDataView dataView, int depthFilter, List<string> nameFilters, List<string> nameExcludes, TimingOptions.TimingOption timingOption, int threadSelectionCount)
|
||||
{
|
||||
// Make a copy rather than keeping a reference
|
||||
this.dataView = dataView==null ? new ProfileDataView() : new ProfileDataView(dataView);
|
||||
this.depthFilter = depthFilter;
|
||||
this.nameFilters = nameFilters;
|
||||
this.nameExcludes = nameExcludes;
|
||||
this.timingOption = timingOption;
|
||||
this.threadSelectionCount = threadSelectionCount;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hash = 13;
|
||||
hash = (hash * 7) + dataView.GetHashCode();
|
||||
hash = (hash * 7) + depthFilter.GetHashCode();
|
||||
hash = (hash * 7) + nameFilters.GetHashCode();
|
||||
hash = (hash * 7) + nameExcludes.GetHashCode();
|
||||
hash = (hash * 7) + timingOption.GetHashCode();
|
||||
hash = (hash * 7) + threadSelectionCount.GetHashCode();
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
public override bool Equals(object b)
|
||||
{
|
||||
if (System.Object.ReferenceEquals(null, b))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (System.Object.ReferenceEquals(this, b))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (b.GetType() != this.GetType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return IsEqual((RangeSettings)b);
|
||||
}
|
||||
|
||||
bool IsEqual(RangeSettings b)
|
||||
{
|
||||
if (timingOption != b.timingOption)
|
||||
return false;
|
||||
|
||||
if (b.dataView == null && dataView != null)
|
||||
return false;
|
||||
|
||||
// Check contents of data view (the reference will definitly not match as we made a copy)
|
||||
if (b.dataView != null)
|
||||
{
|
||||
// Only need to check data, analysis and selectedIndices
|
||||
if (dataView.data != b.dataView.data)
|
||||
return false;
|
||||
if (dataView.analysis != b.dataView.analysis)
|
||||
return false;
|
||||
|
||||
if (dataView.selectedIndices != b.dataView.selectedIndices)
|
||||
return false;
|
||||
if (dataView.selectedIndices.Count != b.dataView.selectedIndices.Count)
|
||||
return false;
|
||||
|
||||
// Want to check if contents match, not just if reference is the same
|
||||
for (int i = 0; i < dataView.selectedIndices.Count; i++)
|
||||
{
|
||||
if (dataView.selectedIndices[i] != b.dataView.selectedIndices[i])
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (depthFilter != b.depthFilter)
|
||||
return false;
|
||||
if (threadSelectionCount != b.threadSelectionCount)
|
||||
return false;
|
||||
|
||||
if (nameFilters.Count != b.nameFilters.Count)
|
||||
return false;
|
||||
if (nameExcludes.Count != b.nameExcludes.Count)
|
||||
return false;
|
||||
|
||||
// Want to check if contents match, not just if reference is the same
|
||||
for (int i = 0; i < nameFilters.Count; i++)
|
||||
{
|
||||
if (nameFilters[i] != b.nameFilters[i])
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < nameExcludes.Count; i++)
|
||||
{
|
||||
if (nameExcludes[i] != b.nameExcludes[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool operator==(RangeSettings a, RangeSettings b)
|
||||
{
|
||||
// If both are null, or both are same instance, return true.
|
||||
if (System.Object.ReferenceEquals(a, b))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If one is null, but not both, return false.
|
||||
if (((object)a == null) || ((object)b == null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return a.IsEqual(b);
|
||||
}
|
||||
|
||||
public static bool operator!=(RangeSettings a, RangeSettings b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
}
|
||||
|
||||
internal class Settings
|
||||
{
|
||||
public readonly int barCount;
|
||||
public readonly float timeRange;
|
||||
|
||||
public readonly bool includeOthers;
|
||||
public readonly bool includeUnaccounted;
|
||||
|
||||
public RangeSettings rangeSettings { get ; private set ; }
|
||||
|
||||
public Settings(RangeSettings rangeSettings, int barCount, float timeRange, bool includeOthers, bool includeUnaccounted)
|
||||
{
|
||||
this.rangeSettings = rangeSettings;
|
||||
this.barCount = barCount;
|
||||
this.timeRange = timeRange;
|
||||
this.includeOthers = includeOthers;
|
||||
this.includeUnaccounted = includeUnaccounted;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hash = 13;
|
||||
hash = (hash * 7) + rangeSettings.GetHashCode();
|
||||
hash = (hash * 7) + barCount.GetHashCode();
|
||||
hash = (hash * 7) + timeRange.GetHashCode();
|
||||
hash = (hash * 7) + includeOthers.GetHashCode();
|
||||
hash = (hash * 7) + includeUnaccounted.GetHashCode();
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
public override bool Equals(object b)
|
||||
{
|
||||
if (System.Object.ReferenceEquals(null, b))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (System.Object.ReferenceEquals(this, b))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (b.GetType() != this.GetType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return IsEqual((Settings)b);
|
||||
}
|
||||
|
||||
bool IsEqual(Settings b)
|
||||
{
|
||||
if (rangeSettings != b.rangeSettings)
|
||||
return false;
|
||||
if (barCount != b.barCount)
|
||||
return false;
|
||||
if (timeRange != b.timeRange)
|
||||
return false;
|
||||
if (includeOthers != b.includeOthers)
|
||||
return false;
|
||||
if (includeUnaccounted != b.includeUnaccounted)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool operator==(Settings a, Settings b)
|
||||
{
|
||||
// If both are null, or both are same instance, return true.
|
||||
if (System.Object.ReferenceEquals(a, b))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If one is null, but not both, return false.
|
||||
if (((object)a == null) || ((object)b == null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return a.IsEqual(b);
|
||||
}
|
||||
|
||||
public static bool operator!=(Settings a, Settings b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
}
|
||||
|
||||
internal enum SummaryType
|
||||
{
|
||||
Marker,
|
||||
Other,
|
||||
Unaccounted
|
||||
}
|
||||
|
||||
internal struct MarkerSummaryEntry
|
||||
{
|
||||
public readonly string name;
|
||||
public readonly float msAtMedian; // At the median frame (Miliseconds)
|
||||
public readonly float msMedian; // median value for marker over all frames (Miliseconds) on frame medianFrameIndex
|
||||
public readonly float x;
|
||||
public readonly float w;
|
||||
public readonly int medianFrameIndex;
|
||||
public readonly SummaryType summaryType;
|
||||
|
||||
public MarkerSummaryEntry(string name, float msAtMedian, float msMedian, float x, float w, int medianFrameIndex, SummaryType summaryType)
|
||||
{
|
||||
this.name = name;
|
||||
this.msAtMedian = msAtMedian;
|
||||
this.msMedian = msMedian;
|
||||
this.x = x;
|
||||
this.w = w;
|
||||
this.medianFrameIndex = medianFrameIndex;
|
||||
this.summaryType = summaryType;
|
||||
}
|
||||
}
|
||||
|
||||
internal class MarkerSummary
|
||||
{
|
||||
public List<MarkerSummaryEntry> entry;
|
||||
|
||||
public float totalTime;
|
||||
|
||||
public MarkerSummary()
|
||||
{
|
||||
entry = new List<MarkerSummaryEntry>();
|
||||
totalTime = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
Settings m_CurrentSettings; // Current settings, including latest RangeSettings
|
||||
RangeSettings m_RequestedRangeSettings; // Next requested range setting set by SetData
|
||||
|
||||
float m_TimeRange;
|
||||
bool m_TimeRangeDirty; // Set when renquested range settings change
|
||||
MarkerSummary m_MarkerSummary;
|
||||
|
||||
internal static class Styles
|
||||
{
|
||||
public static readonly GUIContent menuItemSelectFramesInAll = new GUIContent("Select Frames that contain this marker (within whole data set)", "");
|
||||
public static readonly GUIContent menuItemSelectFramesInCurrent = new GUIContent("Select Frames that contain this marker (within current selection)", "");
|
||||
//public static readonly GUIContent menuItemClearSelection = new GUIContent("Clear Selection");
|
||||
public static readonly GUIContent menuItemSelectFramesAll = new GUIContent("Select All");
|
||||
public static readonly GUIContent menuItemAddToIncludeFilter = new GUIContent("Add to Include Filter", "");
|
||||
public static readonly GUIContent menuItemAddToExcludeFilter = new GUIContent("Add to Exclude Filter", "");
|
||||
public static readonly GUIContent menuItemRemoveFromIncludeFilter = new GUIContent("Remove from Include Filter", "");
|
||||
public static readonly GUIContent menuItemRemoveFromExcludeFilter = new GUIContent("Remove from Exclude Filter", "");
|
||||
public static readonly GUIContent menuItemSetAsParentMarkerFilter = new GUIContent("Set as Parent Marker Filter", "");
|
||||
public static readonly GUIContent menuItemClearParentMarkerFilter = new GUIContent("Clear Parent Marker Filter", "");
|
||||
public static readonly GUIContent menuItemCopyToClipboard = new GUIContent("Copy to Clipboard", "");
|
||||
}
|
||||
|
||||
private static class Content
|
||||
{
|
||||
public static readonly string frameTime = L10n.Tr("{0} Frame time on median frame");
|
||||
public static readonly string multipleThreads = L10n.Tr("Multiple threads selected\nSelect a single thread for an overview");
|
||||
public static readonly string totalTimeAllDepths = L10n.Tr("{0} Total time of all markers at all depths");
|
||||
public static readonly string totalTimeAtSpecificDepth = L10n.Tr("{0} Total time of all markers at Depth {1}");
|
||||
public static readonly string selectSelf = L10n.Tr("For an overview select Analysis Type Self");
|
||||
public static readonly string selectTotal = L10n.Tr("To include child times select Analysis Type Total");
|
||||
public static readonly string selfTimeAllDepths = L10n.Tr("{0} Self time of markers at all depths");
|
||||
public static readonly string selfTimeAtSpecificDepth = L10n.Tr("{0} Self time of markers at Depth {1}");
|
||||
|
||||
public static readonly string tooltip = L10n.Tr("{0}\n{1:f2}% ({2} on median frame {3})\n\nMedian marker time (in currently selected frames)\n{4} on frame {5}");
|
||||
}
|
||||
|
||||
ProfileAnalyzerWindow m_ProfileAnalyzerWindow;
|
||||
Draw2D m_2D;
|
||||
Color m_BackgroundColor;
|
||||
Color m_TextColor;
|
||||
|
||||
public TopMarkers(ProfileAnalyzerWindow profileAnalyzerWindow, Draw2D draw2D, Color backgroundColor, Color textColor)
|
||||
{
|
||||
m_ProfileAnalyzerWindow = profileAnalyzerWindow;
|
||||
m_2D = draw2D;
|
||||
m_BackgroundColor = backgroundColor;
|
||||
m_TextColor = textColor;
|
||||
|
||||
m_CurrentSettings = new Settings(new RangeSettings(null, 0, null, null, TimingOptions.TimingOption.Time, 0), 0, 0, false, false);
|
||||
m_TimeRangeDirty = true;
|
||||
}
|
||||
|
||||
string ToDisplayUnits(float ms, bool showUnits = false, int limitToDigits = 5)
|
||||
{
|
||||
return m_ProfileAnalyzerWindow.ToDisplayUnits(ms, showUnits, limitToDigits);
|
||||
}
|
||||
|
||||
public void SetData(ProfileDataView dataView, int depthFilter, List<string> nameFilters, List<string> nameExcludes, TimingOptions.TimingOption timingOption, int threadSelectionCount)
|
||||
{
|
||||
m_RequestedRangeSettings = new RangeSettings(dataView, depthFilter, nameFilters, nameExcludes, timingOption, threadSelectionCount);
|
||||
if (m_CurrentSettings.rangeSettings != m_RequestedRangeSettings)
|
||||
m_TimeRangeDirty = true;
|
||||
}
|
||||
|
||||
float CalculateTopMarkerTimeRange(RangeSettings rangeSettings)
|
||||
{
|
||||
if (rangeSettings == null)
|
||||
return 0.0f;
|
||||
if (rangeSettings.dataView == null)
|
||||
return 0.0f;
|
||||
|
||||
ProfileAnalysis analysis = rangeSettings.dataView.analysis;
|
||||
if (analysis == null)
|
||||
return 0.0f;
|
||||
|
||||
var frameSummary = analysis.GetFrameSummary();
|
||||
if (frameSummary == null)
|
||||
return 0.0f;
|
||||
|
||||
int depthFilter = rangeSettings.depthFilter;
|
||||
List<string> nameFilters = rangeSettings.nameFilters;
|
||||
List<string> nameExcludes = rangeSettings.nameExcludes;
|
||||
|
||||
var markers = analysis.GetMarkers();
|
||||
|
||||
float range = 0;
|
||||
foreach (var marker in markers)
|
||||
{
|
||||
if (depthFilter != ProfileAnalyzer.kDepthAll && marker.minDepth != depthFilter)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nameFilters.Count > 0)
|
||||
{
|
||||
if (!m_ProfileAnalyzerWindow.NameInFilterList(marker.name, nameFilters))
|
||||
continue;
|
||||
}
|
||||
if (nameExcludes.Count > 0)
|
||||
{
|
||||
if (m_ProfileAnalyzerWindow.NameInExcludeList(marker.name, nameExcludes))
|
||||
continue;
|
||||
}
|
||||
|
||||
range += marker.msAtMedian;
|
||||
}
|
||||
|
||||
// Minimum is the frame time range
|
||||
// As we can have unaccounted markers
|
||||
if (range < frameSummary.msMedian)
|
||||
range = frameSummary.msMedian;
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
public float GetTopMarkerTimeRange()
|
||||
{
|
||||
if (m_TimeRangeDirty)
|
||||
{
|
||||
Profiler.BeginSample("CalculateTopMarkerTimeRange");
|
||||
|
||||
// Use latest requested rather than current (as current may not yet be updated)
|
||||
m_TimeRange = CalculateTopMarkerTimeRange(m_RequestedRangeSettings);
|
||||
m_TimeRangeDirty = false;
|
||||
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
return m_TimeRange;
|
||||
}
|
||||
|
||||
public MarkerSummary CalculateTopMarkers()
|
||||
{
|
||||
if (m_CurrentSettings.rangeSettings.dataView == null)
|
||||
return null;
|
||||
|
||||
ProfileAnalysis analysis = m_CurrentSettings.rangeSettings.dataView.analysis;
|
||||
if (analysis == null)
|
||||
return null;
|
||||
|
||||
FrameSummary frameSummary = analysis.GetFrameSummary();
|
||||
if (frameSummary == null)
|
||||
return new MarkerSummary();
|
||||
|
||||
var markers = analysis.GetMarkers();
|
||||
if (markers == null)
|
||||
return new MarkerSummary();
|
||||
|
||||
float timeRange = m_CurrentSettings.timeRange;
|
||||
int depthFilter = m_CurrentSettings.rangeSettings.depthFilter;
|
||||
List<string> nameFilters = m_CurrentSettings.rangeSettings.nameFilters;
|
||||
List<string> nameExcludes = m_CurrentSettings.rangeSettings.nameExcludes;
|
||||
|
||||
// Show marker graph
|
||||
float x = 0;
|
||||
float width = 1.0f;
|
||||
|
||||
int max = m_CurrentSettings.barCount;
|
||||
int at = 0;
|
||||
|
||||
float other = 0.0f;
|
||||
|
||||
if (timeRange <= 0.0f)
|
||||
timeRange = frameSummary.msMedian;
|
||||
|
||||
float msToWidth = width / timeRange;
|
||||
|
||||
float totalMarkerTime = 0;
|
||||
|
||||
MarkerSummary markerSummary = new MarkerSummary();
|
||||
|
||||
foreach (var marker in markers)
|
||||
{
|
||||
float msAtMedian = MarkerData.GetMsAtMedian(marker);
|
||||
totalMarkerTime += msAtMedian;
|
||||
|
||||
if (depthFilter != ProfileAnalyzer.kDepthAll && marker.minDepth != depthFilter)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nameFilters.Count > 0)
|
||||
{
|
||||
if (!m_ProfileAnalyzerWindow.NameInFilterList(marker.name, nameFilters))
|
||||
continue;
|
||||
}
|
||||
if (nameExcludes.Count > 0)
|
||||
{
|
||||
if (m_ProfileAnalyzerWindow.NameInExcludeList(marker.name, nameExcludes))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (at < max)
|
||||
{
|
||||
float w = CaculateWidth(x, msAtMedian, msToWidth, width);
|
||||
float msMedian = MarkerData.GetMsMedian(marker);
|
||||
int medianFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(marker.medianFrameIndex, m_CurrentSettings.rangeSettings.dataView);
|
||||
markerSummary.entry.Add(new MarkerSummaryEntry(marker.name, msAtMedian, msMedian, x, w, medianFrameIndex, SummaryType.Marker));
|
||||
|
||||
x += w;
|
||||
}
|
||||
else
|
||||
{
|
||||
other += msAtMedian;
|
||||
if (!m_CurrentSettings.includeOthers)
|
||||
break;
|
||||
}
|
||||
|
||||
at++;
|
||||
}
|
||||
|
||||
if (m_CurrentSettings.includeOthers && other > 0.0f)
|
||||
{
|
||||
float w = CaculateWidth(x, other, msToWidth, width);
|
||||
markerSummary.entry.Add(new MarkerSummaryEntry("Other", other, 0f, x, w, -1, SummaryType.Other));
|
||||
x += w;
|
||||
}
|
||||
if (m_CurrentSettings.includeUnaccounted && totalMarkerTime < frameSummary.msMedian)
|
||||
{
|
||||
float unaccounted = frameSummary.msMedian - totalMarkerTime;
|
||||
float w = CaculateWidth(x, unaccounted, msToWidth, width);
|
||||
markerSummary.entry.Add(new MarkerSummaryEntry("Unaccounted", unaccounted, 0f, x, w, -1, SummaryType.Unaccounted));
|
||||
x += w;
|
||||
}
|
||||
|
||||
markerSummary.totalTime = totalMarkerTime;
|
||||
|
||||
return markerSummary;
|
||||
}
|
||||
|
||||
GenericMenu GenerateActiveContextMenu(string markerName, Event evt)
|
||||
{
|
||||
GenericMenu menu = new GenericMenu();
|
||||
menu.AddItem(Styles.menuItemSelectFramesInAll, false, () => m_ProfileAnalyzerWindow.SelectFramesContainingMarker(markerName, false));
|
||||
menu.AddItem(Styles.menuItemSelectFramesInCurrent, false, () => m_ProfileAnalyzerWindow.SelectFramesContainingMarker(markerName, true));
|
||||
|
||||
if (m_ProfileAnalyzerWindow.AllSelected())
|
||||
menu.AddDisabledItem(Styles.menuItemSelectFramesAll);
|
||||
else
|
||||
menu.AddItem(Styles.menuItemSelectFramesAll, false, () => m_ProfileAnalyzerWindow.SelectAllFrames());
|
||||
|
||||
menu.AddSeparator("");
|
||||
if (!m_CurrentSettings.rangeSettings.nameFilters.Contains(markerName))
|
||||
menu.AddItem(Styles.menuItemAddToIncludeFilter, false, () => m_ProfileAnalyzerWindow.AddToIncludeFilter(markerName));
|
||||
else
|
||||
menu.AddItem(Styles.menuItemRemoveFromIncludeFilter, false, () => m_ProfileAnalyzerWindow.RemoveFromIncludeFilter(markerName));
|
||||
if (!m_CurrentSettings.rangeSettings.nameExcludes.Contains(markerName))
|
||||
menu.AddItem(Styles.menuItemAddToExcludeFilter, false, () => m_ProfileAnalyzerWindow.AddToExcludeFilter(markerName));
|
||||
else
|
||||
menu.AddItem(Styles.menuItemRemoveFromExcludeFilter, false, () => m_ProfileAnalyzerWindow.RemoveFromExcludeFilter(markerName));
|
||||
menu.AddSeparator("");
|
||||
menu.AddItem(Styles.menuItemSetAsParentMarkerFilter, false, () => m_ProfileAnalyzerWindow.SetAsParentMarkerFilter(markerName));
|
||||
menu.AddItem(Styles.menuItemClearParentMarkerFilter, false, () => m_ProfileAnalyzerWindow.SetAsParentMarkerFilter(""));
|
||||
menu.AddSeparator("");
|
||||
menu.AddItem(Styles.menuItemCopyToClipboard, false, () => CopyToClipboard(evt, markerName));
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
GenericMenu GenerateDisabledContextMenu(string markerName)
|
||||
{
|
||||
GenericMenu menu = new GenericMenu();
|
||||
menu.AddDisabledItem(Styles.menuItemSelectFramesInAll);
|
||||
menu.AddDisabledItem(Styles.menuItemSelectFramesInCurrent);
|
||||
menu.AddDisabledItem(Styles.menuItemSelectFramesAll);
|
||||
|
||||
menu.AddSeparator("");
|
||||
if (!m_CurrentSettings.rangeSettings.nameFilters.Contains(markerName))
|
||||
menu.AddDisabledItem(Styles.menuItemAddToIncludeFilter);
|
||||
else
|
||||
menu.AddDisabledItem(Styles.menuItemRemoveFromIncludeFilter);
|
||||
if (!m_CurrentSettings.rangeSettings.nameExcludes.Contains(markerName))
|
||||
menu.AddDisabledItem(Styles.menuItemAddToExcludeFilter);
|
||||
else
|
||||
menu.AddDisabledItem(Styles.menuItemRemoveFromExcludeFilter);
|
||||
|
||||
menu.AddSeparator("");
|
||||
menu.AddDisabledItem(Styles.menuItemSetAsParentMarkerFilter);
|
||||
menu.AddDisabledItem(Styles.menuItemClearParentMarkerFilter);
|
||||
menu.AddSeparator("");
|
||||
menu.AddDisabledItem(Styles.menuItemCopyToClipboard);
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
public GUIContent ConstructTimeRangeText()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
ProfileAnalysis analysis = m_CurrentSettings.rangeSettings.dataView.analysis;
|
||||
int depthFilter = m_CurrentSettings.rangeSettings.depthFilter;
|
||||
|
||||
FrameSummary frameSummary = analysis.GetFrameSummary();
|
||||
|
||||
string frameTimeString = ToDisplayUnits(frameSummary.msMedian, true, 0);
|
||||
string accountedTimeString = ToDisplayUnits(m_MarkerSummary.totalTime, true, 0);
|
||||
sb.AppendFormat(Content.frameTime, frameTimeString);
|
||||
|
||||
// Note m_CurrentSettings.rangeSettings.dataView.analysis.GetThreads contains all thread names, not just the filtered threads
|
||||
bool singleThread = m_CurrentSettings.rangeSettings.threadSelectionCount == 1;
|
||||
|
||||
if (depthFilter == ProfileAnalyzer.kDepthAll)
|
||||
{
|
||||
if (m_CurrentSettings.rangeSettings.timingOption == TimingOptions.TimingOption.Time)
|
||||
{
|
||||
sb.Append("\n");
|
||||
sb.AppendFormat(Content.totalTimeAllDepths, accountedTimeString);
|
||||
if (singleThread)
|
||||
{
|
||||
sb.Append("\n\n");
|
||||
sb.Append(Content.selectSelf);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append("\n");
|
||||
sb.AppendFormat(Content.selfTimeAllDepths,accountedTimeString);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_CurrentSettings.rangeSettings.timingOption == TimingOptions.TimingOption.Self)
|
||||
{
|
||||
sb.Append("\n");
|
||||
sb.AppendFormat(Content.selfTimeAtSpecificDepth,accountedTimeString, depthFilter);
|
||||
if (singleThread)
|
||||
{
|
||||
sb.Append("\n\n");
|
||||
sb.Append(Content.selectTotal);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append("\n");
|
||||
sb.AppendFormat(Content.totalTimeAtSpecificDepth, accountedTimeString, depthFilter);
|
||||
}
|
||||
}
|
||||
|
||||
if (!singleThread)
|
||||
{
|
||||
sb.Append("\n\n");
|
||||
sb.Append(Content.multipleThreads);
|
||||
}
|
||||
|
||||
string timeRangeString = ToDisplayUnits(m_CurrentSettings.timeRange, true);
|
||||
return new GUIContent(timeRangeString, sb.ToString());
|
||||
}
|
||||
|
||||
public void Draw(Rect rect, Color barColor, int barCount, float timeRange, Color selectedBackground, Color selectedBorder, Color selectedText, bool includeOthers, bool includeUnaccounted)
|
||||
{
|
||||
Settings newSettings = new Settings(m_RequestedRangeSettings, barCount, timeRange, includeOthers, includeUnaccounted);
|
||||
if (m_CurrentSettings != newSettings)
|
||||
{
|
||||
Profiler.BeginSample("CalculateTopMarkers");
|
||||
|
||||
m_CurrentSettings = newSettings;
|
||||
m_MarkerSummary = CalculateTopMarkers();
|
||||
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
if (m_CurrentSettings.rangeSettings == null)
|
||||
return;
|
||||
if (m_CurrentSettings.rangeSettings.dataView == null)
|
||||
return;
|
||||
if (m_CurrentSettings.rangeSettings.dataView.analysis == null)
|
||||
return;
|
||||
|
||||
if (m_MarkerSummary == null || m_MarkerSummary.entry == null)
|
||||
return;
|
||||
|
||||
ProfileAnalysis analysis = m_CurrentSettings.rangeSettings.dataView.analysis;
|
||||
|
||||
FrameSummary frameSummary = analysis.GetFrameSummary();
|
||||
if (frameSummary == null)
|
||||
return;
|
||||
if (frameSummary.count <= 0)
|
||||
return;
|
||||
|
||||
var markers = analysis.GetMarkers();
|
||||
if (markers == null)
|
||||
return;
|
||||
|
||||
Profiler.BeginSample("DrawHeader");
|
||||
|
||||
int rangeLabelWidth = 60;
|
||||
|
||||
// After the marker graph we want an indication of the time range
|
||||
if (frameSummary.count > 0)
|
||||
{
|
||||
Rect rangeLabelRect = new Rect(rect.x + rect.width - rangeLabelWidth, rect.y, rangeLabelWidth, rect.height);
|
||||
GUIContent timeRangeText = ConstructTimeRangeText();
|
||||
GUI.Label(rangeLabelRect, timeRangeText);
|
||||
}
|
||||
|
||||
// Reduce the size of the marker graph for the button/label we just added
|
||||
rect.width -= rangeLabelWidth;
|
||||
|
||||
// Show marker graph
|
||||
float y = 0;
|
||||
float width = rect.width;
|
||||
float height = rect.height;
|
||||
|
||||
var selectedPairingMarkerName = m_ProfileAnalyzerWindow.GetSelectedMarkerName();
|
||||
|
||||
if (timeRange <= 0.0f)
|
||||
timeRange = frameSummary.msMedian;
|
||||
|
||||
Profiler.EndSample();
|
||||
|
||||
if (m_2D.DrawStart(rect, Draw2D.Origin.BottomLeft))
|
||||
{
|
||||
Profiler.BeginSample("DrawBars");
|
||||
|
||||
m_2D.DrawFilledBox(0, y, width, height, m_BackgroundColor);
|
||||
|
||||
foreach (MarkerSummaryEntry entry in m_MarkerSummary.entry)
|
||||
{
|
||||
String name = entry.name;
|
||||
|
||||
float x = entry.x * width;
|
||||
float w = entry.w * width;
|
||||
if (entry.summaryType == SummaryType.Marker)
|
||||
{
|
||||
if (name == selectedPairingMarkerName)
|
||||
{
|
||||
DrawBar(x, y, w, height, selectedBackground, selectedBorder, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawBar(x, y, w, height, barColor, selectedBorder, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Others / Unaccounted
|
||||
Color color = entry.summaryType == SummaryType.Unaccounted ? new Color(barColor.r * 0.5f, barColor.g * 0.5f, barColor.b * 0.5f, barColor.a) : barColor;
|
||||
|
||||
DrawBar(x, y, w, height, color, selectedBorder, false);
|
||||
}
|
||||
}
|
||||
|
||||
Profiler.EndSample();
|
||||
|
||||
m_2D.DrawEnd();
|
||||
}
|
||||
|
||||
GUIStyle centreAlignStyle = new GUIStyle(GUI.skin.label);
|
||||
centreAlignStyle.alignment = TextAnchor.MiddleCenter;
|
||||
centreAlignStyle.normal.textColor = m_TextColor;
|
||||
GUIStyle leftAlignStyle = new GUIStyle(GUI.skin.label);
|
||||
leftAlignStyle.alignment = TextAnchor.MiddleLeft;
|
||||
leftAlignStyle.normal.textColor = m_TextColor;
|
||||
Color contentColor = GUI.contentColor;
|
||||
|
||||
int frameSummaryMedianFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(frameSummary.medianFrameIndex, m_CurrentSettings.rangeSettings.dataView);
|
||||
|
||||
Profiler.BeginSample("DrawText");
|
||||
foreach (MarkerSummaryEntry entry in m_MarkerSummary.entry)
|
||||
{
|
||||
String name = entry.name;
|
||||
|
||||
float x = entry.x * width;
|
||||
float w = entry.w * width;
|
||||
float msAtMedian = entry.msAtMedian;
|
||||
|
||||
if (entry.summaryType == SummaryType.Marker)
|
||||
{
|
||||
Rect labelRect = new Rect(rect.x + x, rect.y, w, rect.height);
|
||||
GUIStyle style = centreAlignStyle;
|
||||
String displayName = "";
|
||||
if (w >= 20)
|
||||
{
|
||||
displayName = name;
|
||||
Vector2 size = centreAlignStyle.CalcSize(new GUIContent(name));
|
||||
if (size.x > w)
|
||||
{
|
||||
var words = name.Split('.');
|
||||
displayName = words[words.Length - 1];
|
||||
style = leftAlignStyle;
|
||||
}
|
||||
}
|
||||
float percentAtMedian = msAtMedian * 100 / timeRange;
|
||||
string tooltip = string.Format(
|
||||
Content.tooltip,
|
||||
name,
|
||||
percentAtMedian, ToDisplayUnits(msAtMedian, true, 0), frameSummaryMedianFrameIndex,
|
||||
ToDisplayUnits(entry.msMedian, true, 0), entry.medianFrameIndex);
|
||||
if (name == selectedPairingMarkerName)
|
||||
style.normal.textColor = selectedText;
|
||||
else
|
||||
style.normal.textColor = m_TextColor;
|
||||
GUI.Label(labelRect, new GUIContent(displayName, tooltip), style);
|
||||
|
||||
Event current = Event.current;
|
||||
if (labelRect.Contains(current.mousePosition))
|
||||
{
|
||||
if (current.type == EventType.ContextClick)
|
||||
{
|
||||
GenericMenu menu;
|
||||
if (!m_ProfileAnalyzerWindow.IsAnalysisRunning())
|
||||
menu = GenerateActiveContextMenu(name, current);
|
||||
else
|
||||
menu = GenerateDisabledContextMenu(name);
|
||||
|
||||
menu.ShowAsContext();
|
||||
|
||||
current.Use();
|
||||
}
|
||||
if (current.type == EventType.MouseDown)
|
||||
{
|
||||
m_ProfileAnalyzerWindow.SelectMarker(name);
|
||||
m_ProfileAnalyzerWindow.RequestRepaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawBarText(rect, x, w, msAtMedian, name, timeRange, leftAlignStyle, frameSummaryMedianFrameIndex);
|
||||
}
|
||||
}
|
||||
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
static float CaculateWidth(float x, float msTime, float msToWidth, float width)
|
||||
{
|
||||
float w = msTime * msToWidth;
|
||||
if (x + w > width)
|
||||
w = width - x;
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
float DrawBar(float x, float y, float w, float height, Color barColor, Color selectedBorder, bool withBorder)
|
||||
{
|
||||
if (withBorder)
|
||||
m_2D.DrawFilledBox(x + 1, y + 1, w, height - 2, selectedBorder);
|
||||
|
||||
m_2D.DrawFilledBox(x + 2, y + 2, w - 2, height - 4, barColor);
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
float DrawBarText(Rect rect, float x, float w, float msTime, string name, float timeRange, GUIStyle leftAlignStyle, int medianFrameIndex)
|
||||
{
|
||||
float width = rect.width;
|
||||
Rect labelRect = new Rect(rect.x + x, rect.y, w, rect.height);
|
||||
float percent = msTime / timeRange * 100;
|
||||
GUIStyle style = leftAlignStyle;
|
||||
string tooltip = string.Format("{0}\n{1:f2}% ({2} on median frame {3})",
|
||||
name,
|
||||
percent,
|
||||
ToDisplayUnits(msTime, true, 0),
|
||||
medianFrameIndex);
|
||||
GUI.Label(labelRect, new GUIContent("", tooltip), style);
|
||||
|
||||
Event current = Event.current;
|
||||
if (labelRect.Contains(current.mousePosition))
|
||||
{
|
||||
if (current.type == EventType.ContextClick)
|
||||
{
|
||||
GenericMenu menu = new GenericMenu();
|
||||
|
||||
if (!m_ProfileAnalyzerWindow.IsAnalysisRunning())
|
||||
menu.AddItem(Styles.menuItemSelectFramesAll, false, m_ProfileAnalyzerWindow.SelectAllFrames);
|
||||
else
|
||||
menu.AddDisabledItem(Styles.menuItemSelectFramesAll);
|
||||
|
||||
menu.ShowAsContext();
|
||||
|
||||
current.Use();
|
||||
}
|
||||
if (current.type == EventType.MouseDown)
|
||||
{
|
||||
m_ProfileAnalyzerWindow.SelectMarker(null);
|
||||
m_ProfileAnalyzerWindow.RequestRepaint();
|
||||
}
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
void CopyToClipboard(Event current, string text)
|
||||
{
|
||||
EditorGUIUtility.systemCopyBuffer = text;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 406c1c524495d4e0283f7848bbbfedc9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,236 @@
|
|||
using System;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
/// <summary>Unit type identifier</summary>
|
||||
internal enum Units
|
||||
{
|
||||
/// <summary>Time in milliseconds</summary>
|
||||
Milliseconds,
|
||||
/// <summary>Time in microseconds</summary>
|
||||
Microseconds,
|
||||
/// <summary>Count of number of instances</summary>
|
||||
Count,
|
||||
};
|
||||
|
||||
internal class DisplayUnits
|
||||
{
|
||||
public static readonly string[] UnitNames =
|
||||
{
|
||||
"Milliseconds",
|
||||
"Microseconds",
|
||||
"Count",
|
||||
};
|
||||
|
||||
public static readonly int[] UnitValues = (int[])Enum.GetValues(typeof(Units));
|
||||
|
||||
public readonly Units Units;
|
||||
|
||||
const bool kShowFullValueWhenBelowZero = true;
|
||||
const int kTooltipDigitsNumber = 7;
|
||||
|
||||
public DisplayUnits(Units units)
|
||||
{
|
||||
Assert.AreEqual(UnitNames.Length, UnitValues.Length, "Number of UnitNames should match number of enum values UnitValues: You probably forgot to update one of them.");
|
||||
|
||||
Units = units;
|
||||
}
|
||||
|
||||
public string Postfix()
|
||||
{
|
||||
switch (Units)
|
||||
{
|
||||
default:
|
||||
case Units.Milliseconds:
|
||||
return "ms";
|
||||
case Units.Microseconds:
|
||||
return "us";
|
||||
case Units.Count:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
int ClampToRange(int value, int min, int max)
|
||||
{
|
||||
if (value < min)
|
||||
value = min;
|
||||
if (value > max)
|
||||
value = max;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
string RemoveTrailingZeros(string numberStr, int minNumberStringLength)
|
||||
{
|
||||
// Find out string length without trailing zeroes.
|
||||
var strLenWithoutTrailingZeros = numberStr.Length;
|
||||
while (strLenWithoutTrailingZeros > minNumberStringLength && numberStr[strLenWithoutTrailingZeros - 1] == '0')
|
||||
strLenWithoutTrailingZeros--;
|
||||
|
||||
// Remove hanging '.' in case all zeroes can be omitted.
|
||||
if (strLenWithoutTrailingZeros > 0 && numberStr[strLenWithoutTrailingZeros - 1] == '.')
|
||||
strLenWithoutTrailingZeros--;
|
||||
|
||||
|
||||
return strLenWithoutTrailingZeros == numberStr.Length ? numberStr : numberStr.Substring(0, strLenWithoutTrailingZeros);
|
||||
}
|
||||
|
||||
public string ToString(float ms, bool showUnits, int limitToNDigits, bool showFullValueWhenBelowZero = false)
|
||||
{
|
||||
float value = ms;
|
||||
int unitPower = -3;
|
||||
|
||||
int minNumberStringLength = -1;
|
||||
int maxDecimalPlaces = 0;
|
||||
float minValueShownWhenUsingLimitedDecimalPlaces = 1f;
|
||||
switch (Units)
|
||||
{
|
||||
default:
|
||||
case Units.Milliseconds:
|
||||
maxDecimalPlaces = 2;
|
||||
minValueShownWhenUsingLimitedDecimalPlaces = 0.01f;
|
||||
break;
|
||||
case Units.Microseconds:
|
||||
value *= 1000f;
|
||||
unitPower -= 3;
|
||||
if (value < 100)
|
||||
{
|
||||
maxDecimalPlaces = 1;
|
||||
minValueShownWhenUsingLimitedDecimalPlaces = 0.1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
maxDecimalPlaces = 0;
|
||||
minValueShownWhenUsingLimitedDecimalPlaces = 1f;
|
||||
}
|
||||
break;
|
||||
case Units.Count:
|
||||
showUnits = false;
|
||||
break;
|
||||
}
|
||||
|
||||
int sgn = Math.Sign(value);
|
||||
if (value < 0)
|
||||
value = -value;
|
||||
int numberOfDecimalPlaces = maxDecimalPlaces;
|
||||
int unitsTextLength = showUnits ? 2 : 0;
|
||||
int signTextLength = sgn == -1 ? 1 : 0;
|
||||
if (limitToNDigits > 0 && value > float.Epsilon)
|
||||
{
|
||||
int numberOfSignificantFigures = limitToNDigits;
|
||||
if (!showFullValueWhenBelowZero)
|
||||
numberOfSignificantFigures -= unitsTextLength + signTextLength;
|
||||
|
||||
int valueExp = (int)Math.Log10(value);
|
||||
// Less than 1 values needs exponent correction as (int) rounds to the upper negative.
|
||||
if (value < 1)
|
||||
valueExp -= 1;
|
||||
|
||||
int originalUnitPower = unitPower;
|
||||
float limitRange = (float)Math.Pow(10, numberOfSignificantFigures);
|
||||
if (limitRange > 0)
|
||||
{
|
||||
if (value >= limitRange)
|
||||
{
|
||||
while (value >= 1000f && unitPower < 9)
|
||||
{
|
||||
value /= 1000f;
|
||||
unitPower += 3;
|
||||
valueExp -= 3;
|
||||
}
|
||||
}
|
||||
else if (showFullValueWhenBelowZero) // Only upscale and change unit type if we want to see exact number.
|
||||
{
|
||||
while (value < 0.01f && unitPower > -9)
|
||||
{
|
||||
value *= 1000f;
|
||||
unitPower -= 3;
|
||||
valueExp += 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (unitPower != originalUnitPower)
|
||||
{
|
||||
showUnits = true;
|
||||
unitsTextLength = 2;
|
||||
numberOfSignificantFigures = limitToNDigits;
|
||||
if (!showFullValueWhenBelowZero)
|
||||
numberOfSignificantFigures -= unitsTextLength + signTextLength;
|
||||
}
|
||||
|
||||
// Use all allowed digits to display significant digits if we have any beyond maxDecimalPlaces
|
||||
int numberOfDigitsBeforeDecimalPoint = 1 + Math.Max(0, valueExp);
|
||||
if (showFullValueWhenBelowZero)
|
||||
{
|
||||
numberOfDecimalPlaces = numberOfSignificantFigures - numberOfDigitsBeforeDecimalPoint;
|
||||
minNumberStringLength = numberOfDigitsBeforeDecimalPoint + signTextLength + maxDecimalPlaces + 1;
|
||||
}
|
||||
else
|
||||
numberOfDecimalPlaces = ClampToRange(numberOfSignificantFigures - numberOfDigitsBeforeDecimalPoint, 0, maxDecimalPlaces);
|
||||
}
|
||||
|
||||
value *= sgn;
|
||||
|
||||
string numberStr;
|
||||
if (value < minValueShownWhenUsingLimitedDecimalPlaces && showFullValueWhenBelowZero)
|
||||
{
|
||||
numberStr = string.Format("{0}", value);
|
||||
}
|
||||
else
|
||||
{
|
||||
string formatString = string.Concat("{0:f", numberOfDecimalPlaces, "}");
|
||||
numberStr = string.Format(formatString, value);
|
||||
}
|
||||
|
||||
// Remove trailing 0 if any from string
|
||||
if (minNumberStringLength > 0 && numberStr.Length > 0)
|
||||
numberStr = RemoveTrailingZeros(numberStr, minNumberStringLength);
|
||||
|
||||
if (!showUnits)
|
||||
return numberStr;
|
||||
|
||||
string siUnitString = GetSIUnitString(unitPower) + "s";
|
||||
return string.Concat(numberStr, siUnitString);
|
||||
}
|
||||
|
||||
public static string GetSIUnitString(int unitPower)
|
||||
{
|
||||
// https://en.wikipedia.org/wiki/Metric_prefix
|
||||
switch (unitPower)
|
||||
{
|
||||
case -9:
|
||||
return "n";
|
||||
case -6:
|
||||
return "u";
|
||||
case -3:
|
||||
return "m";
|
||||
case 0:
|
||||
return "";
|
||||
case 3:
|
||||
return "k";
|
||||
case 6:
|
||||
return "M";
|
||||
case 9:
|
||||
return "G";
|
||||
}
|
||||
|
||||
return "?";
|
||||
}
|
||||
|
||||
public string ToTooltipString(double ms, bool showUnits, int frameIndex = -1)
|
||||
{
|
||||
if (frameIndex >= 0)
|
||||
return string.Format("{0} on frame {1}", ToString((float)ms, showUnits, kTooltipDigitsNumber, kShowFullValueWhenBelowZero), frameIndex);
|
||||
|
||||
return ToString((float)ms, showUnits, kTooltipDigitsNumber, kShowFullValueWhenBelowZero);
|
||||
}
|
||||
|
||||
public GUIContent ToGUIContentWithTooltips(float ms, bool showUnits = false, int limitToNDigits = 5, int frameIndex = -1)
|
||||
{
|
||||
return new GUIContent(ToString(ms, showUnits, limitToNDigits), ToTooltipString(ms, true, frameIndex));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0a92e3733078e4ca68373f1d6c83f584
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "Unity.Performance.Profile-Analyzer.Editor",
|
||||
"references": [],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 211d499e772b640dbb43b9de9b957156
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Add table
Add a link
Reference in a new issue