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, }; /// /// Main profile Analyzer UI window /// 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 m_ThreadUINames = new List(); List m_ThreadNames = new List(); Dictionary m_ThreadNameToUIName; GUIContent[] m_removeMarkerDisplay = null; int[] m_removeMarkerValues = null; bool m_removeMarkerSomeMissing = false; struct MarkerFilter { public Dictionary MarkerCache; public bool NeedsRebuild; public List IncludeFilter; public List 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 m_PairingsNew; int m_TotalCombinedMarkerCountNew; [SerializeField] List m_Pairings = new List(); 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("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; } /// /// Open profile analyzer window /// 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(), 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 values = new List(); List strings = new List(); 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 unitNames = new List(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 GetFrameTimeData(ProfileData profileData) { List data = new List(); 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 leftMarkers = left.GetMarkerNames(); if (leftMarkers == null) return 0; List rightMarkers = right.GetMarkerNames(); if (rightMarkers == null) return 0; HashSet markerPairs = new HashSet(); 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 GeneratePairings(ProfileAnalysis leftAnalysis, ProfileAnalysis rightAnalysis) { if (leftAnalysis == null) return null; if (rightAnalysis == null) return null; List leftMarkers = leftAnalysis.GetMarkers(); if (leftMarkers == null) return null; List rightMarkers = rightAnalysis.GetMarkers(); if (rightMarkers == null) return null; Dictionary markerPairs = new Dictionary(); 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 pairings = new List(); 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 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 threadUINamesNew; List threadNamesNew; Dictionary threadNameToUINameNew; GetThreadNames(m_ProfileLeftView.data, m_ProfileRightView.data, out threadUINamesNew, out threadNamesNew, out threadNameToUINameNew); List 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 selection = new List(); 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 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 threadUINamesNew; List threadNamesNew; Dictionary threadNameToUINameNew; GetThreadNames(m_ProfileSingleView.data, out threadUINamesNew, out threadNamesNew, out threadNameToUINameNew); List 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 selection = new List(); 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 threadUINames, out List threadFilters, out Dictionary 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 threadUINames, out List threadFilters, out Dictionary threadNameToUIName) { List threadNames = (leftData != null) ? new List(leftData.GetThreadNames()) : new List(); 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 threadNamesDict = new Dictionary(); 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 uiNames = new List(); foreach (string uiName in threadNamesDict.Keys) uiNames.Add(uiName); uiNames.Sort(CompareUINames); var allThreadIdentifier = new ThreadIdentifier(); allThreadIdentifier.SetName("All"); allThreadIdentifier.SetAll(); threadUINames = new List(); threadFilters = new List(); threadNameToUIName = new Dictionary(); 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 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 GetLimitedThreadSelection(List threadNames, ThreadSelection threadSelection) { List limitedThreadSelection = new List(); 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 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 GetNameFilters(string nameFilter) { List nameFilters = new List(); 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 GetNameFilters() { return GetNameFilters(m_NameFilter); } internal List GetNameExcludes() { return GetNameFilters(m_NameExclude); } internal bool DoesMarkerPassFilter(string name) { return m_MarkerFilter.DoesMarkerPassFilter(this, name); } internal bool NameInIncludeList(string name, List nameFilters) { return NameInFilterList(name, nameFilters, m_NameFilterOperation); } internal bool NameInExcludeList(string name, List nameExcludes) { return NameInFilterList(name, nameExcludes, m_NameExcludeOperation); } static bool NameInFilterList(string name, List 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 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 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 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 leftMarkers = m_ProfileLeftView.analysis.GetMarkers(); List 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 selectedOffsets = new List(); 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(); var threadSelectionDict = new Dictionary(); 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 threads = new List(); 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(); 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 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 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(); var values = new List(); 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 selectedOffsets, int clickCount, FrameTimeGraph.State inputStatus, ProfileDataView mainData, List 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 selectedOffsets, int clickCount, FrameTimeGraph.State inputStatus) { SetRange(selectedOffsets, clickCount, inputStatus, m_ProfileLeftView, m_ProfileLeftView.selectedIndices); } void SetRightRange(List 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 selectedOffsets = new List(); 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 selectedOffsets = new List(); 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 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 leftMarkers = m_ProfileLeftView.analysis.GetMarkers(); List 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 markers, string name) { int index = 0; foreach (var marker in markers) { if (marker.name == name) return index; index++; } return -1; // not found } /// /// Select marker to focus on /// /// Name of the marker // 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 selection = new List(); 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); } /// /// Get currently selected marker /// /// Name of currently selected marker, or null if none selected 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 selection = new List(); 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 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(); } /// /// 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 /// /// target frame index /// capture frameIndex offset /// 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 leftFrames = leftMarker != null ? topMarkerList.GetTopN(leftMarker, m_TopNumber, showCount) : new List(); List rightFrames = rightMarker != null ? topMarkerList.GetTopN(rightMarker, m_TopNumber, showCount) : new List(); 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 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); } } }