You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5917 lines
255 KiB
C#

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