Created unity project
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"timestamp": 1675161311,
|
||||
"signature": "PEpNzq21eTCgLjQ/nEIaajhdWMLm9NyFBZAqEptbkFU7tScLJ6iktv3wdrk5LYRiwe/sHYaDzY3OthZrYg/q1ia7D2sj9+FjxXK/Gzt/Wo63sNFQCm/gtketSrJqpS+iXkoPavjmw4NlexIxiVclOM9aAfQJg6QrY/zfqEb45hwBCCPln85CSg8+dFMEv926C3pXaj3GCB0DUk/rUPd9z1XrLjavk37L28jogkjTmTdw1Yq/MrvKFg860iLGNvRpSAAAPJmcr38bohtjT5xHUQxXbhJ9nUurGSLJ7pR5bqjcJlqcJ8Yt2VVMCOENtPM8LeLtXnV4Q4EdmHpAPI7+4uuznt4nWUCcnJ7EXWwdBOdBRFJrK4lNoNU1C4dpIThvFK73TRbUyrk9e8lh792qFR8+WiuIgbutIgQI05RopegyJY7q6W/gZREfiahv6nHXE9XlxdewhwHvgxwDoevv6/nXb74WoUMw85i8zmZBKocaAx0w37JL/PKPl+kr97Vg",
|
||||
"publicKey": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQm9qQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FZOEFNSUlCaWdLQ0FZRUFzdUhXYUhsZ0I1cVF4ZEJjTlJKSAordHR4SmoxcVY1NTdvMlZaRE1XaXhYRVBkRTBEMVFkT1JIRXNSS1RscmplUXlERU83ZlNQS0ZwZ1A3MU5TTnJCCkFHM2NFSU45aHNQVDhOVmllZmdWem5QTkVMenFkVmdEbFhpb2VpUnV6OERKWFgvblpmU1JWKytwbk9ySTRibG4KS0twelJlNW14OTc1SjhxZ1FvRktKT0NNRlpHdkJMR2MxSzZZaEIzOHJFODZCZzgzbUovWjBEYkVmQjBxZm13cgo2ZDVFUXFsd0E5Y3JZT1YyV1VpWXprSnBLNmJZNzRZNmM1TmpBcEFKeGNiaTFOaDlRVEhUcU44N0ZtMDF0R1ZwCjVNd1pXSWZuYVRUemEvTGZLelR5U0pka0tldEZMVGdkYXpMYlpzUEE2aHBSK0FJRTJhc0tLTi84UUk1N3UzU2cKL2xyMnZKS1IvU2l5eEN1Q20vQWJkYnJMbXk0WjlSdm1jMGdpclA4T0lLQWxBRWZ2TzV5Z2hSKy8vd1RpTFlzUQp1SllDM0V2UE16ZGdKUzdGR2FscnFLZzlPTCsxVzROY05yNWdveVdSUUJ0cktKaWlTZEJVWmVxb0RvSUY5NHpCCndGbzJJT1JFdXFqcU51M3diMWZIM3p1dGdtalFra3IxVjJhd3hmcExLWlROQWdNQkFBRT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg"
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
# Changelog
|
||||
All notable changes to this package will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.2.2] - 2023-01-30
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed exception thrown when removing marker.
|
||||
|
||||
## [1.2.1] - 2023-01-02
|
||||
|
||||
### Changed
|
||||
|
||||
* Updated What's New page
|
||||
|
||||
## [1.2.0] - 2023-01-02
|
||||
|
||||
### Added
|
||||
|
||||
* Added support for removing vsync time, so we can see actual CPU duration over multiple frames. A drop down has been added to 'remove' a marker from the analysis and has entries for "FPS Wait", "Present Wait" and "Custom" where you can select any marker from the table using the right click context menu to "Remove Marker".
|
||||
* Added optional standard deviation (SD) column into marker table (on single view) for sorting data based on variation of marker timings in over the frames.
|
||||
* Added export of the comparison table as CSV.
|
||||
|
||||
### Changed
|
||||
|
||||
* Improved profile analysis performance.
|
||||
* Updated minimal suppourted version to Unity 2020.3.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed bug when depth filter and marker vsync removal both applied.
|
||||
* Fixed commas in CSV export file.
|
||||
|
||||
## [1.1.1] - 2021-10-04
|
||||
|
||||
### Fixed
|
||||
* Fixed pulling frame data from the Profiler to exclude first and last frames if their main thread profiler data is incomplete, so that they don't skewer the analysis results [(case 1359686)](https://issuetracker.unity3d.com/product/unity/issues/guid/1359686/).
|
||||
* Fixed IndexOutOfRangeException thrown when using Profiler or selecting Profiler frames in Profile Analyzer. [(1366931)](https://issuetracker.unity3d.com/product/unity/issues/guid/1366931/)
|
||||
|
||||
## [1.1.0] - 2021-07-23
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed x axis display on frame time graph when capture doesn't match Unity Profiler contents.
|
||||
* Fixed selected marker name to be updated even when Profiler fails to sync selection.
|
||||
|
||||
## [1.1.0-pre.2] - 2021-04-16
|
||||
|
||||
### Changed
|
||||
|
||||
* Ensured forward compilation compatibility of ProfilerWindow API usage.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed Frame View Dropdown not showing time in Microseconds when Microseconds are set as display unit type.
|
||||
* Fixed ArgurmentOutOfRange exception on 'Clear Selection' usage in Frame Control.
|
||||
* Improved loading and analysis progressbar progression to be monotonously incremental and reflect actual stage correctly.
|
||||
* Fixed the marked frame time overlay so that it works on loaded data (case 1327888).
|
||||
* Fixed keyboard frame controls so that they do not play an error sound (case 1327931).
|
||||
* Fixed RangeSettings.IsEqual so that it doesn't throw an exception when there is a different dataView.selectedIndices.
|
||||
|
||||
## [1.1.0-pre.1] - 2021-02-22
|
||||
|
||||
### Changed
|
||||
|
||||
* Used the new `EditorWindow.docked` API on 2020.1 and later to replace reflection usage.
|
||||
* Made the **Thread Selection** window a utility window.
|
||||
* Added a progress bar, which shows data loading progress.
|
||||
* **All** no longer collapses in the thread view because it was superfluous and held no discernible purpose.
|
||||
* Added information to the frame summary tooltip to show the selected ranges.
|
||||
* Selecting the **Frame**, **Thread**, and **Marker Summary** title labels now also toggles their foldout.
|
||||
* Timings in tooltips now display up to seven digits with a unit type change if below 0.01. This ensures the value is readable and correct.
|
||||
* Frame Time Graph selection modifiers (Next, Previous, Grow, Shrink etc.) now work when multiple regions are selected.
|
||||
* Added new (hidden) columns to the comparison tab for **Percentage Diff** for each of the diff types
|
||||
* The **Depth Slice** drop-down is searchable and scrollable for Unity versions 2019.1 or newer.
|
||||
* Improved tooltips on the top 10 marker duration time.
|
||||
|
||||
### Fixed
|
||||
|
||||
* The Thread Summary and Marker Summary views now correctly clip their contents when scrolling on 2018.4 and below.
|
||||
* Marker detail bars are no longer drawn on top of the list's headers when scrolling.
|
||||
* Fixed an issue with saving when there is no data loaded, which previously caused a null reference exception.
|
||||
* **Add to** and **Remove from** name filter is no longer case sensitive.
|
||||
* Optimized frame selection with **Show Filtered Threads** option enabled and **All Threads** turned on.
|
||||
* Fixed an issue where the **Upper Quartile of frame time** option did not fit in the Graph scale dropdown in **Thread Summary**.
|
||||
* Improved the error message when the Profile Analyzer fails to load a .data file.
|
||||
* Fixed an issue where changing the depth slice and right-clicking on a marker caused an error in 2018.4 and earlier.
|
||||
* Fixed the automatic increase of an unsaved file counter in Single view.
|
||||
* Improved the Marker Column mode so that it is separate for Single and Compare view.
|
||||
* The tooltip of the frame graph's scale control is now clearer and matches the documentation.
|
||||
* Fixed an issue where a frame of '0' was incorrectly shown in the **Marker Summary** when the marker had a duration of 0ms.
|
||||
* Fixed an issue with **Selection Synchronization** between Profiler Window and Profile Analyzer in Unity 2021.1 or newer.
|
||||
* Added a log message when the loading or analysis fails due to domain reload.
|
||||
* Fixed an issue with the table context menu, where it was not disabled during data processing.
|
||||
* Fixed an issue where exporting a marker table that contained markers with commas or quotation marks to the .csv format would break the format.
|
||||
|
||||
## [1.0.3] - 2020-07-31
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fixed warnings in code so package will not break Unity projects that have warnings as errors enabled.
|
||||
|
||||
## [1.0.2] - 2020-06-30
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fixed issue where second profiler instance could appear after entering play mode when profile analyzer is open
|
||||
* Improved performance of pulling data from Unity profiler
|
||||
* Pull Data button now enabled when profiler is recording. This will stop the recording for the duration of the action of pulling of data.
|
||||
* Fixed Median sorting in Comparison mode when value missing from one data set, Missing values always sorted before 0 values.
|
||||
|
||||
## [1.0.1] - 2020-06-16
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fixed "median marker time (in currently selected frames)" in tooltip for 'Top 10 markers'.
|
||||
* Fixed profile analyzer 5.6 support
|
||||
* Fixed minor visual artefact when no marker selection
|
||||
* Fixed issue where frame time graph tooltips were not always appearing when hovering
|
||||
* Marker Summary - Count Values are now correctly sorted in descending order
|
||||
* Hiding selected marker in comparison view if lacking 2 valid data sets
|
||||
* Corrected bucketing of histogram data for counts. Display was fractionally incorrect
|
||||
* Fixed sorting of frames (by time/count) to be more stable by providing a secondary sort by frame index
|
||||
* Marker table export now orders by descending median time (to match the default UI sort option).
|
||||
|
||||
## [1.0.0] - 2020-06-02
|
||||
|
||||
### Changes
|
||||
* Export window updates. Includes tooltips, better on screen positioning.
|
||||
* If opened and then new data set loaded. New data set now correctly exported.
|
||||
* Frame Time graph now shows border when selected
|
||||
* Keys 1 and 2 select the first or second frame time graph to take keyboard focus
|
||||
* Frame Time graph now shows highlighted when all frames selected, un highlighted when no frames selected.
|
||||
|
||||
### Fixes
|
||||
* Improved histogram display when 0 range.
|
||||
* Widened frame index buttons when frame value large
|
||||
* 'total number of markers' value in Compare tab now corrected to use unfiltered count
|
||||
* Improved error messages when jumpping to frame when profiler data doesn't match profile analyzer data
|
||||
|
||||
## [0.7.0-preview.4] - 2020-05-29
|
||||
|
||||
### Changes
|
||||
* Thread selection window
|
||||
* Now only applys thread selection changes when clicking 'Apply'. Closing the window no longer applies the changes automatically.
|
||||
* Now contains buttons to Reset to previous thread selection, Select just "Main Thread" or "Common" set selection (Main, Render and Jobs).
|
||||
* Apply button is now greyed out while analysis is running.
|
||||
* Group column enabled and used to split off group from thread name, default to by group first
|
||||
* Re sorted when selection changed (so states sort correctly when sorting by state)
|
||||
* Sort order preserved when buttons pressed and re-opening
|
||||
* Fixed sort order for threads (10 after 9 rather than between 1 and 2)
|
||||
* Marker sorting
|
||||
* Marker table column filtering now preserved when clicking Analyse or Compare or tab switching.
|
||||
* Compare table bars to now sort by bar size (=delta value) rather than the left/right value.
|
||||
* Marker table bars now start sorted by descending size which is more common usage.
|
||||
* Added depths columns into the comparison table and a 'Depth' option in the Marker Columns dropdown
|
||||
* Added thread count into thread summary area
|
||||
* Added threads column into marker tables (threads the marker occurs on)
|
||||
* Thread summary now contains thread count, unique count per data set and selection count
|
||||
* Frame time area now allows adding to the selection by holding CTRL (or COMMAND on Mac)
|
||||
* Frame range value now includes tooltip to give more detail about the selection
|
||||
* Improved text string for depth filter status on the top marker display
|
||||
|
||||
### Fixes
|
||||
* Detected and ignored invalid frame markers (duration < 0)
|
||||
* Fixed/Removed warnings on loading data in compare view when selection active.
|
||||
* Disabled "Analyze" button while analysis is already running
|
||||
* Fixed Marker Summary 'Top' dropdown selection text width
|
||||
* Fixed thread selection window sort order
|
||||
* Fixed tooltip on 'count' bars to be scalar value (not a time unit)
|
||||
* Clamped selected region shown when zoomed in on frame time graph
|
||||
* Frame Control value no longer overlaps with drop-down list when Units are set to Microseconds
|
||||
* Fixed thread count text when analysis completes after swapping single/compare tabs during analysis
|
||||
* Fixed Frame View's tooltip "total time" when rapidly changing frames
|
||||
* Fixed overlapping text when selecting single frame when zoomed in
|
||||
* Histogram frame count now lists "1 frame" when single frame rather than "1 frames"
|
||||
* Mode: text in top bar now same size as rest of text.
|
||||
* Clear Selection in marker right click context menu now only shown if a selection has been made
|
||||
* Cut/Paste now supported in the include/exclude marker name filter
|
||||
* Frame Time graph frame selection grow/shrink now keeps current selection in paired graph
|
||||
* Fixed frame start/end display for "Select Frames that contain this marker (within whole data set)"
|
||||
* Corrected bucketing of histogram data. Display was fractionally incorrect
|
||||
* Histogram now shows non zero height bar if an item in the bucket
|
||||
* Fixed right click "Remove from Included Filters" when using (quoted) markers with spaces in names
|
||||
* Fixed data auto-load from Single to Compare tab when capturing after entering playmode
|
||||
* Fixed auto right calculation to use most common difference
|
||||
* Fixed auto right display to show + or - and not both at the same time
|
||||
* Fixed infinite analysis loop when auto right calculation was clamping to max depth in the other data set
|
||||
* Auto right now clamps to min/max depth rather than reverting to 'all'
|
||||
* Fixed thread count bug when using comparison mode and loading data on top of existing data
|
||||
* Selected marker refocused in marker list when table is regenerated (e.g. when selecting all frames containing the marker).
|
||||
* Column by which the Marker Details is sorted by is now maintained when entering Play Mode
|
||||
* Updated frame time graph context menu to make hotkeys more consistent with out Unity UI layouts
|
||||
* Percent symbol no longer cut off in Mean frame contribution when the value is 100%
|
||||
* Frame time graph now scales up when zooming when comparing data with different frame amounts
|
||||
* Changing 'Auto Right' tick box no longer causes re-analysis if depth settings unchanged
|
||||
* Most tooltips now show values without rounding, so its more obvious when low values give non zero deltas.
|
||||
* Fixed individual max tooltip
|
||||
* Disabled 'Pull data' button when Unity profiler is still capturing data (as the pull would not complete).
|
||||
* Fixed right frame index in exported comparison CSV file.
|
||||
* Frame selection no longer changed when moving the cursor in the Filter text box with arrow keys
|
||||
* Help text now continues to be shown after entering play mode, while no data set loaded/pulled
|
||||
* Fixed frame index button heights when alternative (Verdana) font used.
|
||||
* Fixed cache styles to be updated when changing theme (to fix text colour in filters area)
|
||||
* Single click (without drag) now always single frame rather than the group of frames in the pixel wide area.
|
||||
* Split frame range into start and end rows to display more information when values large
|
||||
* Marker Summary comparison times are now limited to 5 digits to prevent text clipping off
|
||||
|
||||
### Enhancements
|
||||
* Optimised filter processing for more responsive scrolling when a large marker filter list is supplied.
|
||||
* Optimised thread name summary display
|
||||
|
||||
## [0.6.0-preview.1] - 2020-02-04
|
||||
* Fixed a crash with the thread selection API in Unity 2020.1
|
||||
* Fixed marker and thread scroll bars
|
||||
* Added extra documentation for public API elements
|
||||
|
||||
## [0.5.0-preview.2] - 2019-09-19
|
||||
* Minor documentation update to fix the changelog formatting
|
||||
|
||||
## [0.5.0-preview.1] - 2019-09-18
|
||||
|
||||
### Features
|
||||
* Added self time option to display 'exclusive' time of markers excluding time in children.
|
||||
* Added ability to filter to a parent marker to reduce the marker list to a part of the tree.
|
||||
* Added option to filter the column list to set groups (including custom).
|
||||
* Added column for total marker time over the frame
|
||||
* Added copy to clipboard on table entries
|
||||
* Added export option for marker table
|
||||
|
||||
### Enhancements
|
||||
* Improved Top N Markers graph to make it clearer this is for the median frames of each data set.
|
||||
* Added thread count display (next to marker count).
|
||||
* Added frame index to more tooltips to improve clarity of the data values (marker table, frame and marker summary).
|
||||
* Added additional visual bars for total and count diffs. Added abs count column.
|
||||
* Improved performance of adding to include/exclude filter via the right click menu by only refreshing the table (and no longer rerunning full analysis)
|
||||
* Improved performance for scrolling by caching strings for profile table and comparison table.
|
||||
* Added unaccounted time into the Top N Markers graph when total is less than the median frame time
|
||||
* Added grow/shrink selection hot keys and menu options
|
||||
* Added tooltip info for frame time duration for selection range
|
||||
|
||||
### Fixes
|
||||
* Fixed issue with combined marker count when data sets have some unique markers.
|
||||
* Fixed bars less than 1 pixel wide to clamp to min width of 1.
|
||||
* Fixed help text for new editor skin in 2019.3
|
||||
* Fixed bug with calculation of the auto right depth offset (see with 2017.4/2018.4 comparisons)
|
||||
* Improved the frame offset times in the frame time and comparison frame time exports
|
||||
* Fixed bug with missing first frame of data / frame offset incorrect when reloading .pdata
|
||||
|
||||
## [0.4.0-preview.5] - 2019-04-02
|
||||
|
||||
* Updated package.json file to indicate this package is valid for all unity versions
|
||||
|
||||
## [0.4.0-preview.4] - 2019-04-02
|
||||
|
||||
* Fixed issue in 2017.4 with unsupported analytics API and a GUI style.
|
||||
|
||||
## [0.4.0-preview.3] - 2019-04-01
|
||||
|
||||
* First public release of Profile Analyzer.
|
||||
|
||||
## [0.1.0-preview] - 2018-12-07
|
||||
|
||||
* This is the first beta release of Profile Analyzer
|
||||
|
||||
The profile analyzer tool augments the standard Unity Profiler. It provides multi frame analysis of the profiling data.
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 379ad77c12d5940bb8254d679e69d4ce
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
* [About Profile Analyzer](index.md)
|
||||
* [What's new](whats-new.md)
|
||||
* [Upgrade guide](upgrade-guide.md)
|
||||
* [Profile Analyzer window](profile-analyzer-window.md)
|
||||
* [Single view](single-view.md)
|
||||
* [Compare view](compare-view.md)
|
||||
* [Frame Control pane](frame-range-selection.md)
|
||||
* [Filters pane](filtering-system.md)
|
||||
* [Frame Summary](frame-summary.md)
|
||||
* [Thread Summary](thread-summary.md)
|
||||
* [Marker Summary](marker-summary.md)
|
||||
* [Statistics in the Profile Analyzer](statistics.md)
|
||||
* Workflows
|
||||
* [Collecting and viewing data](collecting-and-viewing-data.md)
|
||||
* [Comparing frames from the same data set](comparing-frames-same-dataset.md)
|
||||
* [Comparing frames from different data sets](analyzing-mulitple-datasets.md)
|
||||
* [Ordering frames by length](ordering-frames-by-cost.md)
|
||||
@@ -0,0 +1,21 @@
|
||||
# Comparing frames from different data sets
|
||||
|
||||
This workflow explains how to compare two frames from different data sets. In this example, it shows how to compare the median frames from each data set. Comparing the median frames helps you understand what might be happening in a frame that is central to the distribution for each data set.
|
||||
|
||||
## Step 1: Collect performance data to analyze
|
||||
|
||||
1. Open the Profile Analyzer window (menu: **Window > Analysis > Profile Analyzer**). Click the **Compare** button in the toolbar to enter the Compare view.
|
||||
1. Collect some profiling data. To pull data from an active profiling session, click the **Pull Data** button. This pulls in the current set of available frames from the Profiler. If you don't have an active profile session, click the **Open Profiler Window** button, then load or record some data. For more information on how to collect data, see the workflow documentation on [Collecting and viewing data](collecting-and-viewing-data.md).
|
||||
1. Pull a different data set that you want to analyze into each graph in the [Frame Control](frame-range-selection.md) pane.
|
||||
|
||||
## Step 2: Select frames of interest
|
||||
|
||||
Enable **Pair Graph Selection**. Right-click on one of the graphs in the Frame Control pane and then choose **Select Median Frame** from the context menu.
|
||||
|
||||
The Profile Analyzer then analyzes the two median frames of the data sets like this:
|
||||
|
||||

|
||||
|
||||
You can then look further and compare the differences between the median frames of each data set.
|
||||
|
||||
For further information, see the [Ordering frames by length](ordering-frames-by-cost.md) workflow, which extends the selected range and number of frames used from the middle of the frame distribution.
|
||||
@@ -0,0 +1,37 @@
|
||||
# Collecting and viewing data
|
||||
|
||||
This workflow explains how to populate the [Unity Profiler](https://docs.unity3d.com/Manual/Profiler.html) and Profile Analyzer with data. Note that the Profile Analyzer only analyzes CPU data from the Profiler.
|
||||
|
||||
## Step 1: Open the Unity Profiler
|
||||
|
||||
Go to the menu: **Window > Analysis > Profiler** or press Ctrl+7 (Command+7 on macOS). If you have the Profile Analyzer window open, you can click the **Open Profiler Window** button.
|
||||
|
||||
## Step 2: Populate the Profiler with data
|
||||
|
||||
To use the Profile Analyzer, you must populate it with data from the Profiler. To add data to the Profiler, you can either record some new data, or load a Profiler capture file in the .data file format.
|
||||
|
||||
### a) Record new data
|
||||
|
||||
Click the **Attach to Player** dropdown at the top of the window (next to the Record button) and select a player to profile. By default this is set to **Playmode**. Click the **Record** button to start recording data. If you enabled **Autoconnect to Profiler** in the Build Settings, the Profiler automatically collects data when you start a built player.
|
||||
|
||||
For more information on how to record data in the Profiler, see the documentation on [Profiling your application](https://docs.unity3d.com/Manual/profiler-profiling-applications.html) in the Unity User Manual.
|
||||
|
||||
### b) Load data
|
||||
|
||||
To load a saved .data file, in the top right of the Profiler window, select the **Load** button.
|
||||
|
||||
## Step 3: Pull the data into the Profile Analyzer window
|
||||
|
||||
Open the Profile Analyzer window (menu: **Window > Analysis > Profile Analyzer**) and then select the **Pull Data** button in the [Frame Control](frame-range-selection.md) pane. The Profile Analyzer then pulls in the data that is loaded in the Profiler window.
|
||||
|
||||
>[!TIP]
|
||||
>The Profiler window and the Profile Analyzer window require a lot of screen real-estate. Docking the two windows together in a single tabbed window lets you navigate between the two views quickly.
|
||||
|
||||
<br/>*The Profile Analyzer in the Compare mode docked next to the Profiler in one window*
|
||||
|
||||
## Step 4: Load and save Profile Analyzer data
|
||||
|
||||
To save the data from the Profile Analyzer, click the **Save** button in any view. Select where you would like to save your data, and then Unity saves the data in the .pdata format. To load this data, click the **Load** button in any view.
|
||||
|
||||
>[!NOTE]
|
||||
>When you load data into the Profile Analyzer, the data must be in the Profile Analyzer .pdata format. If you have data from the Profiler in the .data file format, open it in the Profiler first, and in the Profile Analyzer select the **Pull Data** button.
|
||||
@@ -0,0 +1,119 @@
|
||||
# Compare view
|
||||
|
||||
In the **Compare** view you can load two data sets, which the Profiler Analyzer displays in two different colors. It displays the information from both data sets in a similar way to the [Single view](single-view.md) and has the same panes and panels.
|
||||
|
||||
For information on navigating the window, see the [Profile Analyzer window navigation](profile-analyzer-window.html/#window-navigation) documentation.
|
||||
|
||||
<br/>*The Compare view with two data sets loaded.*
|
||||
|
||||
## Loading data
|
||||
|
||||
To load data into the **Compare** view, select the **Pull Data** button in the frame control pane, and the Profile Analyzer pulls in any data in the [Profiler](https://docs.unity3d.com/Manual/Profiler.html) window. Alternatively, select the **Load** button to load Profile Analyzer (.pdata) data you have saved from a previous session.
|
||||
|
||||
>[!NOTE]
|
||||
>If you select the **Load** option, the data must be in the Profile Analyzer .pdata format. If you have data from the Profiler in the .data file format, open it in the Profiler first, and then select the **Pull Data** button in the Profile Analyzer.
|
||||
|
||||
For more information on how to pull data into the Profile Analyzer, see the workflow documentation on [Collecting and viewing data](collecting-and-viewing-data.md).
|
||||
|
||||
## Marker Comparison list
|
||||
|
||||
The **Marker Comparison** pane contains a sortable list of markers with a number of useful statistics, including the difference between the two sets. The proportional graphs with the `<` and `>` labels visualize the values of each marker, so you can see the difference between the timings of both samples.
|
||||
|
||||
If you select a marker in the list, the **Marker Summary** panel displays in depth information on the marker. Each marker in the list is an aggregation of all the instances of that marker, across all filtered threads and in all ranged frames.
|
||||
|
||||
You can filter the columns in the **Marker Comparison** list to a more relevant set. This is particularly useful if you want to filter out irrelevant data when you look for **Time** or **Count** values. To filter the columns, select the **Marker columns** dropdown from the **Filters** pane. For more information on how to filter data, see the [Filters](filtering-system.md) documentation.
|
||||
|
||||
### Marker Comparison columns and groups
|
||||
|
||||
By default, the **Marker columns** dropdown in the **Filters** pane has six preset column layouts that you can use to adjust the layout of the **Marker Comparison** pane. They are:
|
||||
|
||||
* **Time and count:** Displays information on the average timings and number of times the markers were called.
|
||||
* **Time:** Displays information on the average timings of the markers.
|
||||
* **Totals:** Displays information about the total amount of time the markers took on the whole data set.
|
||||
* **Time with totals:** Displays information about both the average and total times of the markers.
|
||||
* **Count totals:** Displays information about the total number of times the markers were called.
|
||||
* **Count per frame:** Displays information on the average total per frame the markers were called.
|
||||
* **Depths:** Displays information on where the markers are in the Hierarchy. For more information, see the documentation on Depth Slices in [Filters pane](filtering-system.html#depth-slice).
|
||||
* **Threads:** Displays the name of the thread that the markers appear on. For more information, see the documentation on the Thread window in [Filters pane](filtering-system.html#thread-window).
|
||||
|
||||
You can also use the **Custom** column layout to select your own custom mix of columns to add to the layout. To do this, right click on the header of any column, and manually enable or disable any of the columns as necessary.
|
||||
|
||||
>[!NOTE]
|
||||
>In this pane, the **Left** label refers to the first data set loaded into the **Frame Control** pane, which is colored blue. The **Right** label refers to the second data set, which is colored orange.
|
||||
|
||||
The following table shows the columns that the Profile Analyzer displays when you select that layout.
|
||||
|
||||
||**Time and count**| **Time**| **Totals**| **Time with totals**| **Count totals**| **Count per frame** |**Depths**|**Threads**|
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
|**Marker Name**|✓|✓|✓|✓|✓|✓|✓|✓|
|
||||
|**Left Median**<br/> **Right Median**|✓|✓||✓|||||
|
||||
|`<`<br/> `>`|✓|✓||✓|||||
|
||||
|**Diff**|✓|✓|||||||
|
||||
|**Diff Percent**|||||||||
|
||||
|**Abs Diff**|✓|✓||✓|||||
|
||||
|**Count Left** <br/> **Count Right**|✓||||✓||||
|
||||
|`<` **Count** <br/> `>` **Count**|||||✓||||
|
||||
|**Count Delta**|✓||||✓||||
|
||||
|**Count Delta Percent**|||||||||
|
||||
|**Abs Count**|||||✓||||
|
||||
|**Count Left Frame** <br/> **Count Right Frame**||||||✓|||
|
||||
|`<` **Frame Count** <br/> `>` **Frame Count**||||||✓|||
|
||||
|**Count Delta Frame**||||||✓|||
|
||||
|**Count Delta Percent Frame**|||||||||
|
||||
|**Abs Frame Count**||||||✓|||
|
||||
|**Total Left** <br/> **Total Right**|||✓|✓|||||
|
||||
|`<` **Total** <br/> `>` **Total**|||✓|✓|||||
|
||||
|**Total Delta**|||✓||||||
|
||||
|**Total Delta Percent**|||||||||
|
||||
|**Abs Total**|||✓|✓|||||
|
||||
|**Depth Left** <br/> **Depth Right**|||||||✓||
|
||||
|**Depth Diff**|||||||✓||
|
||||
|**Threads Left** <br/> **Threads Right**||||||||✓|
|
||||
|
||||
The following table explains what each column does:
|
||||
|
||||
|**Column**|**Description**|
|
||||
|---|---|
|
||||
|**Marker Name**| Displays the name of the marker.|
|
||||
|**Left Median** <br/> **Right Median** | The sum of activity for the marker. **Left Median** displays the first data set loaded into the **Frame Control** pane, colored blue. **Right Median** displays the second data set loaded into the **Frame Control** pane, colored orange.|
|
||||
|`<` <br/> `>`|A visual representation of the **Left Median** (`<`) and **Right Median** (`>`) data.|
|
||||
|**Diff**|The difference between the summed values in each data set. Negative values mean that the left (blue) set of data is bigger, positive means the right (orange) set of data is bigger.|
|
||||
|**Diff Percent**|The difference relative to the first data set.|
|
||||
|**Abs Diff**|The absolute difference between the summed values in each data set.|
|
||||
|**Count Left**<br/>**Count Right**|The number of times the marker started or stopped. **Count Left** displays the first data set loaded into the **Frame Control** pane, colored blue. **Count Right** displays the second data set loaded into the **Frame Control** pane, colored orange.|
|
||||
|`<` **Count** <br/> `>` **Count**|A visual representation of the **Count Left** and **Count Right** data.|
|
||||
|**Count Delta**|The difference between the **Count** values in each data set. Negative values mean that the left (blue) set of data is bigger, positive means the right (orange) set of data is bigger.|
|
||||
|**Count Delta Percent**|The difference in count relative to the first data set.|
|
||||
|**Abs Count**|The absolute difference between the **Count** values for the selected frames. Negative values mean that the left (blue) set of data is bigger, positive means the right (orange) set of data is bigger.|
|
||||
|**Count Left Frame** <br/> **Count Right Frame**| The average count of the marker over all non-zero frames. **Count Left Frame** displays the first data set loaded into the **Frame Control** pane, colored blue. **Count Right Frame** displays the second data set loaded into the **Frame Control** pane, colored orange.|
|
||||
|`<` **Frame Count** <br/> `>` **Frame Count**|A visual representation of the **Count Left Frame** and **Count Right Frame** data.|
|
||||
|**Count Delta Frame**|The difference between the **Count Left Frame** and **Count Right Frame** values. Negative values mean that the left (blue) set of data is bigger, positive means the right (orange) set of data is bigger.|
|
||||
|**Count Delta Percent Frame**|The difference in average count relative to the first data set.|
|
||||
|**Abs Frame Count**|The absolute difference between the number of times the marker started or stopped in each data set.|
|
||||
|**Total Left** <br/> **Total Right**|The total time for the marker over the selected frames. **Total Left** displays the first data set loaded into the **Frame Control** pane, colored blue. **Total Right** displays the second data set loaded into the **Frame Control** pane, colored orange.|
|
||||
|`<` **Total** <br/> `>` **Total**|A visual representation of the **Total Left** and **Total Right** data.|
|
||||
|**Total Delta**|The difference between the total times over the selected frames in each data set. Negative values mean that the left (blue) set of data is bigger, positive means the right (orange) set of data is bigger.|
|
||||
|**Total Delta Percent**|The difference in total time relative to the first data set.|
|
||||
|**Abs Total**|The absolute difference between the total times over all of the selected frames in each data set.|
|
||||
|**Depth Left** <br/> **Depth Right**|The level, or depth, that the marker appears at. The marker might appear on multiple depth levels. **Depth Left** displays the first data set loaded into the **Frame Control** pane, colored blue. **Depth Right** displays the second data set loaded into the **Frame Control** pane, colored orange.|
|
||||
|**Depth Diff**|The difference between the **Depth Left** and **Depth Right** values.|
|
||||
|**Threads Left** <br/> **Threads Right**|The name of the thread that the marker appears on. **Threads Left** displays the first data set loaded into the **Frame Control** pane, colored blue. **Threads Right** displays the second data set loaded into the **Frame Control** pane, colored orange.|
|
||||
|
||||
### Marker Comparison context menu commands
|
||||
|
||||
If you right-click on a marker in the **Marker Comparison** list you can control the filter and list even further.
|
||||
|
||||
|**Command**|**Function**|
|
||||
|---|---|
|
||||
|**Select Frames that contain this marker (within whole data set)**| Select all the frames from the entire data set that contain an instance of this marker.|
|
||||
|**Select Frames that contain this marker (within current selection)**| Select all the frames from a selected range of data that contain an instance of this marker.|
|
||||
|**Select All**| Selects the entire data set, if you have a range of data selected.|
|
||||
|**Add to / Remove From Include Filter**| Add or remove the selected marker to the **Include** filter. This filters the marker list to only markers that match.|
|
||||
|**Add to Exclude Filter**| Add the selected marker to the **Exclude** filter. This removes the marker from the marker list. This is useful if you want to remove markers that are using up resources and skewing the markers that you are interested in.|
|
||||
|**Set as Parent Marker Filter**| Limit the analysis to this marker and markers included below it on the callstack. For more information, see the [Parent Marker](filtering-system.html#parent-marker) documentation on the Filters page.|
|
||||
|**Clear Parent Marker Filter**| Select this to clear the marker as a parent marker filter.|
|
||||
|**Copy To Clipboard**| Copies the selected value to the clipboard.|
|
||||
|
||||
## Analyzing data in Compare view
|
||||
|
||||
For further information on how to analyze data in Compare view, see the workflow documentation on [Comparing frames from different data sets](comparing-frames-same-dataset.md) and [Comparing frames from the same data set](comparing-frames-same-dataset.md).
|
||||
@@ -0,0 +1,22 @@
|
||||
# Comparing frames from the same data set
|
||||
|
||||
This workflow explains how to compare two frames from the same data set. In this example, it explains how to compare the median and longest frames in a data set. Comparing the median and longest frames is useful to help understand what is happening in the longest frame that is not happening in an average frame, or what is taking longer than average to complete.
|
||||
|
||||
## Step 1: Collect performance data to analyze
|
||||
|
||||
Open the Profile Analyzer window (menu: **Window > Analysis > Profile Analyzer**) and collect some profiling data. To pull data from an active profiling session, click the **Pull Data** button. This pulls in the current set of available frames from the Profiler. If you don't have an active profile session, click the **Open Profiler Window** button, then load or record some data.
|
||||
|
||||
For more information on how to collect data, see the workflow documentation on [Collecting and viewing data](collecting-and-viewing-data.md).
|
||||
|
||||
## Step 2: Open Compare view
|
||||
|
||||
Click the **Compare** button in the toolbar to switch to the [Compare View](compare-view.md).
|
||||
|
||||
## Step 3: Select the median and longest frames
|
||||
|
||||
1. In the [Frame Control](frame-range-selection.md) pane, right click on the top graph and choose **Select Median Frame** from the context menu.
|
||||
1. Next, right click on the lower graph and choose **Select Longest Frame** in the context menu.
|
||||
|
||||
The Profile Analyzer then analyzes the two frames and displays the data for the median and longest frames:
|
||||
|
||||
<br/>*The Profile Analyzer window with the median and longest frames of the same data set selected*
|
||||
@@ -0,0 +1,43 @@
|
||||
apiRules:
|
||||
- exclude:
|
||||
# inherited Object methods
|
||||
uidRegex: ^System\.Object\..*$
|
||||
type: Method
|
||||
- exclude:
|
||||
# mentioning types from System.* namespace
|
||||
uidRegex: ^System\..*$
|
||||
type: Type
|
||||
- exclude:
|
||||
# mentioning types from NiceIO.* namespace
|
||||
uidRegex: ^NiceIO\..*$
|
||||
type: Type
|
||||
- exclude:
|
||||
# mentioning types from Format.* namespace
|
||||
uidRegex: ^Format\..*$
|
||||
type: Type
|
||||
- exclude:
|
||||
hasAttribute:
|
||||
uid: System.ObsoleteAttribute
|
||||
type: Member
|
||||
- exclude:
|
||||
hasAttribute:
|
||||
uid: System.ObsoleteAttribute
|
||||
type: Type
|
||||
- exclude:
|
||||
uidRegex: ^$
|
||||
type: Namespace
|
||||
- exclude:
|
||||
uidRegex: .*Tests$
|
||||
type: Type
|
||||
- exclude:
|
||||
uidRegex: .*Test$
|
||||
type: Type
|
||||
- exclude:
|
||||
uidRegex: .*Fixture$
|
||||
type: Type
|
||||
- exclude:
|
||||
uidRegex: UnityEditor.Performance.ProfileAnalyzer.DepthSliceUI
|
||||
type: Type
|
||||
- exclude:
|
||||
uidRegex: GetUIThreadName
|
||||
type: Method
|
||||
@@ -0,0 +1,48 @@
|
||||
# Filters pane
|
||||
|
||||
You can use the **Filters** pane in the [Single](single-view.md) and [Compare](compare-view.md) view to reduce and filter the working data set. You can limit the data set by filters such as partial marker name match, a specific thread, or a specific stack level. As well as filtering by match, you can set the filter to exclude any markers by name. This is particularly useful to exclude markers the might distort the view, or aren't statistically relevant.
|
||||
|
||||
<br/>*Single view Filters pane (above) and Compare view Filters pane (below). Note the addition of further Depth Slice controls in the Compare view.*
|
||||
|
||||
The pane has the following controls:
|
||||
|
||||
|**Control**|**Function**|
|
||||
|---|---|
|
||||
|**Name Filter**|Enter the name of a marker you would like to filter the data set by. Once you start typing, the Profile Analyzer automatically filters the **Marker details** pane to display the matching markers. You can also right click on a marker in the **Marker details** pane and select **Add to Include Filter** to add it to the filter. To include a marker name with a space, surround it in quotation marks <br/><br/> Choose **Any** from the dropdown to match any of the terms to the markers in the list. Select **All** from the dropdown to match all of the listed terms to the markers in the list.|
|
||||
|**Exclude Names**|Enter the name of a marker you would like to exclude from the data set. Once you start typing, the Profile Analyzer automatically filters the **Marker details** pane to display the matching markers. You can also right click on a marker in the **Marker details** pane and select **Add to Exclude Filter** to exclude it from the filter. <br/><br/> Select **Any** from the dropdown to match any of the terms to the markers in the list. Select **All** from the dropdown to match all of the listed terms to the markers in the list.|
|
||||
|**Thread**|Select which thread or threads to filter the data set by. Click the **Select** button to open the **Thread** window and filter the data set further. For more information, see the [Thread window](#thread-window) section of this page.|
|
||||
|**Depth Slice**|Set the depth level of the [Hierarchy](https://docs.unity3d.com/Manual/ProfilerCPU.html#hierarchy) to display. The **Marker Details/Comparison** view then displays all the markers on this level only. In the **Compare** view, the **Auto Right** checkbox is enabled by default, which automatically aligns the depth of the two data sets. For more information, see the [Depth Slice](#depth-slice) section of this page. |
|
||||
|**Parent Marker**|Displays the parent marker the data set is filtered by. To filter by a parent marker, right click on it in the **Marker Details/Comparison** list and then select **Set As Parent Marker Filter**. For more information, see the [Parent Marker](#parent-marker) section of this page.|
|
||||
|**Analyze** (Single view)<br/>**Compare** (Compare view)|Contains details of the number of markers and threads in the data set. **Note:** The button is redundant, because the Profile Analyzer automatically updates the marker pane with the filters you set. However, you can hover on the button to see timings of the analysis or comparison.|
|
||||
|**Analysis type**|Select what times the Profile Analyzer includes in its analysis, from either **Total** or **self**. The **Total** option sets the marker times to inclusive, which means the time spent in the marker and its children is included in the filter. The **Self** option sets the marker times to exclusive, which means that the the time in the marker's children is excluded from the filter. |
|
||||
|**Units**|Select the unit of measurement of time to display in the Profile Analyzer. Choose between **Milliseconds** (default) or **Microseconds**. <br/><br/>**Note:** If you select **Microseconds**, the Profile Analyzer displays larger timings in milliseconds.|
|
||||
|**Marker Columns**|Select a layout for the **Marker Details/Comparison** pane. For more information on the columns in this pane, see the documentation on [Single view Marker Details list](single-view.html#marker-details-list) and [Compare view Marker Comparison list](compare-view.html#marker-comparison-list).|
|
||||
|
||||
## Thread window
|
||||
|
||||
By default, the Profile Analyzer displays the markers on the main thread in the **Marker Details/Comparison** pane. To analyze the markers on other threads, under **Threads**, click the **Select** button, and the Threads window opens.
|
||||
|
||||
<br/>*The Thread window*
|
||||
|
||||
To add more threads to the analysis, enable the checkboxes next to their names, then click the **Apply** button. The Profile Analyzer then updates the data set. There are two pre-defined sets of threads: **Main Only** and **Common Set**. **Main Only** selects just the Main Thread, and the **Common Set** selects the Main Thread, plus the Render and Jobs threads.
|
||||
|
||||
Click the **Clear** button to clear all of the threads you've selected, and **Reset** to reset the selection back to the previous thread set.
|
||||
|
||||
## Depth Slice
|
||||
|
||||
When you select a **Depth Slice** level in the dropdown, it corresponds to the level of the marker within the hierarchy of the thread. You can visualize this in the Profiler window as follows:
|
||||
|
||||
<br/>*The Profile Analyzer in the Single view with a Depth Slice of 3 selected in the Filters pane.*
|
||||
|
||||
<br/>*The Profiler window with the CPU Usage module selected, in Timeline view. Note that the markers in the Marker Details list correspond to the markers in the third level of the Main Thread's hierarchy.*
|
||||
|
||||
In the Compare view, the Profile Analyzer automatically aligns the depth of the two data sets so that the top level markers are aligned correctly. The value of the offset is displayed in brackets after the **Auto Right** checkbox.
|
||||
|
||||
To override the automatic depth alignment, disable the **Auto Right** checkbox and then manually set the depth levels for the left (blue) and right (orange) sets. This is useful if you're comparing data sets from different versions of Unity.
|
||||
|
||||
## Parent Marker
|
||||
|
||||
To filter the dataset by a specific marker and its children, right click on the marker in the **Marker Details/Comparison** pane and then select **Set As Parent Marker Filter**. You can visualize this in the Profiler window as follows:
|
||||
|
||||
<br/>*The Profile Analyzer in the Single view with the data filtered by the parent marker of BehaviourUpdate.*
|
||||
<br/>*The same frame opened in the Profiler window, with this thread highlighted. Note that the markers in the Timeline view correspond to those filtered in the Profile Analyzer.*
|
||||
@@ -0,0 +1,71 @@
|
||||
# Frame Control pane
|
||||
|
||||
You can use the **Frame Control** pane in both the [Single](single-view.md) and [Compare](compare-view.md) views to select a range of frames to reduce the working set. The Frame Control pane is laid out as follows:
|
||||
|
||||
<br/>*The Frame Control in the Single view (top), and in the Compare view (bottom)*
|
||||
|
||||
|**Control**|**Function**|
|
||||
|---|---|
|
||||
|**A:** Pull Data / Load / Save| Click the **Pull Data** button to pull any data that is loaded in the [Profiler](https://docs.unity3d.com/Manual/Profiler.html) window.<br/>Click the **Save** button to save the data as a .pdata file.<br/>Click the **Load** button to load a .pdata file. **Note:** You can only load a .pdata file. If you have data from the Profiler in the .data file format, open it in the Profiler first, and then select the **Pull Data** button in the Profile Analyzer.|
|
||||
|**B:** Frame control scale| You can adjust the scale of the y-axis of the Frame Control graph by selecting this dropdown. By default, it scales to the maximum value of the data set. You can also choose from traditional frame boundaries. 16.67ms is equivalent to 60Hz/FPS, 33.33ms is equivalent to 30Hz/FPS, and 66.67ms is equivalent to 15Hz/FPS.|
|
||||
|**C:** Selected thread| This displays the name of the marker which is selected in the **Marker Details** pane. When you select a thread from this list, the Profile Analyzer highlights its corresponding timings on the Frame Control graph in a green-blue color.|
|
||||
|**D:** Pair Graph Selection<br/>*(Compare view only)*| When you enable this checkbox, the Profile Analyzer reflects any changes you make in the range selection of a data set in both data sets. This is important to ensure you compare the exact same number of frames in both data sets and get an accurate comparison. |
|
||||
|
||||
## Selecting a frame range
|
||||
|
||||
The Profile Analyzer uses all the frames in the data sets unless you select a sub-range. When you select a sub-range, it limits the analysis to just those frames which lets you focus on a specific frame or set of frames. To select a range of frames, click and drag on the Frame Control graph. To clear the selection, right click on the Frame Control graph and select **Clear Selection**.
|
||||
|
||||
To help visualize which frames are in the current selection, the start and end frame number, plus the frame count in square brackets appears on the x-axis of the graph.
|
||||
|
||||
<br/>*The Frame Control in the Single view (top), with 187 frames selected, starting on frame 37 and ending on frame 224. The Compare view (bottom) has Pair Graph Selection enabled, with the same 237 frames selected on both graphs, starting on frame 135 and ending on frame 371.*
|
||||
|
||||
## Frame range controls
|
||||
|
||||
You can control the selection of data in both the **Single** and **Compare** views by using the following shortcuts, or by right-clicking and selecting an option from the context menu.
|
||||
|
||||
>[!NOTE]
|
||||
>In **Compare** view, make sure you enable the **Pair Graph Selection** checkbox to carry out the following commands on both graphs at the same time.
|
||||
|
||||
### Shortcuts
|
||||
|
||||
|**Shortcut**|**Function**|
|
||||
|---|---|
|
||||
|**Shift+click**|Hold down the Shift key while clicking on the selection on the Frame Control graph to move the selection around freely.|
|
||||
|**Left/Right arrow**|Move the selection forward or backwards by one frame.|
|
||||
|**Ctrl + click**<br/>(**Command + click** on macOS)| Selects multiple parts of the data set. Hold down the Ctrl key (Command key on macOS) while making a selection and then click, and optionally drag, on different sections of the chart to select multiple parts of the data set.|
|
||||
|**Equals (=)**| Extend the selection by one frame on each end of the selection.|
|
||||
|**Alt+Equals**<br/>(**⌥ + Equals** on macOS)|Reduce the selection by one frame on each end of the selection.|
|
||||
|**Shift+Equals**| Extend the selection by 10 frames on each end of the selection.|
|
||||
|**Hyphen (-)**|Reduce the selection by one frame on each end of the selection.|
|
||||
|**Alt+Hyphen**<br/>(**⌥ + Hyphen** on macOS)|Extend the selection by one frame on each end of the selection.|
|
||||
|**Shift+Hyphen**| Reduce the selection by 10 frames on each end of the selection.|
|
||||
|**Comma (,)**| Extend the start of the selection by one frame.|
|
||||
|**Alt+Comma**<br/>(**⌥ + Comma**) on macOS|Reduce the start of the selection by one frame.|
|
||||
|**Shift+Comma**|Extend the start of the selection by 10 frames.|
|
||||
|**Period (.)**| Extend the end of the selection by one frame.|
|
||||
|**Alt+Period**<br/>**(⌥ + Period on macOS)**|Reduce the end of the selection by one frame.|
|
||||
|**Shift+Period**|Extend the end of the selection by 10 frames.|
|
||||
|**1**<br/>**2**<br/>*Compare view only*|In **Compare** view, with **Pair Graph Selection** disabled, use the 1 or 2 key on your keyboard to switch between frames. 1 selects the top data, and 2 selects the bottom data. |
|
||||
|
||||
### Context menu commands
|
||||
|
||||
Right click on the Frame Control graph to bring up the context menu.
|
||||
|
||||
|**Menu item**|**Function**|
|
||||
|---|---|
|
||||
|**Clear Selection**|Clears the selected range. The Profile Analyzer then performs the analysis on the whole data set.|
|
||||
|**Invert Selection**|Inverts the selected range.|
|
||||
|**Select Shortest Frame**|Selects the frame with the shortest time.|
|
||||
|**Select Longest Frame**|Selects the frame with the longest time.|
|
||||
|**Select Median Frame**| Selects the frame with the Median time. |
|
||||
|**Move selection left / right**|Move the whole selection one frame backwards, or one frame forwards. |
|
||||
|**Grow selection**|Extend the selection by one frame on each end of the selection. Select the **(fast)** operation to extend the selection by 10 frames at each end.|
|
||||
|**Shrink selection**|Reduce the selection by one frame on each end of the selection. Select the **(fast)** operation to reduce the selection by 10 frames at each end.|
|
||||
|**Grow selection left / right**|Extend the start or the end of the selection by one frame. Select the **(fast)** operation to extend the start or the end of the selection by 10 frames.|
|
||||
|**Shrink selection left /right**|Reduce the selection by one frame at the beginning or the end of the selection. Select the **(fast)** operation to reduce the start or the end of the selection by 10 frames.|
|
||||
|**Zoom Selection**| Zoom the Frame Control graph to display the selected range only.|
|
||||
|**Zoom All**| Zoom out to show all frames. The current selection range is highlighted.|
|
||||
|**Show Selected Marker**| Enable this setting to highlight the selected marker's time on the Frame Control graph. By default, this setting is enabled and the Profile Analyzer highlights the marker's timings in green on the graph.|
|
||||
|**Show Filtered Threads**|Enable this setting to highlight the current filtered thread times on the Frame Control graph. The Profile Analyzer highlights the timings in purple. This setting is disabled by default.|
|
||||
|**Show Frame Lines**| Enable this setting to display the common frame boundaries as a horizontal line on the Frame Control graph. This setting is enabled by default.|
|
||||
|**Order By Frame Duration**| Enable this setting to display the order of the frames by their duration from smallest to largest on the Frame Control graph, rather than by frame index. By default, this setting is disabled. This setting is particularly useful to group similar performant frames together.|
|
||||
@@ -0,0 +1,23 @@
|
||||
# Frame Summary
|
||||
|
||||
The Frame Summary pane displays a summary of the frame times for the range of data selected. This pane provides useful information about the frames in the data selection, including the maximum, minimum, upper and lower quartile, mean, and median timings.
|
||||
|
||||
<br/>*The Frame Summary pane in Single view (left,) and Compare view (right)*
|
||||
|
||||
The Frame Summary pane is a good way for you to see at an overview what frames might be an outlier and how evenly distributed the timings are in the data set. For example, in the above screenshot, in the Compare view, while the median values are fairly similar, with little difference, the maximum frame value differs greatly, which suggests that the Right data set had more spikes in performance, that you could focus investigating further.
|
||||
|
||||
## Statistics
|
||||
|
||||
|**Statistic**|**Description**|
|
||||
|---|---|
|
||||
|**Frame Count**|The number of frames selected in the [Frame Control pane](frame-range-selection.md).<br/><br/>In Compare view, the **Left** count corresponds to the first data set loaded into the Profile Analyzer, colored blue, and the **Right** count corresponds to the second data set loaded into the Profile Analyzer, colored orange. <br/><br/>The **Diff** count is the difference in frame count between the Right and Left values. When this number is negative, it indicates that the Left frame count is larger than the Right frame count. When the Diff is a positive number, it means that the Right frame count is larger than the Left frame count.|
|
||||
|**Start**|The frame number that the data selection starts on. In Single view, you can click the button next to this number to jump to the relevant frame in the Profiler window.|
|
||||
|**End**|The frame number that the data selection ends on. In Single view, you can click the button next to this number to jump to the relevant frame in the Profiler window.|
|
||||
|**Max**|The largest (maximum) frame time in the data selection. In Compare view, the Diff column shows the difference between the Right and Left timings.|
|
||||
|**Upper Quartile**|Displays the upper [quartile](https://en.wikipedia.org/wiki/Quartile) of the data set. In Compare view, the Diff column shows the difference between the Right and Left timings.|
|
||||
|**Median**|Displays the [median](https://en.wikipedia.org/wiki/Median) value of the data set. In Compare view, the Diff column shows the difference between the Right and Left timings.|
|
||||
|**Mean**|Displays the [mean](https://en.wikipedia.org/wiki/Arithmetic_mean) value of the data set. In Compare view, the Diff column shows the difference between the Right and Left timings.|
|
||||
|**Lower Quartile**|Displays the lower [quartile](https://en.wikipedia.org/wiki/Quartile) of the data set. In Compare view, the Diff column shows the difference between the Right and Left timings.|
|
||||
|**Min**|The smallest (minimum) frame time in the data selection. In Compare view, the Diff column shows the difference between the Right and Left timings.|
|
||||
|
||||
Underneath the statistics, the Profile Analyzer displays the timings as a histogram and box and whisker plot. For further information on the statistics available and how to analyze them, see the documentation on [Statistics in the Profile Analyzer](statistics.md).
|
||||
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 391 KiB |
|
After Width: | Height: | Size: 208 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 217 KiB |
|
After Width: | Height: | Size: 230 KiB |
|
After Width: | Height: | Size: 189 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 24 KiB |
@@ -0,0 +1,31 @@
|
||||
# About the Profile Analyzer package
|
||||
|
||||
The Profile Analyzer aggregates and visualizes frame and marker data from a set of [Unity Profiler](https://docs.unity3d.com/Manual/Profiler.html) frames to help you understand their behavior. You can use the Profile Analyzer to compare two sets of data side-by-side, which complements the single frame analysis already available in the Unity Profiler.
|
||||
|
||||
For information on how to use the Profile Analyzer, see the documentation on the [Profile Analyzer window](profile-analyzer-window.md)
|
||||
|
||||
<br/>*The Profile Analyzer window, with two sets of data loaded*
|
||||
|
||||
## Installing the Profile Analyzer
|
||||
|
||||
To install this package into versions of Unity that support the package manager follow the instructions in the [Package Manager documentation](https://docs.unity3d.com/Packages/com.unity.package-manager-ui@latest/index.html).
|
||||
|
||||
When using any Unity version newer than 2021.2.0a5, you can just click __[this link to install it by name](com.unity3d.kharma:upmpackage/com.unity.performance.profile-analyzer)__.
|
||||
|
||||
### Earlier versions of Unity
|
||||
|
||||
For earlier versions, follow this link to the Profile Analyzer [download](https://download.packages.unity.com/com.unity.performance.profile-analyzer/-/com.unity.performance.profile-analyzer-1.1.1.tgz) and place the contents into your Project's Assets folder.
|
||||
|
||||
## Requirements
|
||||
|
||||
This version of the Profile Analyzer is compatible with the following versions of the Unity Editor:
|
||||
|
||||
* 5.6 and later
|
||||
|
||||
## Known limitations
|
||||
|
||||
The Profile Analyzer has the following known limitations:
|
||||
|
||||
* The original Profile data is not saved in the Profile Analyzer .pdata file. Therefore, you should store both the Unity Profiler .data file (or the .raw file exported from a stand alone player) along with the .pdata file.
|
||||
* When you click on a marker, the Profile Analyzer attempts to jump to the same marker in the Unity Profiler if the same data is loaded. You must make a selection in the Unity Profiler beforehand for this to work. In the Unity Profiler in the UI view, the vertical height is not correct.
|
||||
* On Unity 5.6 and 2017.4, the Thread Select Window allows you to collapse the 'All' threads item, which has no purpose.
|
||||
@@ -0,0 +1,22 @@
|
||||
# Marker Summary
|
||||
|
||||
The Marker Summary pane summarizes the marker data of the current selected item in the **Marker Details** list. You can use this pane to get an overview of the markers that contribute the most amount of time to the data set. For more information on the statistics in this pane, see the [Statistics](statistics.md) documentation.
|
||||
|
||||
<br/>*The Marker Summary pane, in Single View (left), and Compare View (right)*
|
||||
|
||||
## Statistics
|
||||
|
||||
|**Statistic**|**Description**|
|
||||
|---|---|
|
||||
|**Marker name**|Displays the name of the selected marker.|
|
||||
|**Mean frame contribution**|Visualization of the marker's mean frame contribution as a percentage of the data set's total time.|
|
||||
|**First frame**|Link to the frame that the marker first appeared in. Click the button to jump to the relevant frame in the Profiler window.|
|
||||
|**Top by frame costs**|Displays the longest occurrences of the marker in the data set. Use the dropdown to display up to 10 entries in the list.|
|
||||
|**Max**|The largest (maximum) frame time in the data selection. In Compare view, the Diff column shows the difference between the Right and Left timings.|
|
||||
|**Upper Quartile**|Displays the upper [quartile](https://en.wikipedia.org/wiki/Quartile) of the data set. In Compare view, the Diff column shows the difference between the Right and Left timings.|
|
||||
|**Median**|Displays the [median](https://en.wikipedia.org/wiki/Median) value of the data set. In Compare view, the Diff column shows the difference between the Right and Left timings.|
|
||||
|**Mean**|Displays the [mean](https://en.wikipedia.org/wiki/Arithmetic_mean) value of the data set. In Compare view, the Diff column shows the difference between the Right and Left timings.|
|
||||
|**Lower Quartile**|Displays the lower [quartile](https://en.wikipedia.org/wiki/Quartile) of the data set. In Compare view, the Diff column shows the difference between the Right and Left timings.|
|
||||
|**Min**|The smallest (minimum) frame time in the data selection. In Compare view, the Diff column shows the difference between the Right and Left timings.|
|
||||
|**Individual Max**|The maximum value of an individual marker instance.|
|
||||
|**Individual Min**|The minimum value of an individual marker instance.|
|
||||
@@ -0,0 +1,25 @@
|
||||
# Ordering frames by length
|
||||
|
||||
This workflow explains how to order the frame data in the Profile Analyzer by frame length, shortest to longest, which results in an s-curve style graph. The Profile Analyzer groups frames with similar performance together, which enables you to create sub-ranges over a set of similar performing frames without including any outliers. This is particularly useful to normalize the data and to focus on only the shortest, longest, or average frames in the data set.
|
||||
|
||||
This workflow applies for both [Single view](single-view.md) and [Compare view](compare-view.md).
|
||||
|
||||
## Step 1: Collect performance data to analyze
|
||||
|
||||
Open the Profile Analyzer window (menu: **Window > Analysis > Profile Analyzer**) and collect some profiling data. To pull data from an active profiling session, click the **Pull Data** button. This pulls in the current set of available frames from the Profiler. If you don't have an active profile session, click the **Open Profiler Window** button, then load or record some data.
|
||||
|
||||
For more information on how to collect data, see the workflow documentation on [Collecting and viewing data](collecting-and-viewing-data.md).
|
||||
|
||||
## Step 2: Order the data set
|
||||
|
||||
If you are in the Compare view, enable the **Pair Graph Selection** checkbox. By default, the data set is ordered by frame number. To order the frames by their length, right click on a graph in the Frame Control pane, and select **Order by Frame Duration**.
|
||||
|
||||
The Profile Analyzer then orders the graph from shortest to longest frame.
|
||||
|
||||
## Step 3: Select frames of interest
|
||||
|
||||
Click and drag on the graph to select a range of frames with similar performance from the middle of the distribution.
|
||||
|
||||
<br/>*Top, the Frame Control pane in Single view, with the graph ordered by frame duration, and a range of frames selected. Bottom, the Frame Control pane in Compare view, with the graph ordered by frame duration, and a range of frames selected.*
|
||||
|
||||
You can now use the Profile Analyzer to analyze data from frames that have a similar performance.
|
||||
@@ -0,0 +1,76 @@
|
||||
# Profile Analyzer window
|
||||
|
||||
The Profile Analyzer window visualizes frame, thread, and marker data from the [Unity Profiler](https://docs.unity3d.com/Manual/Profiler.html) window. You can use it to analyze the performance of specific markers, threads, and frames.
|
||||
|
||||
To open the Profile Analyzer window, in the Unity Editor go to menu: **Window > Analysis > Profile Analyzer**.
|
||||
|
||||
<br/>*The Profile Analyzer window on start up, with no data loaded.*
|
||||
|
||||
The Profile Analyzer has two views:
|
||||
|
||||
* **Single:** The [Single view](single-view.md) displays one set of Profiler data. You can use this view to analyze specific events on one set of profiling data.
|
||||
* **Compare:** In the [Compare view](compare-view.md), you can load two sets of Profiler data to compare. You can use the Compare view to compare two different sets of profiling data, or to compare different events in the same data set.
|
||||
|
||||
In both views, the Profile Analyzer displays min, max, median, mean, and lower/upper quartile values of the selected frame range. It also displays information on the distribution of each profiler marker in histograms and box and whisker plots.
|
||||
|
||||
## Pulling and analyzing performance data
|
||||
|
||||
The Profile Analyzer only analyzes CPU data. It can either analyze data from the active set of frames loaded into the Profiler, or from a saved Profile Analyzer .pdata file.
|
||||
|
||||
The Profile Analyzer aggregates the activity of each marker, and generates useful statistics to help you analyze the marker costs over a number of threads and frames. This includes summing the runtime and counts of each occurrence of every marker for all active threads and frames the Profile captured.
|
||||
|
||||
### Pulling data from the Profiler window
|
||||
|
||||
To pull data from an active profiling session, click the **Pull Data** button. This pulls in the current set of available frames from the Profiler and visualizes the analyzed results.
|
||||
|
||||
If you don't have an active profile session, click the **Open Profiler Window** button, then load or record some data.
|
||||
|
||||
### Loading and saving a data set
|
||||
|
||||
You can save or reload any data that the Profile Analyzer analyzes at any point in time. This means you can share your analysis with others and load the results into the **Single** or **Compare** view at any time. To save the data from the Profile Analyzer, click the **Save** button in any view. This saves the data in the .pdata format. To load this data, click the **Load** button in any view.
|
||||
|
||||
>[!NOTE]
|
||||
>If you select the **Load** option, the data must be in the Profile Analyzer .pdata format. If you have data from the Profiler in the .data file format, open it in the Profiler first, and then select the **Pull Data** button in the Profile Analyzer.
|
||||
|
||||
For more information on how to pull data into the Profile Analyzer, see the workflow documentation on [Collecting and viewing data](collecting-and-viewing-data.md).
|
||||
|
||||
## Window navigation
|
||||
|
||||
The controls and navigation for the Profile Analyzer window are very similar in both the Single and Compare view.
|
||||
|
||||
The Profile Analyzer window has the following controls across both views, which you can access at the top of the window:
|
||||
|
||||
|**Control**|**Function**|
|
||||
|---|---|
|
||||
|**Single**|Enter the [Single view](single-view.md) mode. This is the default view mode.|
|
||||
|**Compare**|Enter the [Compare view](compare-view.md) mode. You can compare two sets of profiling data in this mode.|
|
||||
|**Export**|Export the data into a .csv format. When you click this button, a dialog box opens and you can choose from:<br/> **Marker table:** Exports the marker data visible in [Single view](single-view.md)<br/>**Single Frame Times:** Exports the frame data visible in the Single view<br/>**Comparison Frame Times:** Exports both sets of data visible in the [Compare view](compare-view.md).<br/><br/>This button is disabled if you haven't imported any data into the Profile Analyzer window.|
|
||||
|**Open Profiler Window**|Click this button to open the [Profiler window](https://docs.unity3d.com/Manual/ProfilerWindow.html). When the Profiler window is open, this button changes to **Close Profiler Window**.|
|
||||
|
||||
When you load some data into the Profile Analyzer window, the window populates with the profiling data, and is laid out as follows:
|
||||
|
||||
<br/>*The Profile Analyzer window in Single view
|
||||
|
||||
|Pane|Description|
|
||||
|---|---|
|
||||
|**A:** Frame control| Displays individual frame timings, ordered by frame index or frame duration. You can also use this pane to select a range of frames for the Profile Analyzer to analyze. For more information on this pane, see the [Frame control](frame-range-selection.md) documentation. <br/><br/> In Compare View, to select the same range on both sets of data, enable **Pair Graph Selection** underneath the charts and then click and drag a selection on either of the charts. For more information on this pane, see the [Frame control](frame-range-selection.md) documentation.|
|
||||
|**B:** Filters| Use the filter pane to limit the data that the Profile Analyzer displays. You can filter by partial match name, thread, or depth slice. You can also exclude markers to remove any markers that aren't relevant from the filtered view. For more information on how to use the filter pane, see the [Filter](filtering-system.md) documentation.|
|
||||
|**C:** Top 10 markers on median frame(s)| Visualizes the ten highest duration markers that contributed to the frame. In Compare view, this shows the ten highest markers for both sets of data. You can select any of the markers in this pane to see more information on them. The Profile Analyzer reflects changes you make to the Depth filter in this pane.<br/><br/>In Compare view, to adjust how the Profile Analyzer draws the markers against each other, use the **Ratio** dropdown.<br/> **Normalised** displays the two data sets relative to their own time at the selected depth. <br/>**Longest** displays the absolute difference between the two sets at the selected depth.|
|
||||
|**D:** Marker details for currently selected range *(Single view)*<br/>Marker Comparison for currently selected range *(Compare view)*| A sortable list of markers, with detailed information on their timings. For more information on the particular statistics available in these panes, see the documentation on [Single view marker details list](single-view.html#marker-details-list) and [Compare view Marker Comparison list](compare-view.html#marker-comparison-list).|
|
||||
|**E:** Frame summary| Displays a summary of frame times. For more information on this pane, see the documentation on [Frame Summary](frame-summary.md).|
|
||||
|**F:** Thread summary|Displays information about the threads in the data set. For more information, see the documentation on the [Thread Summary pane](thread-summary.md).|
|
||||
|**G:** Marker summary| Summarizes the marker data of the current selected item in the **Marker Details** list. For more information, see the [Marker summary](marker-summary.md) documentation.|
|
||||
|
||||
### Frame buttons
|
||||
|
||||
If the data you analyze in the Profile Analyzer is also loaded into the Profiler window, you can click on the frame buttons in the Profile Analyzer window to jump to the relevant frames in the Profiler.
|
||||
|
||||
<br/>*The Frame Summary pane with the frame buttons highlighted.*
|
||||
|
||||
## Frame range selection
|
||||
|
||||
You can limit the analysis to a subset or selection of frames. To do this, you can click and drag on the chart in the Frame Control pane at the top of the **Single** and **Compare** views and select a range, or use the context menu commands to select a range. For more information on using the frame range pane, see the [Frame Control pane](frame-range-selection.md) documentation.
|
||||
|
||||
## Filtering
|
||||
|
||||
You can also filter the data to limit the number of markers the Profile Analyzer displays. This includes filtering by thread, call depth and name substrings. You can order the remaining markers by any of the available metrics in the marker list control. For more information on how to use the filter system, see the [Filters pane](filtering-system.md) documentation.
|
||||
@@ -0,0 +1,100 @@
|
||||
# Single view
|
||||
|
||||
The **Single** view displays information about a single set of profiling data. You can use it to analyze how markers perform across frames. The view is divided into several frames, which contain information on frame timings, as well as min, max, median, mean, and lower / upper quartile values for frames, threads, and markers.
|
||||
|
||||
For information on navigating the window, see the [Profile Analyzer window navigation](profile-analyzer-window.html/#window-navigation) documentation.
|
||||
|
||||
<br/> *The Single view in the Profile Analyzer window, with some data loaded*
|
||||
|
||||
## Loading data
|
||||
|
||||
To load data into the **Single** view, select the **Pull Data** button in the frame control pane, and the Profile Analyzer pulls in any data in the [Profiler](https://docs.unity3d.com/Manual/Profiler.html) window. Alternatively, select the **Load** button to load Profile Analyzer (.pdata) data you have saved from a previous session.
|
||||
|
||||
>[!NOTE]
|
||||
>If you select the **Load** option, the data must be in the Profile Analyzer .pdata format. If you have data from the Profiler in the .data file format, open it in the Profiler first, and then select the **Pull Data** button in the Profile Analyzer.
|
||||
|
||||
For more information on how to pull data into the Profile Analyzer, see the workflow documentation on [Collecting and viewing data](collecting-and-viewing-data.md).
|
||||
|
||||
## Marker details list
|
||||
|
||||
The **Marker details** pane contains a sortable list of markers with a number of useful statistics. If you select a marker in the list, the **Marker Summary** panel displays in depth information on the marker. Each marker in the list is an aggregation of all the instances of that marker, across all filtered threads and in all ranged frames.
|
||||
|
||||
You can filter the columns in the **Marker details** to a more relevant set. This is particularly useful if you want to filter out irrelevant data when you look for **Time** or **Count** values. To filter the columns, select the **Marker columns** dropdown from the **Filters** pane. For more information on how to filter data, see the [Filters](filtering-system.md) documentation.
|
||||
|
||||
### Marker details columns and groups
|
||||
|
||||
By default, the **Marker columns** dropdown in the **Filters** pane has six preset column layouts that you can use to adjust the layout of the **Marker details** pane. They are:
|
||||
|
||||
* **Time and count:** Displays information on the average timings and number of times the markers were called.
|
||||
* **Time:** Displays information on the average timings of the markers.
|
||||
* **Totals:** Displays information about the total amount of time the markers took on the whole data set.
|
||||
* **Time with totals:** Displays information about both the average and total times of the markers.
|
||||
* **Count totals:** Displays information about the total number of times the markers were called.
|
||||
* **Count per frame:** Displays information on the average total per frame the markers were called.
|
||||
* **Depths:** Displays information on where the markers are in the Hierarchy. For more information, see the documentation on Depth Slices in [Filters pane](filtering-system.html#depth-slice).
|
||||
* **Threads:** Displays the name of the thread that the markers appear on. For more information, see the documentation on the Thread window in [Filters pane](filtering-system.html#thread-window).
|
||||
|
||||
You can also use the **Custom** column layout, to select your own custom mix of columns to add to the layout. To do this, right-click on the header of any column, and manually enable or disable any of the columns as necessary.
|
||||
|
||||
The following table shows the columns that the Profile Analyzer displays when you select that layout.
|
||||
|
||||
||**Time and count**|**Time**|**Totals**|**Time with totals**|**Count totals**|**Count per frame**|**Depths**|**Threads**|**Custom only**|
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
|**Marker Name**|✓|✓|✓|✓|✓|✓|✓|✓||
|
||||
|**Depth**|✓|✓|✓|✓|✓|✓|✓|||
|
||||
|**Median**|✓|✓||✓||||||
|
||||
|**Median Bar**|✓|✓||✓||||||
|
||||
|**Mean**|✓|||||||||
|
||||
|**SD**|||||||||✓|
|
||||
|**Min**|✓|✓||✓||||||
|
||||
|**Max**|✓|✓||✓||||||
|
||||
|**Range**|✓|✓||✓|||||
|
||||
|**Count**|✓||||✓|||||
|
||||
|**Count Bar**|||||✓|||||
|
||||
|**Count Frame**|✓|||||✓||||
|
||||
|**Count Frame Bar**||||||✓||||
|
||||
|**Count SD**|||||||||✓|
|
||||
|**1st**|||||||✓||||
|
||||
|**At Median Frame**|✓|✓||✓||||||
|
||||
|**Total**|||✓|✓||||||
|
||||
|**Total Bar**|||✓|✓||||||
|
||||
|**Threads**||||||||✓||
|
||||
|
||||
The following table explains what each column does:
|
||||
|
||||
|**Column**|**Description**|
|
||||
|---|---|
|
||||
|**Marker Name**| Displays the name of the marker.|
|
||||
|**Depth**|The depth in the hierarchy that the marker appears on.|
|
||||
|**Median**| The median value of the marker's time distribution.|
|
||||
|**Median Bar**| A visualization of the **Median** value.|
|
||||
|**Mean**| The average value of the marker's time distribution.|
|
||||
|**SD**| The standard deviation of the marker's time distribution.|
|
||||
|**Min**| The minimum value of the marker's time distribution.|
|
||||
|**Max**| The maximum value of the marker's time distribution.|
|
||||
|**Range**| The difference between the marker's **Min** and **Max** timings.|
|
||||
|**Count**| The number of times the marker was pushed / popped.|
|
||||
|**Count Bar**|A visualization of the **Count** value. |
|
||||
|**Count Frame**|The average number of times per frame that the marker was pushed/popped.|
|
||||
|**Count Frame Bar**|A visualization of the **Count Frame** value.|
|
||||
|**Count SD**|The standard deviation of **Count** value.|
|
||||
|**1st**| The frame number in which the marker first was pushed / popped.|
|
||||
|**At Median Frame**| The sum of activity for the marker in the median frame.|
|
||||
|**Total**| The total time spent for this marker in all selected frames.|
|
||||
|**Total Bar**|A visualization of the **Total** value.|
|
||||
|**Threads**|The name of the thread that the marker appears on.|
|
||||
|
||||
### Marker Details context menu commands
|
||||
|
||||
If you right click on a marker in the **Marker Details** list you can control the filter and list even further.
|
||||
|
||||
|**Command**|**Function**|
|
||||
|---|---|
|
||||
|**Select Frames that contain this marker (within whole data set)**| Select all the frames from the entire data set that contain an instance of this marker.|
|
||||
|**Select Frames that contain this marker (within current selection)**| Select all the frames from a selected range of data that contain an instance of this marker.|
|
||||
|**Select All**| Selects the entire data set, if you have a range of data selected.|
|
||||
|**Add to / Remove From Include Filter**| Add or remove the selected marker to the **Include** filter. This filters the marker list to only markers that match.|
|
||||
|**Add to Exclude Filter**| Add the selected marker to the **Exclude** filter. This removes the marker from the marker list. This is useful if you want to remove markers that are using up resources and skewing the markers that you are interested in.|
|
||||
|**Set as Parent Marker Filter**| Limit the analysis to this marker and markers included below it on the callstack. For more information, see the [Parent Marker](filtering-system.html#parent-marker) documentation on the Filters page.|
|
||||
|**Clear Parent Marker Filter**| Select this to clear the marker as a parent marker filter.|
|
||||
|**Copy To Clipboard**| Copies the selected value to the clipboard.|
|
||||
@@ -0,0 +1,52 @@
|
||||
# Statistics in the Profile Analyzer
|
||||
|
||||
The Profile Analyzer displays a number of statistics about the data you're analyzing in the Frame, Thread, and Marker Summary panes..
|
||||
|
||||
## Available statistics
|
||||
|
||||
|**Statistic**|**Description**|
|
||||
|---|---|
|
||||
|**Min**| Min represents the lowest (minimum) value for the marker or frame time.|
|
||||
|**Max**| Max represents the largest (maximum) value for the marker or frame time.|
|
||||
|**Median**|[Median](https://en.wikipedia.org/wiki/Median) is the middle value of a data set, and separates the higher half from the lower half of a data set. |
|
||||
|**Mean**| [Mean](https://en.wikipedia.org/wiki/Arithmetic_mean) is the average value in a data set. It represents the sum of all of the values in the data set divided by the number of values in the data set.|
|
||||
|**Lower and Upper Quartiles**|The lower [quartile](https://en.wikipedia.org/wiki/Quartile) is the middle number between the smallest number and the median of the data set. The upper quartile is the middle value between the median and the highest value of the data set.|
|
||||
|**Interquartile Range**| The [interquartile range](https://en.wikipedia.org/wiki/Interquartile_range) shows the range of values in the central 50% of the data. The range is equal to the difference between the upper and lower quartile values. |
|
||||
|
||||
## How the statistics are represented
|
||||
|
||||
The statistics are displayed in several ways in the Profile Analyzer. In the Frame, Thread, and Marker summary panes, the statistics are displayed as raw numbers as well as in [histograms](https://en.wikipedia.org/wiki/Histogram) and [box and whisker plots](https://en.wikipedia.org/wiki/Box_plot) to give a visual representation of time distribution. The following section gives some examples of some common distributions you might find in your analysis.
|
||||
|
||||
## Single view
|
||||
|
||||
### Even distribution
|
||||
|
||||

|
||||
|
||||
In this example, the graphs display a distribution of marker calls that range from 16.75ms to 17.26ms. The histogram on the left shows that a lot of the buckets are being hit at a fairly even amount. This is also evident in the box and whisker plot on the right where the box is large and is towards the middle of the upper and lower bounds.
|
||||
|
||||
### Outlier
|
||||
|
||||

|
||||
|
||||
In this example, the graphs display a distribution of marker calls that range from 0.67ms to 5.32ms. The histogram on the left shows that the lower end buckets are used the most and only some of the more expensive buckets are hit. This is also reflected in the box and whisker plot, where the box appears towards the bottom of the range but the whisker, or upper bound of the range, is high up.
|
||||
|
||||
## Compare view
|
||||
|
||||
### Similar distribution
|
||||
|
||||

|
||||
|
||||
In this example, there are two distributions that are similar, and both the histogram and box and whisker plots show a very similar pattern. This shows that the marker activity in both sets is similar.
|
||||
|
||||
### Different distribution
|
||||
|
||||

|
||||
|
||||
In this example, there are two distributions that are different; both the histogram and the box and whisker plots show that the marker in the left (blue) data set ran for longer. The histogram shows that the blue data set used more expensive buckets, and the box and whisker plot is drawn higher up on its range. This means that the marker activity in left (blue) data set is more costly and should be investigated further.
|
||||
|
||||
### Overlapping distributions
|
||||
|
||||

|
||||
|
||||
In this example, there are two distributions that are similar. Both data sets have the same lower bound and have some overlap in the middle of the range, but the right (orange) dataset uses some of the more expensive buckets and has a higher upper bound. This means that the activity in right (orange) data set is more costly or is being called more times and should be investigated further.
|
||||
@@ -0,0 +1,18 @@
|
||||
# Thread Summary
|
||||
|
||||
The Thread Summary pane displays information about the threads in the data set. You can use the information in this pane to compare and understand the number of threads used in the data set.
|
||||
|
||||
By default, the Profile Analyzer only displays information about the Main Thread. To add more threads to the analysis, use the **Thread** button in the Filters pane. For more information, see the documentation on the [Filters pane](filtering-system.html/#thread-window).
|
||||
|
||||
<br/>*The Thread Summary pane in Single View (left), and Compare View (right)*
|
||||
|
||||
## Statistics
|
||||
|
||||
|**Statistic**|**Description**|
|
||||
|---|---|
|
||||
|**Total Count**|The total number of threads in the data set, or data selection. The Compare view also has a **Total** column, which displays the total count for all threads across both data sets.|
|
||||
|**Unique Count**<br/>*(Compare view only)*|The number of unique threads in each data set. A unique thread is one that is not in the other data set. The **Total** column displays the total of unique threads across both data sets.|
|
||||
|**Selected**|The number of threads selected in the data set. To add more threads use the **Thread** button in the [Filters pane](filtering-system.html/#thread-window).|
|
||||
|**Graph Scale**|Select a scale for the plot. You can choose from:<br/> **Median frame time**<br/>**Upper quartile of frame time**</br>**Max frame time**.|
|
||||
|
||||
At the bottom of the pane, there is a summary of the median run time of the current filtered threads with a box and whisker plot of them. You can use these graphs to analyze which threads Unity spends the most time on.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Profile Analyzer upgrade guide
|
||||
|
||||
You do not need to take any actions to upgrade your project when you update this package.
|
||||
@@ -0,0 +1,13 @@
|
||||
# What's new in 1.2.2
|
||||
|
||||
Summary of changes in Profile Analyzer version 1.2.2.
|
||||
|
||||
The main updates in this release include:
|
||||
|
||||
## Added
|
||||
|
||||
* Added support for removing vsync time, so we can see actual CPU duration over multiple frames. A drop down has been added to 'remove' a marker from the analysis and has entries for "FPS Wait", "Present Wait" and "Custom" where you can select any marker from the table using the right click context menu to "Remove Marker".
|
||||
* Added optional standard deviation (SD) column into marker table (on single view) for sorting data based on variation of marker timings in over the frames.
|
||||
* Added export of the comparison table as CSV.
|
||||
|
||||
For a full list of changes and updates in this version, see the [Profile Analyzer package changelog](https://docs.unity3d.com/Packages/com.unity.performance.profile-analyzer@latest/index.html?subfolder=/changelog/CHANGELOG.html).
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6867c2662acbb4810a369aba450b6776
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,373 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor.Analytics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Analytics;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
class ProfileAnalyzerAnalytics
|
||||
{
|
||||
const int k_MaxEventsPerHour = 100;
|
||||
const int k_MaxEventItems = 1000;
|
||||
const string k_VendorKey = "unity.profileanalyzer";
|
||||
const string k_EventTopicName = "usability";
|
||||
|
||||
static bool s_EnableAnalytics = false;
|
||||
|
||||
public static void EnableAnalytics()
|
||||
{
|
||||
AnalyticsResult result = EditorAnalytics.RegisterEventWithLimit(k_EventTopicName, k_MaxEventsPerHour, k_MaxEventItems, k_VendorKey);
|
||||
if (result == AnalyticsResult.Ok)
|
||||
s_EnableAnalytics = true;
|
||||
}
|
||||
|
||||
public enum UIButton
|
||||
{
|
||||
Pull,
|
||||
OpenProfiler,
|
||||
CloseProfiler,
|
||||
JumpToFrame,
|
||||
ExportSingleFrames,
|
||||
ExportComparisonFrames,
|
||||
};
|
||||
|
||||
public enum UIUsageMode
|
||||
{
|
||||
Single,
|
||||
Comparison,
|
||||
};
|
||||
|
||||
public enum UIVisibility
|
||||
{
|
||||
FrameTimeContextMenu,
|
||||
Filters,
|
||||
TopTen,
|
||||
Frames,
|
||||
Threads,
|
||||
Markers,
|
||||
};
|
||||
|
||||
public enum UIResizeView
|
||||
{
|
||||
Single,
|
||||
Comparison,
|
||||
};
|
||||
|
||||
|
||||
[Serializable]
|
||||
struct ProfileAnalyzerUIButtonEventParameters
|
||||
{
|
||||
public string name;
|
||||
|
||||
public ProfileAnalyzerUIButtonEventParameters(string name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
// camelCase since these events get serialized to Json and naming convention in analytics is camelCase
|
||||
[Serializable]
|
||||
struct ProfileAnalyzerUIButtonEvent
|
||||
{
|
||||
public ProfileAnalyzerUIButtonEvent(string name, float durationInTicks)
|
||||
{
|
||||
subtype = "profileAnalyzerUIButton";
|
||||
|
||||
// ts is auto added so no need to include it here
|
||||
//ts = (int)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
|
||||
this.duration = durationInTicks;
|
||||
|
||||
parameters = new ProfileAnalyzerUIButtonEventParameters(name);
|
||||
}
|
||||
|
||||
public string subtype;
|
||||
//public int ts;
|
||||
public float duration; // Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
|
||||
public ProfileAnalyzerUIButtonEventParameters parameters;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
struct ProfileAnalyzerUIUsageEventParameters
|
||||
{
|
||||
public string name;
|
||||
|
||||
public ProfileAnalyzerUIUsageEventParameters(string name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
struct ProfileAnalyzerUIUsageEvent
|
||||
{
|
||||
public ProfileAnalyzerUIUsageEvent(string name, float durationInTicks)
|
||||
{
|
||||
subtype = "profileAnalyzerModeUsage";
|
||||
|
||||
this.duration = durationInTicks;
|
||||
|
||||
parameters = new ProfileAnalyzerUIUsageEventParameters(name);
|
||||
}
|
||||
|
||||
public string subtype;
|
||||
public float duration; // Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
|
||||
public ProfileAnalyzerUIUsageEventParameters parameters;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
struct ProfileAnalyzerUIVisibilityEventParameters
|
||||
{
|
||||
public string name;
|
||||
public bool show;
|
||||
|
||||
public ProfileAnalyzerUIVisibilityEventParameters(string name, bool show)
|
||||
{
|
||||
this.name = name;
|
||||
this.show = show;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
struct ProfileAnalyzerUIVisibilityEvent
|
||||
{
|
||||
public ProfileAnalyzerUIVisibilityEvent(string name, float durationInTicks, bool show)
|
||||
{
|
||||
subtype = "profileAnalyzerUIVisibility";
|
||||
|
||||
this.duration = durationInTicks;
|
||||
|
||||
parameters = new ProfileAnalyzerUIVisibilityEventParameters(name, show);
|
||||
}
|
||||
|
||||
public string subtype;
|
||||
public float duration; // Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
|
||||
public ProfileAnalyzerUIVisibilityEventParameters parameters;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
struct ProfileAnalyzerUIResizeEventParameters
|
||||
{
|
||||
public string name;
|
||||
public float width;
|
||||
public float height;
|
||||
public float screenWidth;
|
||||
public float screenHeight;
|
||||
public bool docked;
|
||||
|
||||
public ProfileAnalyzerUIResizeEventParameters(string name, float width, float height, float screenWidth, float screenHeight, bool isDocked)
|
||||
{
|
||||
this.name = name;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.screenWidth = screenWidth;
|
||||
this.screenHeight = screenHeight;
|
||||
docked = isDocked;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
struct ProfileAnalyzerUIResizeEvent
|
||||
{
|
||||
public ProfileAnalyzerUIResizeEvent(string name, float durationInTicks, float width, float height, float screenWidth, float screenHeight, bool isDocked)
|
||||
{
|
||||
subtype = "profileAnalyzerUIResize";
|
||||
|
||||
this.duration = durationInTicks;
|
||||
|
||||
parameters = new ProfileAnalyzerUIResizeEventParameters(name, width, height, screenWidth, screenHeight, isDocked);
|
||||
}
|
||||
|
||||
public string subtype;
|
||||
public float duration; // Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
|
||||
public ProfileAnalyzerUIResizeEventParameters parameters;
|
||||
}
|
||||
|
||||
static float SecondsToTicks(float durationInSeconds)
|
||||
{
|
||||
return durationInSeconds * 10000;
|
||||
}
|
||||
|
||||
public static bool SendUIButtonEvent(UIButton uiButton, float durationInSeconds)
|
||||
{
|
||||
if (!s_EnableAnalytics)
|
||||
return false;
|
||||
|
||||
// Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
|
||||
float durationInTicks = SecondsToTicks(durationInSeconds);
|
||||
|
||||
ProfileAnalyzerUIButtonEvent uiButtonEvent;
|
||||
switch (uiButton)
|
||||
{
|
||||
case UIButton.Pull:
|
||||
uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerGrab", durationInTicks);
|
||||
break;
|
||||
case UIButton.OpenProfiler:
|
||||
uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerOpenProfiler", durationInTicks);
|
||||
break;
|
||||
case UIButton.CloseProfiler:
|
||||
uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerCloseProfiler", durationInTicks);
|
||||
break;
|
||||
case UIButton.JumpToFrame:
|
||||
uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerJumpToFrame", durationInTicks);
|
||||
break;
|
||||
case UIButton.ExportSingleFrames:
|
||||
uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerExportSingleFrames", durationInTicks);
|
||||
break;
|
||||
case UIButton.ExportComparisonFrames:
|
||||
uiButtonEvent = new ProfileAnalyzerUIButtonEvent("profilerAnalyzerExportComparisonFrames", durationInTicks);
|
||||
break;
|
||||
default:
|
||||
Debug.LogFormat("SendUIButtonEvent: Unsupported button type : {0}", uiButton);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
AnalyticsResult result = EditorAnalytics.SendEventWithLimit(k_EventTopicName, uiButtonEvent);
|
||||
if (result != AnalyticsResult.Ok)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool SendUIUsageModeEvent(UIUsageMode uiUsageMode, float durationInSeconds)
|
||||
{
|
||||
if (!s_EnableAnalytics)
|
||||
return false;
|
||||
|
||||
// Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
|
||||
float durationInTicks = SecondsToTicks(durationInSeconds);
|
||||
|
||||
ProfileAnalyzerUIUsageEvent uiUsageEvent;
|
||||
switch (uiUsageMode)
|
||||
{
|
||||
case UIUsageMode.Single:
|
||||
uiUsageEvent = new ProfileAnalyzerUIUsageEvent("profileAnalyzerSingle", durationInTicks);
|
||||
break;
|
||||
case UIUsageMode.Comparison:
|
||||
uiUsageEvent = new ProfileAnalyzerUIUsageEvent("profileAnalyzerCompare", durationInTicks);
|
||||
break;
|
||||
default:
|
||||
Debug.LogFormat("SendUsageEvent: Unsupported usage mode : {0}", uiUsageMode);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
AnalyticsResult result = EditorAnalytics.SendEventWithLimit(k_EventTopicName, uiUsageEvent);
|
||||
if (result != AnalyticsResult.Ok)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool SendUIVisibilityEvent(UIVisibility uiVisibility, float durationInSeconds, bool show)
|
||||
{
|
||||
if (!s_EnableAnalytics)
|
||||
return false;
|
||||
|
||||
// Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
|
||||
float durationInTicks = SecondsToTicks(durationInSeconds);
|
||||
|
||||
ProfileAnalyzerUIVisibilityEvent uiUsageEvent;
|
||||
switch (uiVisibility)
|
||||
{
|
||||
case UIVisibility.FrameTimeContextMenu:
|
||||
uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerFrameTimeContextMenu", durationInTicks, show);
|
||||
break;
|
||||
case UIVisibility.Filters:
|
||||
uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerFilters", durationInTicks, show);
|
||||
break;
|
||||
case UIVisibility.TopTen:
|
||||
uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerTopTen", durationInTicks, show);
|
||||
break;
|
||||
case UIVisibility.Frames:
|
||||
uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerFrames", durationInTicks, show);
|
||||
break;
|
||||
case UIVisibility.Threads:
|
||||
uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerThreads", durationInTicks, show);
|
||||
break;
|
||||
case UIVisibility.Markers:
|
||||
uiUsageEvent = new ProfileAnalyzerUIVisibilityEvent("profilerAnalyzerMarkers", durationInTicks, show);
|
||||
break;
|
||||
default:
|
||||
Debug.LogFormat("SendUIVisibilityEvent: Unsupported visibililty item : {0}", uiVisibility);
|
||||
return false;
|
||||
}
|
||||
|
||||
AnalyticsResult result = EditorAnalytics.SendEventWithLimit(k_EventTopicName, uiUsageEvent);
|
||||
if (result != AnalyticsResult.Ok)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool SendUIResizeEvent(UIResizeView uiResizeView, float durationInSeconds, float width, float height, bool isDocked)
|
||||
{
|
||||
if (!s_EnableAnalytics)
|
||||
return false;
|
||||
|
||||
// Duration is in "ticks" 100 nanosecond intervals. I.e. 0.1 microseconds
|
||||
float durationInTicks = SecondsToTicks(durationInSeconds);
|
||||
|
||||
ProfileAnalyzerUIResizeEvent uiResizeEvent;
|
||||
switch (uiResizeView)
|
||||
{
|
||||
case UIResizeView.Single:
|
||||
// Screen.width, Screen.height is game view size
|
||||
uiResizeEvent = new ProfileAnalyzerUIResizeEvent("profileAnalyzerSingle", durationInTicks, width, height, Screen.currentResolution.width, Screen.currentResolution.height, isDocked);
|
||||
break;
|
||||
case UIResizeView.Comparison:
|
||||
uiResizeEvent = new ProfileAnalyzerUIResizeEvent("profileAnalyzerCompare", durationInTicks, width, height, Screen.currentResolution.width, Screen.currentResolution.height, isDocked);
|
||||
break;
|
||||
default:
|
||||
Debug.LogFormat("SendUIResizeEvent: Unsupported view : {0}", uiResizeView);
|
||||
return false;
|
||||
}
|
||||
|
||||
AnalyticsResult result = EditorAnalytics.SendEventWithLimit(k_EventTopicName, uiResizeEvent);
|
||||
if (result != AnalyticsResult.Ok)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal class Analytic
|
||||
{
|
||||
double m_StartTime;
|
||||
float m_DurationInSeconds;
|
||||
|
||||
public Analytic()
|
||||
{
|
||||
m_StartTime = EditorApplication.timeSinceStartup;
|
||||
m_DurationInSeconds = 0;
|
||||
}
|
||||
|
||||
public void End()
|
||||
{
|
||||
m_DurationInSeconds = (float)(EditorApplication.timeSinceStartup - m_StartTime);
|
||||
}
|
||||
|
||||
public float GetDurationInSeconds()
|
||||
{
|
||||
return m_DurationInSeconds;
|
||||
}
|
||||
}
|
||||
|
||||
static public Analytic BeginAnalytic()
|
||||
{
|
||||
return new Analytic();
|
||||
}
|
||||
|
||||
static public void SendUIButtonEvent(UIButton uiButton, Analytic instance)
|
||||
{
|
||||
instance.End();
|
||||
SendUIButtonEvent(uiButton, instance.GetDurationInSeconds());
|
||||
}
|
||||
|
||||
static public void SendUIUsageModeEvent(UIUsageMode uiUsageMode, Analytic instance)
|
||||
{
|
||||
instance.End();
|
||||
SendUIUsageModeEvent(uiUsageMode, instance.GetDurationInSeconds());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: adf9820979228054693ce2e8224012fb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Unity.Performance.Profile-Analyzer.EditorTests")]
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dcb585fc3518ff949912f2d4fff2e26f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,240 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
internal class BoxAndWhiskerPlot
|
||||
{
|
||||
Draw2D m_2D;
|
||||
|
||||
Color m_ColorBackground;
|
||||
DisplayUnits m_Units;
|
||||
|
||||
string DisplayUnits()
|
||||
{
|
||||
return m_Units.Postfix();
|
||||
}
|
||||
|
||||
string ToDisplayUnits(float ms, bool showUnits = false)
|
||||
{
|
||||
return m_Units.ToString(ms, showUnits, 5, true);
|
||||
}
|
||||
|
||||
string ToTooltipDisplayUnits(float ms, bool showUnits = false)
|
||||
{
|
||||
return m_Units.ToTooltipString(ms, showUnits);
|
||||
}
|
||||
|
||||
public void SetUnits(Units units)
|
||||
{
|
||||
m_Units = new DisplayUnits(units);
|
||||
}
|
||||
|
||||
public BoxAndWhiskerPlot(Draw2D draw2D, Units units, Color colorBackground)
|
||||
{
|
||||
m_2D = draw2D;
|
||||
SetUnits(units);
|
||||
m_ColorBackground = colorBackground;
|
||||
}
|
||||
|
||||
public BoxAndWhiskerPlot(Draw2D draw2D, Units units)
|
||||
{
|
||||
m_2D = draw2D;
|
||||
SetUnits(units);
|
||||
m_ColorBackground = new Color(0.4f, 0.4f, 0.4f);
|
||||
}
|
||||
|
||||
float ClampToRange(float value, float min, float max)
|
||||
{
|
||||
return Math.Max(min, Math.Min(value, max));
|
||||
}
|
||||
|
||||
public void Draw(float width, float height, float min, float lowerQuartile, float median, float upperQuartile, float max, float yAxisStart, float yAxisEnd, Color color, Color colorFilled)
|
||||
{
|
||||
if (m_2D.DrawStart(width, height, Draw2D.Origin.BottomLeft))
|
||||
{
|
||||
Rect rect = GUILayoutUtility.GetLastRect();
|
||||
|
||||
float x = 0;
|
||||
float y = 0;
|
||||
float w = width;
|
||||
float h = height;
|
||||
|
||||
Draw(rect, x, y, w, h, min, lowerQuartile, median, upperQuartile, max, yAxisStart, yAxisEnd, color, colorFilled);
|
||||
m_2D.DrawEnd();
|
||||
}
|
||||
}
|
||||
|
||||
string GetTooltip(float min, float lowerQuartile, float median, float upperQuartile, float max)
|
||||
{
|
||||
string tooltip = string.Format(
|
||||
"Max :\t\t{0}\n\nUpper Quartile :\t{1}\nMedian :\t\t{2}\nLower Quartile :\t{3}\nInterquartile range : \t{4}\n\nMin :\t\t{5}\nUnits :\t\t{6}",
|
||||
ToTooltipDisplayUnits(max),
|
||||
ToTooltipDisplayUnits(upperQuartile),
|
||||
ToTooltipDisplayUnits(median),
|
||||
ToTooltipDisplayUnits(lowerQuartile),
|
||||
ToTooltipDisplayUnits(upperQuartile - lowerQuartile),
|
||||
ToTooltipDisplayUnits(min),
|
||||
m_Units.Postfix()
|
||||
);
|
||||
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
public void Draw(Rect rect, float x, float y, float w, float h, float min, float lowerQuartile, float median, float upperQuartile, float max, float yAxisStart, float yAxisEnd, Color color, Color colorFilled, bool clearFirst = true)
|
||||
{
|
||||
string tooltip = GetTooltip(min, lowerQuartile, median, upperQuartile, max);
|
||||
GUI.Label(rect, new GUIContent("", tooltip));
|
||||
|
||||
if (clearFirst)
|
||||
m_2D.DrawFilledBox(x, y, w, h, m_ColorBackground);
|
||||
|
||||
float first = yAxisStart;
|
||||
float last = yAxisEnd;
|
||||
float range = last - first;
|
||||
|
||||
bool startCap = (min >= first) ? true : false;
|
||||
bool endCap = (max <= last) ? true : false;
|
||||
|
||||
// Range clamping
|
||||
min = ClampToRange(min, first, last);
|
||||
lowerQuartile = ClampToRange(lowerQuartile, first, last);
|
||||
median = ClampToRange(median, first, last);
|
||||
upperQuartile = ClampToRange(upperQuartile, first, last);
|
||||
max = ClampToRange(max, first, last);
|
||||
|
||||
float hMax = h - 1;
|
||||
float yMin = hMax * (min - first) / range;
|
||||
float yLowerQuartile = hMax * (lowerQuartile - first) / range;
|
||||
float yMedian = hMax * (median - first) / range;
|
||||
float yUpperQuartile = hMax * (upperQuartile - first) / range;
|
||||
float yMax = hMax * (max - first) / range;
|
||||
|
||||
// Min to max line
|
||||
float xCentre = x + (w / 2);
|
||||
m_2D.DrawLine(xCentre, y + yMin, xCentre, y + yLowerQuartile, color);
|
||||
m_2D.DrawLine(xCentre, y + yUpperQuartile, xCentre, y + yMax, color);
|
||||
|
||||
// Quartile boxes
|
||||
float xMargin = (2 * w / 8);
|
||||
float x1 = x + xMargin;
|
||||
float x2 = x + (w - xMargin);
|
||||
float wBox = x2 - x1;
|
||||
if (colorFilled != color)
|
||||
m_2D.DrawFilledBox(x1, y + yLowerQuartile, wBox, (yMedian - yLowerQuartile), colorFilled);
|
||||
m_2D.DrawBox(x1, y + yLowerQuartile, wBox, (yMedian - yLowerQuartile), color);
|
||||
if (colorFilled != color)
|
||||
m_2D.DrawFilledBox(x1, y + yMedian, wBox, (yUpperQuartile - yMedian), colorFilled);
|
||||
m_2D.DrawBox(x1, y + yMedian, wBox, (yUpperQuartile - yMedian), color);
|
||||
|
||||
// Median line
|
||||
//xMargin = (1 * w / 8);
|
||||
//x1 = x + xMargin;
|
||||
//x2 = x + (w - xMargin);
|
||||
m_2D.DrawLine(x1, y + yMedian, x2, y + yMedian, color);
|
||||
m_2D.DrawLine(x1, y + yMedian + 1, x2, y + yMedian + 1, color);
|
||||
|
||||
// Line caps
|
||||
xMargin = (3 * w / 8);
|
||||
x1 = x + xMargin;
|
||||
x2 = x + (w - xMargin);
|
||||
if (startCap)
|
||||
m_2D.DrawLine(x1, y + yMin, x2, y + yMin, color);
|
||||
|
||||
if (endCap)
|
||||
m_2D.DrawLine(x1, y + yMax, x2, y + yMax, color);
|
||||
}
|
||||
|
||||
public void DrawHorizontal(float width, float height, float min, float lowerQuartile, float median, float upperQuartile, float max, float xAxisStart, float xAxisEnd, Color color, Color colorFilled, GUIStyle style = null)
|
||||
{
|
||||
if (m_2D.DrawStart(width, height, Draw2D.Origin.BottomLeft, style))
|
||||
{
|
||||
Rect rect = GUILayoutUtility.GetLastRect();
|
||||
|
||||
float x = 0;
|
||||
float y = 0;
|
||||
float w = width;
|
||||
float h = height;
|
||||
|
||||
DrawHorizontal(rect, x, y, w, h, min, lowerQuartile, median, upperQuartile, max, xAxisStart, xAxisEnd, color, colorFilled);
|
||||
m_2D.DrawEnd();
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawHorizontal(Rect rect, float x, float y, float w, float h, float min, float lowerQuartile, float median, float upperQuartile, float max, float xAxisStart, float xAxisEnd, Color color, Color colorFilled, bool clearFirst = true)
|
||||
{
|
||||
string tooltip = GetTooltip(min, lowerQuartile, median, upperQuartile, max);
|
||||
GUI.Label(rect, new GUIContent("", tooltip));
|
||||
|
||||
if (clearFirst)
|
||||
m_2D.DrawFilledBox(x, y, w, h, m_ColorBackground);
|
||||
|
||||
float first = xAxisStart;
|
||||
float last = xAxisEnd;
|
||||
float range = last - first;
|
||||
|
||||
bool startCap = (min >= first) ? true : false;
|
||||
bool endCap = (max <= last) ? true : false;
|
||||
|
||||
// Range clamping
|
||||
min = ClampToRange(min, first, last);
|
||||
lowerQuartile = ClampToRange(lowerQuartile, first, last);
|
||||
median = ClampToRange(median, first, last);
|
||||
upperQuartile = ClampToRange(upperQuartile, first, last);
|
||||
max = ClampToRange(max, first, last);
|
||||
|
||||
float xMin = w * (min - first) / range;
|
||||
float xLowerQuartile = w * (lowerQuartile - first) / range;
|
||||
float xMedian = w * (median - first) / range;
|
||||
float xUpperQuartile = w * (upperQuartile - first) / range;
|
||||
float xMax = w * (max - first) / range;
|
||||
|
||||
// Min to max line
|
||||
m_2D.DrawLine(x + xMin, y + (h / 2), x + xMax, y + (h / 2), color);
|
||||
|
||||
// Quartile boxes
|
||||
float yMargin = (2 * h / 8);
|
||||
float y1 = y + yMargin;
|
||||
float y2 = y + (h - yMargin);
|
||||
float hBox = y2 - y1;
|
||||
if (colorFilled != color)
|
||||
m_2D.DrawFilledBox(x + xLowerQuartile, y1, xMedian - xLowerQuartile, hBox, colorFilled);
|
||||
m_2D.DrawBox(x + xLowerQuartile, y1, xMedian - xLowerQuartile, hBox, color);
|
||||
if (colorFilled != color)
|
||||
m_2D.DrawFilledBox(x + xMedian, y1, xUpperQuartile - xMedian, hBox, colorFilled);
|
||||
m_2D.DrawBox(x + xMedian, y1, xUpperQuartile - xMedian, hBox, color);
|
||||
|
||||
// Median line
|
||||
//yMargin = (1 * h / 8);
|
||||
//y1 = y + yMargin;
|
||||
//y2 = y + (h - yMargin);
|
||||
m_2D.DrawLine(x + xMedian, y1, x + xMedian, y2, color);
|
||||
m_2D.DrawLine(x + xMedian + 1, y1, x + xMedian + 1, y2, color);
|
||||
|
||||
// Line caps
|
||||
yMargin = (3 * h / 8);
|
||||
y1 = y + yMargin;
|
||||
y2 = y + (h - yMargin);
|
||||
if (startCap)
|
||||
m_2D.DrawLine(x + xMin, y1, x + xMin, y2, color);
|
||||
|
||||
if (endCap)
|
||||
m_2D.DrawLine(x + xMax, y1, x + xMax, y2, color);
|
||||
}
|
||||
|
||||
public void DrawText(float width, float plotHeight, float min, float max, string minTooltip, string maxTooltip)
|
||||
{
|
||||
GUIStyle shiftUpStyle = new GUIStyle(GUI.skin.label);
|
||||
shiftUpStyle.contentOffset = new Vector2(0, -5);
|
||||
shiftUpStyle.alignment = TextAnchor.UpperLeft;
|
||||
EditorGUILayout.BeginVertical(GUILayout.Height(plotHeight));
|
||||
EditorGUILayout.LabelField(new GUIContent(ToDisplayUnits(max), maxTooltip), shiftUpStyle, GUILayout.Width(width));
|
||||
GUILayout.FlexibleSpace();
|
||||
GUIStyle shiftDownStyle = new GUIStyle(GUI.skin.label);
|
||||
shiftDownStyle.contentOffset = new Vector2(0, 1);
|
||||
shiftDownStyle.alignment = TextAnchor.LowerLeft;
|
||||
EditorGUILayout.LabelField(new GUIContent(ToDisplayUnits(min), minTooltip), shiftDownStyle, GUILayout.Width(width));
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae5c414d7d406467d8ce01807c211f90
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,184 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
internal class Columns
|
||||
{
|
||||
int[] m_ColumnWidth = new int[4];
|
||||
|
||||
public Columns(int a, int b, int c, int d)
|
||||
{
|
||||
SetColumnSizes(a, b, c, d);
|
||||
}
|
||||
|
||||
public void SetColumnSizes(int a, int b, int c, int d)
|
||||
{
|
||||
m_ColumnWidth[0] = a;
|
||||
m_ColumnWidth[1] = b;
|
||||
m_ColumnWidth[2] = c;
|
||||
m_ColumnWidth[3] = d;
|
||||
}
|
||||
|
||||
public int GetColumnWidth(int n)
|
||||
{
|
||||
if (n < 0 || n >= m_ColumnWidth.Length)
|
||||
return 0;
|
||||
|
||||
return m_ColumnWidth[n];
|
||||
}
|
||||
|
||||
public void Draw(int n, string col)
|
||||
{
|
||||
if (n < 0 || n >= m_ColumnWidth.Length || m_ColumnWidth[n] <= 0)
|
||||
EditorGUILayout.LabelField(col);
|
||||
|
||||
EditorGUILayout.LabelField(col, GUILayout.Width(m_ColumnWidth[n]));
|
||||
}
|
||||
|
||||
public void Draw(int n, float value)
|
||||
{
|
||||
Draw(n, string.Format("{0:f2}", value));
|
||||
}
|
||||
|
||||
public void Draw2(string col1, string col2)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
Draw(0, col1);
|
||||
Draw(1, col2);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public void Draw2(string label, float value)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
Draw(0, label);
|
||||
Draw(1, value);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public void Draw3(string col1, string col2, string col3)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
Draw(0, col1);
|
||||
Draw(1, col2);
|
||||
Draw(2, col3);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public void Draw3(string col1, float value2, float value3)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
Draw(0, col1);
|
||||
Draw(1, value2);
|
||||
Draw(2, value3);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public void Draw4(string col1, string col2, string col3, string col4)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
Draw(0, col1);
|
||||
Draw(1, col2);
|
||||
Draw(2, col3);
|
||||
Draw(3, col4);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public void Draw4Diff(string col1, float left, float right)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
Draw(0, col1);
|
||||
Draw(1, left);
|
||||
Draw(2, right);
|
||||
Draw(3, right - left);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public void Draw4(string col1, float value2, float value3, float value4)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
Draw(0, col1);
|
||||
Draw(1, value2);
|
||||
Draw(2, value3);
|
||||
Draw(3, value4);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
// GUIContent versions
|
||||
public void Draw(int n, GUIContent col)
|
||||
{
|
||||
if (n < 0 || n >= m_ColumnWidth.Length || m_ColumnWidth[n] <= 0)
|
||||
EditorGUILayout.LabelField(col);
|
||||
|
||||
EditorGUILayout.LabelField(col, GUILayout.Width(m_ColumnWidth[n]));
|
||||
}
|
||||
|
||||
public void Draw2(GUIContent col1, GUIContent col2)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
Draw(0, col1);
|
||||
Draw(1, col2);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public void Draw2(GUIContent label, float value)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
Draw(0, label);
|
||||
Draw(1, value);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public void Draw3(GUIContent col1, GUIContent col2, GUIContent col3)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
Draw(0, col1);
|
||||
Draw(1, col2);
|
||||
Draw(2, col3);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public void Draw3(GUIContent col1, float value2, float value3)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
Draw(0, col1);
|
||||
Draw(1, value2);
|
||||
Draw(2, value3);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public void Draw4(GUIContent col1, GUIContent col2, GUIContent col3, GUIContent col4)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
Draw(0, col1);
|
||||
Draw(1, col2);
|
||||
Draw(2, col3);
|
||||
Draw(3, col4);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public void Draw4Diff(GUIContent col1, float left, float right)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
Draw(0, col1);
|
||||
Draw(1, left);
|
||||
Draw(2, right);
|
||||
Draw(3, right - left);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public void Draw4(GUIContent col1, float value2, float value3, float value4)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
Draw(0, col1);
|
||||
Draw(1, value2);
|
||||
Draw(2, value3);
|
||||
Draw(3, value4);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a402cbe9b84984bd4a16cb20ca9c3bed
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 281fa44557d2f417aafc937bd5d93ba5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
class DepthSliceDropdown : AdvancedDropdown
|
||||
{
|
||||
class DepthSliceDropdownItem : AdvancedDropdownItem
|
||||
{
|
||||
public int depthSlice;
|
||||
public int depthSliceLeft;
|
||||
public int depthSliceRight;
|
||||
public DepthSliceDropdownItem(int depthSlice)
|
||||
: base(DepthSliceUI.DepthFilterToString(depthSlice))
|
||||
{
|
||||
this.depthSlice = depthSlice;
|
||||
depthSliceLeft = depthSlice;
|
||||
depthSliceRight = depthSlice;
|
||||
}
|
||||
public DepthSliceDropdownItem(int depthSliceLeft, int depthSliceRight, bool leftIsMain)
|
||||
: base(DepthSliceUI.DepthFilterToString(depthSliceLeft, depthSliceRight, leftIsMain))
|
||||
{
|
||||
depthSlice = Math.Max(depthSliceLeft, depthSliceRight);
|
||||
this.depthSliceLeft = depthSliceLeft;
|
||||
this.depthSliceRight = depthSliceRight;
|
||||
}
|
||||
}
|
||||
|
||||
Action<int, int, int> m_Callback = null;
|
||||
int m_DepthSliceCount;
|
||||
int m_DepthSliceCountRight;
|
||||
int m_CurrentDepthSliceA;
|
||||
int m_CurrentDepthSliceB;
|
||||
int m_DepthDiff;
|
||||
|
||||
static FieldInfo m_DataSourceFieldInfo;
|
||||
static Type m_DataSourceTypeInfo;
|
||||
static PropertyInfo m_SelectedIdsFieldInfo;
|
||||
|
||||
public DepthSliceDropdown(int depthSliceCount, int currentDepthSliceA, int currentDepthSliceB, Action<int, int, int> callback, int depthDiff, int depthSliceCountRight = ProfileAnalyzer.kDepthAll) : base(new AdvancedDropdownState())
|
||||
{
|
||||
m_DepthSliceCount = depthSliceCount;
|
||||
m_DepthSliceCountRight = depthSliceCountRight;
|
||||
m_CurrentDepthSliceA = currentDepthSliceA;
|
||||
m_CurrentDepthSliceB = currentDepthSliceB;
|
||||
m_Callback = callback;
|
||||
m_DepthDiff = depthDiff;
|
||||
if (m_DataSourceFieldInfo == null || m_DataSourceFieldInfo == null || m_SelectedIdsFieldInfo == null)
|
||||
{
|
||||
Assembly assem = typeof(AdvancedDropdown).Assembly;
|
||||
var advancedDropdownTypeInfo = typeof(AdvancedDropdown);
|
||||
m_DataSourceTypeInfo = assem.GetType("UnityEditor.IMGUI.Controls.CallbackDataSource");
|
||||
m_DataSourceFieldInfo = advancedDropdownTypeInfo.GetField("m_DataSource", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedIdsFieldInfo = m_DataSourceTypeInfo.GetProperty("selectedIDs", BindingFlags.Public | BindingFlags.Instance);
|
||||
}
|
||||
}
|
||||
|
||||
protected override AdvancedDropdownItem BuildRoot()
|
||||
{
|
||||
var root = new AdvancedDropdownItem("Depth Slice");
|
||||
var allItem = new DepthSliceDropdownItem(ProfileAnalyzer.kDepthAll);
|
||||
root.AddChild(allItem);
|
||||
if (m_CurrentDepthSliceA == ProfileAnalyzer.kDepthAll && m_CurrentDepthSliceB == ProfileAnalyzer.kDepthAll)
|
||||
(m_SelectedIdsFieldInfo.GetValue(m_DataSourceFieldInfo.GetValue(this)) as List<int>).Add(allItem.id);
|
||||
var count = m_DepthSliceCountRight == ProfileAnalyzer.kDepthAll ? m_DepthSliceCount :
|
||||
Math.Max(m_DepthSliceCount + Math.Max(0, m_DepthDiff), m_DepthSliceCountRight - Math.Min(0, m_DepthDiff));
|
||||
|
||||
var leftIsMain = m_DepthDiff < 0;
|
||||
var mainThreshold = leftIsMain ? m_DepthSliceCount : m_DepthSliceCountRight;
|
||||
var secondaryMinThreshold = Math.Abs(m_DepthDiff);
|
||||
var secondaryMaxThreshold = (leftIsMain ? m_DepthSliceCountRight : m_DepthSliceCount) + secondaryMinThreshold;
|
||||
|
||||
var startIndex = 1;
|
||||
for (int i = startIndex; i <= count; i++)
|
||||
{
|
||||
var selected = false;
|
||||
AdvancedDropdownItem child;
|
||||
if (m_DepthSliceCountRight != ProfileAnalyzer.kDepthAll)
|
||||
{
|
||||
var left = Mathf.Clamp(i - Math.Max(0, m_DepthDiff), 1, m_DepthSliceCount);
|
||||
var right = Mathf.Clamp(i - Math.Max(0, -m_DepthDiff), 1, m_DepthSliceCountRight);
|
||||
|
||||
if (m_DepthSliceCount <= 0)
|
||||
left = -1;
|
||||
else if (m_DepthSliceCountRight <= 0)
|
||||
right = -1;
|
||||
else
|
||||
{
|
||||
// Separators only make sense if there is data on both sides
|
||||
|
||||
// did we pass the threshold of the main's max depth and started clamping it down?
|
||||
if (i == mainThreshold + 1
|
||||
// ... or the threshold of the secondary's negative depth when adjusted for the depth diff, and stoped clamping it up?
|
||||
|| (secondaryMinThreshold != 0 && i == secondaryMinThreshold + 1)
|
||||
// ... or the threshold of the secondary's max depth when adjusted for the depth diff, and started clamping it down?
|
||||
|| (i == secondaryMaxThreshold + 1))
|
||||
root.AddSeparator();
|
||||
}
|
||||
|
||||
child = new DepthSliceDropdownItem(left, right, leftIsMain);
|
||||
selected = m_CurrentDepthSliceA == left && m_CurrentDepthSliceB == right;
|
||||
}
|
||||
else
|
||||
{
|
||||
child = new DepthSliceDropdownItem(i);
|
||||
selected = m_CurrentDepthSliceA == i;
|
||||
}
|
||||
root.AddChild(child);
|
||||
if (selected)
|
||||
(m_SelectedIdsFieldInfo.GetValue(m_DataSourceFieldInfo.GetValue(this)) as List<int>).Add(child.id);
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
protected override void ItemSelected(AdvancedDropdownItem item)
|
||||
{
|
||||
base.ItemSelected(item);
|
||||
if (m_Callback != null)
|
||||
{
|
||||
var sliceItem = (item as DepthSliceDropdownItem);
|
||||
m_Callback(sliceItem.depthSlice, sliceItem.depthSliceLeft, sliceItem.depthSliceRight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 71b9e5c19de243c386bd048443d1c5cc
|
||||
timeCreated: 1608205585
|
||||
@@ -0,0 +1,460 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Video;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
[Serializable]
|
||||
public class DepthSliceUI
|
||||
{
|
||||
[SerializeField] int m_DepthFilter = ProfileAnalyzer.kDepthAll;
|
||||
public int depthFilter {get { return m_DepthFilter; }}
|
||||
|
||||
[SerializeField] int m_DepthFilter1 = ProfileAnalyzer.kDepthAll;
|
||||
public int depthFilter1 {get { return m_DepthFilter1; }}
|
||||
|
||||
[SerializeField] int m_DepthFilter2 = ProfileAnalyzer.kDepthAll;
|
||||
public int depthFilter2 {get { return m_DepthFilter2; }}
|
||||
|
||||
[SerializeField] bool m_DepthFilterAuto = true;
|
||||
|
||||
[SerializeField] int m_MostCommonDepthDiff = 0;
|
||||
|
||||
int mostCommonDepthDiff
|
||||
{
|
||||
set
|
||||
{
|
||||
if (m_MostCommonDepthDiff != value)
|
||||
{
|
||||
m_MostCommonDepthDiff = value;
|
||||
UpdateAutoDepthTitleText();
|
||||
}
|
||||
}
|
||||
get
|
||||
{
|
||||
return m_MostCommonDepthDiff;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAutoDepthTitleText()
|
||||
{
|
||||
ProfileAnalyzerWindow.Styles.autoDepthTitle.text =
|
||||
string.Format(ProfileAnalyzerWindow.Styles.autoDepthTitleText, mostCommonDepthDiff);
|
||||
}
|
||||
|
||||
Action<bool> m_UpdateActiveTabCallback = null;
|
||||
public DepthSliceUI(Action<bool> updateActiveTabCallback)
|
||||
{
|
||||
m_UpdateActiveTabCallback = updateActiveTabCallback;
|
||||
UpdateAutoDepthTitleText();
|
||||
}
|
||||
|
||||
public void OnEnable(Action<bool> updateActiveTabCallback)
|
||||
{
|
||||
m_UpdateActiveTabCallback = updateActiveTabCallback;
|
||||
UpdateAutoDepthTitleText();
|
||||
}
|
||||
|
||||
enum ViewType
|
||||
{
|
||||
Single,
|
||||
Left,
|
||||
Right,
|
||||
Locked,
|
||||
}
|
||||
void DrawDepthFilterDropdown(GUIContent title, bool enabled, ProfileDataView view, Action<int, int, int> callback,
|
||||
ViewType viewType, ProfileDataView profileSingleView, ProfileDataView profileLeftView, ProfileDataView profileRightView)
|
||||
{
|
||||
if(title !=null)
|
||||
EditorGUILayout.LabelField(title, GUILayout.Width(ProfileAnalyzerWindow.LayoutSize.FilterOptionsEnumWidth));
|
||||
|
||||
int depthFilter = ProfileAnalyzer.kDepthAll;
|
||||
int depthFilterOther = ProfileAnalyzer.kDepthAll;
|
||||
var maxDepth = view.GetMaxDepth();
|
||||
var maxDepthLeft = ProfileAnalyzer.kDepthAll;
|
||||
var maxDepthRight = ProfileAnalyzer.kDepthAll;
|
||||
|
||||
var oldDepthFilter = ProfileAnalyzer.kDepthAll;
|
||||
var oldDepthFilterOtherLocked = ProfileAnalyzer.kDepthAll;
|
||||
var depthDiff = mostCommonDepthDiff;
|
||||
GUIContent content;
|
||||
switch (viewType)
|
||||
{
|
||||
case ViewType.Single:
|
||||
oldDepthFilter = m_DepthFilter;
|
||||
depthFilter = m_DepthFilter =
|
||||
m_DepthFilter == ProfileAnalyzer.kDepthAll ?
|
||||
ProfileAnalyzer.kDepthAll :
|
||||
profileSingleView.ClampToValidDepthValue(m_DepthFilter);
|
||||
content = new GUIContent(DepthFilterToString(depthFilter));
|
||||
depthFilterOther = depthFilter;
|
||||
depthDiff = 0;
|
||||
break;
|
||||
case ViewType.Left:
|
||||
oldDepthFilter = m_DepthFilter1;
|
||||
depthFilter = m_DepthFilter1 =
|
||||
m_DepthFilter1 == ProfileAnalyzer.kDepthAll ?
|
||||
ProfileAnalyzer.kDepthAll :
|
||||
profileLeftView.ClampToValidDepthValue(m_DepthFilter1);
|
||||
content = new GUIContent(DepthFilterToString(depthFilter));
|
||||
depthFilterOther = depthFilter;
|
||||
break;
|
||||
case ViewType.Right:
|
||||
oldDepthFilter = m_DepthFilter2;
|
||||
depthFilter = m_DepthFilter2 = m_DepthFilter2 == ProfileAnalyzer.kDepthAll
|
||||
? ProfileAnalyzer.kDepthAll
|
||||
: profileRightView.ClampToValidDepthValue(m_DepthFilter2);
|
||||
content = new GUIContent(DepthFilterToString(depthFilter));
|
||||
depthFilterOther = depthFilter;
|
||||
break;
|
||||
case ViewType.Locked:
|
||||
oldDepthFilter = m_DepthFilter1;
|
||||
oldDepthFilterOtherLocked = m_DepthFilter2;
|
||||
maxDepth = maxDepthLeft = profileLeftView.GetMaxDepth();
|
||||
maxDepthRight = profileRightView.GetMaxDepth();
|
||||
|
||||
ClampDepthFilterForAutoRespectingDiff(ref m_DepthFilter1, ref m_DepthFilter2, profileLeftView, profileRightView);
|
||||
|
||||
depthFilter = m_DepthFilter1;
|
||||
depthFilterOther = m_DepthFilter2;
|
||||
content = new GUIContent(DepthFilterToString(m_DepthFilter1, m_DepthFilter2, mostCommonDepthDiff < 0));
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
var lastEnabled = GUI.enabled;
|
||||
GUI.enabled = enabled;
|
||||
var rect = GUILayoutUtility.GetRect(content, EditorStyles.popup, GUILayout.MinWidth(ProfileAnalyzerWindow.LayoutSize.FilterOptionsEnumWidth));
|
||||
if (GUI.Button(rect, content, EditorStyles.popup))
|
||||
{
|
||||
var dropdown = new DepthSliceDropdown(maxDepth, depthFilter, depthFilterOther, (slice, left, right) =>
|
||||
{
|
||||
if (slice != depthFilter || (viewType == ViewType.Locked && (left != m_DepthFilter1 || right != m_DepthFilter2)))
|
||||
{
|
||||
callback(slice, left, right);
|
||||
UpdateDepthFilters(viewType == ViewType.Single, profileSingleView, profileLeftView, profileRightView);
|
||||
m_UpdateActiveTabCallback(true);
|
||||
}
|
||||
}, depthDiff, maxDepthRight);
|
||||
dropdown.Show(rect);
|
||||
EditorGUIUtility.ExitGUI();
|
||||
}
|
||||
else
|
||||
{
|
||||
// The depths can change because the data changed, not just because the user selected a different option in the dropdown
|
||||
// in that case, the depth filters need to perform a refresh
|
||||
if(oldDepthFilter != depthFilter || viewType == ViewType.Locked && oldDepthFilterOtherLocked != depthFilterOther)
|
||||
{
|
||||
UpdateDepthFilters(viewType == ViewType.Single, profileSingleView, profileLeftView, profileRightView);
|
||||
m_UpdateActiveTabCallback(true);
|
||||
}
|
||||
}
|
||||
GUI.enabled = lastEnabled;
|
||||
}
|
||||
|
||||
int CalcSliceMenuEntryIndex(int filterDepthLeft, int filterDepthRight, int leftMax, int rightMax)
|
||||
{
|
||||
return mostCommonDepthDiff > 0 ?
|
||||
filterDepthRight + Math.Max(0, filterDepthLeft - rightMax + (rightMax > 0 ? mostCommonDepthDiff : filterDepthLeft > 0 ? 1 : 0)) :
|
||||
filterDepthLeft + Math.Max(0, filterDepthRight - leftMax - (leftMax > 0 ? mostCommonDepthDiff : filterDepthRight > 0 ? -1 :0));
|
||||
}
|
||||
|
||||
void CalcAutoSlicesFromMenuEntryIndex(int depthSlcieMenuEntryIndex, ref int filterDepthLeft, ref int filterDepthRight, int leftMax, int rightMax)
|
||||
{
|
||||
if (mostCommonDepthDiff > 0)
|
||||
{
|
||||
filterDepthRight = Mathf.Clamp(depthSlcieMenuEntryIndex, 1, rightMax);
|
||||
filterDepthLeft = Mathf.Clamp(depthSlcieMenuEntryIndex - (rightMax > 0 ? mostCommonDepthDiff : 0), 1, leftMax);
|
||||
}
|
||||
else
|
||||
{
|
||||
filterDepthLeft = Mathf.Clamp(depthSlcieMenuEntryIndex, 1, leftMax);
|
||||
filterDepthRight = Mathf.Clamp(depthSlcieMenuEntryIndex + (leftMax > 0 ? mostCommonDepthDiff : 0), 1, rightMax);
|
||||
}
|
||||
// if a side has no depth, only allow All
|
||||
if (leftMax <= 0)
|
||||
filterDepthLeft = -1;
|
||||
if (rightMax <= 0)
|
||||
filterDepthRight = -1;
|
||||
}
|
||||
|
||||
void ClampDepthFilterForAutoRespectingDiff(ref int filterDepthLeft, ref int filterDepthRight, ProfileDataView profileLeftView, ProfileDataView profileRightView)
|
||||
{
|
||||
if (filterDepthLeft == ProfileAnalyzer.kDepthAll && filterDepthRight == ProfileAnalyzer.kDepthAll)
|
||||
{
|
||||
// nothing to do here, keep showing all
|
||||
return;
|
||||
}
|
||||
|
||||
var leftMax = profileLeftView.GetMaxDepth();
|
||||
var rightMax = profileRightView.GetMaxDepth();
|
||||
|
||||
var sliceMenuEntryIndex = CalcSliceMenuEntryIndex(filterDepthLeft, filterDepthRight, leftMax, rightMax);
|
||||
CalcAutoSlicesFromMenuEntryIndex(sliceMenuEntryIndex, ref filterDepthLeft, ref filterDepthRight, leftMax, rightMax);
|
||||
}
|
||||
|
||||
internal void DrawDepthFilter(bool isAnalysisRunning, bool singleView,
|
||||
ProfileDataView profileSingleView, ProfileDataView profileLeftView, ProfileDataView profileRightView)
|
||||
{
|
||||
bool lastEnabled = GUI.enabled;
|
||||
bool enabled = !isAnalysisRunning;
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (singleView)
|
||||
{
|
||||
EditorGUILayout.LabelField(ProfileAnalyzerWindow.Styles.depthTitle, GUILayout.Width(ProfileAnalyzerWindow.LayoutSize.FilterOptionsLeftLabelWidth));
|
||||
DrawDepthFilterDropdown(null, enabled,
|
||||
profileSingleView, (primary, left, right) => m_DepthFilter = primary,
|
||||
ViewType.Single, profileSingleView, profileLeftView, profileRightView);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField(ProfileAnalyzerWindow.Styles.depthTitle, GUILayout.Width(ProfileAnalyzerWindow.LayoutSize.FilterOptionsLeftLabelWidth));
|
||||
|
||||
if (m_DepthFilterAuto)
|
||||
{
|
||||
DrawDepthFilterDropdown(null, enabled, profileLeftView, (primary, left, right) =>
|
||||
{
|
||||
m_DepthFilter1 = left;
|
||||
m_DepthFilter2 = right;
|
||||
ClampDepthFilterForAutoRespectingDiff(ref m_DepthFilter1, ref m_DepthFilter2, profileLeftView, profileRightView);
|
||||
},
|
||||
ViewType.Locked, profileSingleView, profileLeftView, profileRightView);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
DrawDepthFilterDropdown(ProfileAnalyzerWindow.Styles.leftDepthTitle, enabled, profileLeftView,
|
||||
(primary, left, right) => m_DepthFilter1 = primary,
|
||||
ViewType.Left, profileSingleView, profileLeftView, profileRightView);
|
||||
|
||||
DrawDepthFilterDropdown(ProfileAnalyzerWindow.Styles.rightDepthTitle, enabled && !m_DepthFilterAuto, profileRightView,
|
||||
(primary, left, right) => m_DepthFilter2 = primary,
|
||||
ViewType.Right, profileSingleView, profileLeftView, profileRightView);
|
||||
}
|
||||
bool lastDepthFilterLock = m_DepthFilterAuto;
|
||||
GUI.enabled = enabled;
|
||||
m_DepthFilterAuto = EditorGUILayout.ToggleLeft(ProfileAnalyzerWindow.Styles.autoDepthTitle, m_DepthFilterAuto);
|
||||
GUI.enabled = lastEnabled;
|
||||
if (m_DepthFilterAuto != lastDepthFilterLock)
|
||||
{
|
||||
if (UpdateDepthFilters(singleView, profileSingleView, profileLeftView, profileRightView))
|
||||
m_UpdateActiveTabCallback(true);
|
||||
}
|
||||
}
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
internal bool UpdateDepthFilters(bool singleView, ProfileDataView profileSingleView, ProfileDataView profileLeftView, ProfileDataView profileRightView)
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
if (!singleView)
|
||||
{
|
||||
// First respect the auto flag
|
||||
if (UpdateAutoDepthFilter(profileLeftView, profileRightView))
|
||||
changed = true;
|
||||
|
||||
// Make sure Single matches the updated comparison view
|
||||
if (profileLeftView.path == profileSingleView.path)
|
||||
{
|
||||
// Use same filter on single view if its the same file
|
||||
if (m_DepthFilter != m_DepthFilter1)
|
||||
{
|
||||
m_DepthFilter = m_DepthFilter1;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (profileRightView.path == profileSingleView.path)
|
||||
{
|
||||
// Use same filter on single view if its the same file
|
||||
if (m_DepthFilter != m_DepthFilter2)
|
||||
{
|
||||
m_DepthFilter = m_DepthFilter2;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make sure comparisons match updated single view
|
||||
if (profileLeftView.path == profileSingleView.path)
|
||||
{
|
||||
// Use same filter on comparison left view if its the same file
|
||||
if (m_DepthFilter1 != m_DepthFilter)
|
||||
{
|
||||
m_DepthFilter1 = m_DepthFilter;
|
||||
changed = true;
|
||||
}
|
||||
if (m_DepthFilterAuto)
|
||||
{
|
||||
var newDepthFilter2 = m_DepthFilter;
|
||||
ClampDepthFilterForAutoRespectingDiff(ref m_DepthFilter1, ref newDepthFilter2, profileLeftView, profileRightView);
|
||||
|
||||
if (m_DepthFilter2 != newDepthFilter2)
|
||||
{
|
||||
m_DepthFilter2 = newDepthFilter2;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (UpdateAutoDepthFilter(profileLeftView, profileRightView))
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (UpdateAutoDepthFilter(profileLeftView, profileRightView))
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (profileRightView.path == profileSingleView.path)
|
||||
{
|
||||
// Use same filter on comparison right view if its the same file
|
||||
if (m_DepthFilter2 != m_DepthFilter)
|
||||
{
|
||||
m_DepthFilter2 = m_DepthFilter;
|
||||
changed = true;
|
||||
}
|
||||
if (m_DepthFilterAuto)
|
||||
{
|
||||
var newDepthFilter1 = m_DepthFilter;
|
||||
ClampDepthFilterForAutoRespectingDiff(ref newDepthFilter1, ref m_DepthFilter2, profileLeftView, profileRightView);
|
||||
if (m_DepthFilter1 != newDepthFilter1)
|
||||
{
|
||||
m_DepthFilter1 = newDepthFilter1;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (UpdateAutoDepthFilter(profileLeftView, profileRightView))
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
int CalculateDepthDifference(ProfileAnalysis leftAnalysis, ProfileAnalysis rightAnalysis, List<MarkerPairing> pairings)
|
||||
{
|
||||
if (pairings.Count <= 0)
|
||||
{
|
||||
mostCommonDepthDiff = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
var leftMarkers = leftAnalysis.GetMarkers();
|
||||
var rightMarkers = rightAnalysis.GetMarkers();
|
||||
|
||||
int totalCount = 0;
|
||||
Dictionary<int, int> depthDifferences = new Dictionary<int, int>();
|
||||
foreach (var pairing in pairings)
|
||||
{
|
||||
if (pairing.leftIndex >= 0 && pairing.rightIndex >= 0)
|
||||
{
|
||||
MarkerData leftMarker = leftMarkers[pairing.leftIndex];
|
||||
MarkerData rightMarker = rightMarkers[pairing.rightIndex];
|
||||
int markerDepthDiff = rightMarker.minDepth - leftMarker.minDepth;
|
||||
|
||||
int value = 0;
|
||||
depthDifferences.TryGetValue(markerDepthDiff, out value);
|
||||
depthDifferences[markerDepthDiff] = value + 1;
|
||||
totalCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
var newDepthDiff = 0;
|
||||
|
||||
// Find most common depth difference
|
||||
int maxCount = 0;
|
||||
foreach (var diff in depthDifferences.Keys)
|
||||
{
|
||||
if (depthDifferences[diff] > maxCount)
|
||||
{
|
||||
maxCount = depthDifferences[diff];
|
||||
newDepthDiff = diff;
|
||||
}
|
||||
}
|
||||
|
||||
return mostCommonDepthDiff = newDepthDiff;
|
||||
}
|
||||
|
||||
bool UpdateAutoDepthFilter(ProfileDataView profileLeftView, ProfileDataView profileRightView)
|
||||
{
|
||||
if (m_DepthFilterAuto)
|
||||
{
|
||||
var newDepthFilter1 = m_DepthFilter1;
|
||||
var newDepthFilter2 = m_DepthFilter2;
|
||||
ClampDepthFilterForAutoRespectingDiff(ref newDepthFilter1, ref newDepthFilter2, profileLeftView, profileRightView);
|
||||
if (m_DepthFilter1 != newDepthFilter1)
|
||||
{
|
||||
m_DepthFilter1 = newDepthFilter1;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_DepthFilter2 != newDepthFilter2)
|
||||
{
|
||||
m_DepthFilter2 = newDepthFilter2;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal bool UpdateDepthForCompareSync(ProfileAnalysis leftAnalysis, ProfileAnalysis rightAnalysis, List<MarkerPairing> pairings, ProfileDataView profileLeftView, ProfileDataView profileRightView)
|
||||
{
|
||||
int originalDepthDiff = mostCommonDepthDiff;
|
||||
int newDepthDiff = CalculateDepthDifference(leftAnalysis, rightAnalysis, pairings);
|
||||
if (newDepthDiff != originalDepthDiff)
|
||||
{
|
||||
UpdateAutoDepthFilter(profileLeftView, profileRightView);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal GUIContent GetUIInfo(bool compare)
|
||||
{
|
||||
GUIContent info;
|
||||
if (compare && m_DepthFilter1 == ProfileAnalyzer.kDepthAll && m_DepthFilter2 == ProfileAnalyzer.kDepthAll ||
|
||||
!compare && depthFilter == ProfileAnalyzer.kDepthAll)
|
||||
{
|
||||
info = new GUIContent("(All depths)", string.Format("{0}\n\nSet depth 1 to get an overview of the frame", ProfileAnalyzerWindow.Styles.medianFrameTooltip));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (compare && depthFilter1 != depthFilter2)
|
||||
{
|
||||
if (m_DepthFilter1 == ProfileAnalyzer.kDepthAll)
|
||||
info = new GUIContent(string.Format("(Filtered to 'all' depths in the first data set, and depth '{0}' in the second)", m_DepthFilter2), ProfileAnalyzerWindow.Styles.medianFrameTooltip);
|
||||
else if (m_DepthFilter2 == ProfileAnalyzer.kDepthAll)
|
||||
info = new GUIContent(string.Format("(Filtered to depth '{0}' in the first data set, and 'all' depths in the second)", m_DepthFilter1), ProfileAnalyzerWindow.Styles.medianFrameTooltip);
|
||||
else
|
||||
info = new GUIContent(string.Format("(Filtered to depth '{0}' in the first data set, and depth '{1}' in the second)", m_DepthFilter1, depthFilter2), ProfileAnalyzerWindow.Styles.medianFrameTooltip);
|
||||
}
|
||||
else
|
||||
info = new GUIContent(string.Format("(Filtered to depth '{0}' only)", compare ? m_DepthFilter1 : depthFilter), ProfileAnalyzerWindow.Styles.medianFrameTooltip);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
public static string DepthFilterToString(int depthFilter)
|
||||
{
|
||||
return depthFilter == ProfileAnalyzer.kDepthAll ? "All" : depthFilter.ToString();
|
||||
}
|
||||
|
||||
public static string DepthFilterToString(int depthSliceLeft, int depthSliceRight, bool leftIsMain)
|
||||
{
|
||||
if(depthSliceLeft != depthSliceRight)
|
||||
{
|
||||
if (leftIsMain)
|
||||
return string.Format("{0} ({1}{2})", DepthFilterToString(depthSliceLeft), ProfileAnalyzerWindow.Styles.rightDepthTitle.text, DepthFilterToString(depthSliceRight));
|
||||
else
|
||||
return string.Format("{0} ({1}{2})", DepthFilterToString(depthSliceRight), ProfileAnalyzerWindow.Styles.leftDepthTitle.text, DepthFilterToString(depthSliceLeft));
|
||||
}
|
||||
return DepthFilterToString(depthSliceLeft);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d4bf3d974bf4f74a15e72b2e0a8ffa2
|
||||
timeCreated: 1608212438
|
||||
@@ -0,0 +1,210 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
internal class Draw2D
|
||||
{
|
||||
public enum Origin
|
||||
{
|
||||
TopLeft,
|
||||
BottomLeft
|
||||
};
|
||||
|
||||
Origin m_Origin = Origin.TopLeft;
|
||||
GUIStyle m_GLStyle;
|
||||
string m_ShaderName;
|
||||
Material m_Material;
|
||||
Rect m_Rect;
|
||||
Vector4 m_ClipRect;
|
||||
bool m_ClipRectEnabled = false;
|
||||
|
||||
public Draw2D(string shaderName)
|
||||
{
|
||||
m_ShaderName = shaderName;
|
||||
CheckAndSetupMaterial();
|
||||
}
|
||||
|
||||
public bool CheckAndSetupMaterial()
|
||||
{
|
||||
if (m_Material == null)
|
||||
{
|
||||
var shader = Shader.Find(m_ShaderName);
|
||||
if (shader == null)
|
||||
{
|
||||
Debug.LogFormat("Unable to locate shader {0}", m_ShaderName);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_Material = new Material(shader);
|
||||
if (m_Material == null)
|
||||
{
|
||||
Debug.LogFormat("Unable to create material for {0}", m_ShaderName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsMaterialValid()
|
||||
{
|
||||
if (m_Material == null)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
if (m_GLStyle == null)
|
||||
{
|
||||
m_GLStyle = new GUIStyle(GUI.skin.box);
|
||||
m_GLStyle.padding = new RectOffset(0, 0, 0, 0);
|
||||
m_GLStyle.margin = new RectOffset(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetClipRect(Rect clipRect)
|
||||
{
|
||||
m_ClipRect = new Vector4(clipRect.x, clipRect.y, clipRect.x + clipRect.width, clipRect.y + clipRect.height);
|
||||
m_ClipRectEnabled = true;
|
||||
|
||||
if (CheckAndSetupMaterial())
|
||||
{
|
||||
m_Material.SetFloat("_UseClipRect", m_ClipRectEnabled ? 1f : 0f);
|
||||
m_Material.SetVector("_ClipRect", m_ClipRect);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearClipRect()
|
||||
{
|
||||
m_ClipRectEnabled = false;
|
||||
|
||||
if (CheckAndSetupMaterial())
|
||||
{
|
||||
m_Material.SetFloat("_UseClipRect", m_ClipRectEnabled ? 1f : 0f);
|
||||
m_Material.SetVector("_ClipRect", m_ClipRect);
|
||||
}
|
||||
}
|
||||
|
||||
public Rect GetClipRect()
|
||||
{
|
||||
return new Rect(m_ClipRect.x, m_ClipRect.y, m_ClipRect.z - m_ClipRect.x, m_ClipRect.w - m_ClipRect.y);
|
||||
}
|
||||
|
||||
public bool DrawStart(Rect r, Origin origin = Origin.TopLeft)
|
||||
{
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
return false;
|
||||
|
||||
if (!CheckAndSetupMaterial())
|
||||
return false;
|
||||
|
||||
m_Material.SetPass(0);
|
||||
|
||||
m_Rect = r;
|
||||
m_Origin = origin;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool DrawStart(float w, float h, Origin origin = Origin.TopLeft, GUIStyle style = null)
|
||||
{
|
||||
Rect r = GUILayoutUtility.GetRect(w, h, style == null ? m_GLStyle : style, GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(false));
|
||||
return DrawStart(r, origin);
|
||||
}
|
||||
|
||||
public void DrawEnd()
|
||||
{
|
||||
}
|
||||
|
||||
public void Translate(ref float x, ref float y)
|
||||
{
|
||||
// Translation done CPU side so we have world space coords in the shader for clipping.
|
||||
if (m_Origin == Origin.BottomLeft)
|
||||
{
|
||||
x = m_Rect.xMin + x;
|
||||
y = m_Rect.yMax - y;
|
||||
}
|
||||
else
|
||||
{
|
||||
x = m_Rect.xMin + x;
|
||||
y = m_Rect.yMin + y;
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawFilledBox(float x, float y, float w, float h, Color col)
|
||||
{
|
||||
float x2 = x + w;
|
||||
float y2 = y + h;
|
||||
|
||||
Translate(ref x, ref y);
|
||||
Translate(ref x2, ref y2);
|
||||
|
||||
if (m_Origin == Origin.BottomLeft)
|
||||
{
|
||||
GL.Begin(GL.TRIANGLE_STRIP);
|
||||
GL.Color(col);
|
||||
GL.Vertex3(x, y, 0);
|
||||
GL.Vertex3(x, y2, 0);
|
||||
GL.Vertex3(x2, y, 0);
|
||||
GL.Vertex3(x2, y2, 0);
|
||||
GL.End();
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.Begin(GL.TRIANGLE_STRIP);
|
||||
GL.Color(col);
|
||||
GL.Vertex3(x, y, 0);
|
||||
GL.Vertex3(x2, y, 0);
|
||||
GL.Vertex3(x, y2, 0);
|
||||
GL.Vertex3(x2, y2, 0);
|
||||
GL.End();
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawFilledBox(float x, float y, float w, float h, float r, float g, float b)
|
||||
{
|
||||
DrawFilledBox(x, y, w, h, new Color(r, g, b));
|
||||
}
|
||||
|
||||
public void DrawLine(float x, float y, float x2, float y2, Color col)
|
||||
{
|
||||
Translate(ref x, ref y);
|
||||
Translate(ref x2, ref y2);
|
||||
|
||||
GL.Begin(GL.LINES);
|
||||
GL.Color(col);
|
||||
GL.Vertex3(x, y, 0);
|
||||
GL.Vertex3(x2, y2, 0);
|
||||
GL.End();
|
||||
}
|
||||
|
||||
public void DrawLine(float x, float y, float x2, float y2, float r, float g, float b)
|
||||
{
|
||||
DrawLine(x, y, x2, y2, new Color(r, g, b));
|
||||
}
|
||||
|
||||
public void DrawBox(float x, float y, float w, float h, Color col)
|
||||
{
|
||||
float x2 = x + w;
|
||||
float y2 = y + h;
|
||||
|
||||
Translate(ref x, ref y);
|
||||
Translate(ref x2, ref y2);
|
||||
|
||||
GL.Begin(GL.LINE_STRIP);
|
||||
GL.Color(col);
|
||||
GL.Vertex3(x, y, 0);
|
||||
GL.Vertex3(x2, y, 0);
|
||||
GL.Vertex3(x2, y2, 0);
|
||||
GL.Vertex3(x, y2, 0);
|
||||
GL.Vertex3(x, y, 0);
|
||||
GL.End();
|
||||
}
|
||||
|
||||
public void DrawBox(float x, float y, float w, float h, float r, float g, float b)
|
||||
{
|
||||
DrawBox(x, y, w, h, new Color(r, g, b));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c27a543a692ef4bf19459f25898a0ba9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
[Serializable]
|
||||
internal class FrameSummary
|
||||
{
|
||||
public double msTotal;
|
||||
public int first;
|
||||
public int last;
|
||||
public int count; // Valid frame count may not be last-first
|
||||
|
||||
public float msMean;
|
||||
public float msMedian;
|
||||
public float msLowerQuartile;
|
||||
public float msUpperQuartile;
|
||||
public float msMin;
|
||||
public float msMax;
|
||||
|
||||
public int medianFrameIndex;
|
||||
public int minFrameIndex;
|
||||
public int maxFrameIndex;
|
||||
|
||||
public int maxMarkerDepth;
|
||||
public int totalMarkers;
|
||||
public int markerCountMax; // Largest marker count (over all frames)
|
||||
public float markerCountMaxMean; // Largest marker count mean
|
||||
|
||||
public int[] buckets = new int[20]; // Each bucket contains 'number of frames' for frametime in that range
|
||||
public List<FrameTime> frames = new List<FrameTime>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af62f7ba5c15e47ceb426c7745e31835
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
[Serializable]
|
||||
/// <summary>
|
||||
/// Metrics related to an individual frame
|
||||
/// </summary>
|
||||
internal struct FrameTime : IComparable<FrameTime>
|
||||
{
|
||||
/// <summary>Duration in the frame in milliseconds</summary>
|
||||
public float ms;
|
||||
/// <summary>Index of which frame this time duration occured on. A zero based frame index</summary>
|
||||
public int frameIndex;
|
||||
/// <summary>Number of occurrences</summary>
|
||||
public int count;
|
||||
|
||||
/// <summary>Initialise FrameTime</summary>
|
||||
/// <param name="index"> The frame index</param>
|
||||
/// <param name="msTime"> The duration of the frame in milliseconds</param>
|
||||
/// <param name="_count"> The number of occurrences</param>
|
||||
public FrameTime(int index, float msTime, int _count)
|
||||
{
|
||||
frameIndex = index;
|
||||
ms = msTime;
|
||||
count = _count;
|
||||
}
|
||||
|
||||
/// <summary>Initialise from another FrameTime</summary>
|
||||
/// <param name="t"> The FrameTime to assign</param>
|
||||
public FrameTime(FrameTime t)
|
||||
{
|
||||
frameIndex = t.frameIndex;
|
||||
ms = t.ms;
|
||||
count = t.count;
|
||||
}
|
||||
|
||||
/// <summary>Compare the time duration between the frames. Used for sorting in ascending order</summary>
|
||||
/// <param name="other"> The other FrameTime to compare </param>
|
||||
/// <returns>-1 if this is smaller, 0 if the same, 1 if this is larger</returns>
|
||||
public int CompareTo(FrameTime other)
|
||||
{
|
||||
if (ms == other.ms)
|
||||
{
|
||||
// secondary sort by frame index order
|
||||
return frameIndex.CompareTo(other.frameIndex);
|
||||
}
|
||||
|
||||
return ms.CompareTo(other.ms);
|
||||
}
|
||||
|
||||
/// <summary>Compare the time duration between two FrameTimes. Used for sorting in ascending order</summary>
|
||||
/// <param name="a"> The first FrameTime to compare </param>
|
||||
/// <param name="b"> The second FrameTime to compare </param>
|
||||
/// <returns>-1 if a is smaller, 0 if the same, 1 if a is larger</returns>
|
||||
public static int CompareMs(FrameTime a, FrameTime b)
|
||||
{
|
||||
if (a.ms == b.ms)
|
||||
{
|
||||
// secondary sort by frame index order
|
||||
return a.frameIndex.CompareTo(b.frameIndex);
|
||||
}
|
||||
|
||||
return a.ms.CompareTo(b.ms);
|
||||
}
|
||||
|
||||
/// <summary>Compare the instance count between two FrameTimes. Used for sorting in ascending order</summary>
|
||||
/// <param name="a"> The first FrameTime to compare </param>
|
||||
/// <param name="b"> The second FrameTime to compare </param>
|
||||
/// <returns>-1 if a is smaller, 0 if the same, 1 if a is larger</returns>
|
||||
public static int CompareCount(FrameTime a, FrameTime b)
|
||||
{
|
||||
if (a.count == b.count)
|
||||
{
|
||||
// secondary sort by frame index order
|
||||
return a.frameIndex.CompareTo(b.frameIndex);
|
||||
}
|
||||
|
||||
return a.count.CompareTo(b.count);
|
||||
}
|
||||
|
||||
/// <summary>Compare the time duration between two FrameTimes. Used for sorting in descending order</summary>
|
||||
/// <param name="a"> The first FrameTime to compare </param>
|
||||
/// <param name="b"> The second FrameTime to compare </param>
|
||||
/// <returns>-1 if a is larger, 0 if the same, 1 if a is smaller</returns>
|
||||
public static int CompareMsDescending(FrameTime a, FrameTime b)
|
||||
{
|
||||
if (a.ms == b.ms)
|
||||
{
|
||||
// secondary sort by frame index order
|
||||
return a.frameIndex.CompareTo(b.frameIndex);
|
||||
}
|
||||
|
||||
return -a.ms.CompareTo(b.ms);
|
||||
}
|
||||
|
||||
/// <summary>Compare the instance count between two FrameTimes. Used for sorting in descending order</summary>
|
||||
/// <param name="a"> The first FrameTime to compare </param>
|
||||
/// <param name="b"> The second FrameTime to compare </param>
|
||||
/// <returns>-1 if a is larger, 0 if the same, 1 if a is smaller</returns>
|
||||
public static int CompareCountDescending(FrameTime a, FrameTime b)
|
||||
{
|
||||
if (a.count == b.count)
|
||||
{
|
||||
// secondary sort by frame index order
|
||||
return a.frameIndex.CompareTo(b.frameIndex);
|
||||
}
|
||||
|
||||
return -a.count.CompareTo(b.count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11b7852f3babc4a938ba8e18940df7c5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54a4b8c3a2d924a4fb740b9044db9694
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,131 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
internal class Histogram
|
||||
{
|
||||
Draw2D m_2D;
|
||||
Color m_ColorBarBackground;
|
||||
DisplayUnits m_Units;
|
||||
|
||||
public void SetUnits(Units units)
|
||||
{
|
||||
m_Units = new DisplayUnits(units);
|
||||
}
|
||||
|
||||
public Histogram(Draw2D draw2D, Units units)
|
||||
{
|
||||
m_2D = draw2D;
|
||||
SetUnits(units);
|
||||
m_ColorBarBackground = new Color(0.5f, 0.5f, 0.5f);
|
||||
}
|
||||
|
||||
public Histogram(Draw2D draw2D, Units units, Color barBackground)
|
||||
{
|
||||
m_2D = draw2D;
|
||||
SetUnits(units);
|
||||
m_ColorBarBackground = barBackground;
|
||||
}
|
||||
|
||||
public void DrawStart(float width)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal(GUILayout.Width(width + 10));
|
||||
|
||||
EditorGUILayout.BeginVertical();
|
||||
}
|
||||
|
||||
public void DrawEnd(float width, float min, float max, float spacing)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
float halfWidth = width / 2;
|
||||
GUIStyle leftAlignStyle = new GUIStyle(GUI.skin.label);
|
||||
leftAlignStyle.contentOffset = new Vector2(-5, 0);
|
||||
leftAlignStyle.alignment = TextAnchor.MiddleLeft;
|
||||
GUIStyle rightAlignStyle = new GUIStyle(GUI.skin.label);
|
||||
rightAlignStyle.contentOffset = new Vector2(-5, 0);
|
||||
rightAlignStyle.alignment = TextAnchor.MiddleRight;
|
||||
EditorGUILayout.LabelField(m_Units.ToString(min, false, 5, true), leftAlignStyle, GUILayout.Width(halfWidth));
|
||||
EditorGUILayout.LabelField(m_Units.ToString(max, false, 5, true), rightAlignStyle, GUILayout.Width(halfWidth));
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public void DrawBackground(float width, float height, int bucketCount, float min, float max, float spacing)
|
||||
{
|
||||
//bucketCount = (range == 0f) ? 1 : bucketCount;
|
||||
|
||||
float x = (spacing / 2);
|
||||
float y = 0;
|
||||
float w = ((width + spacing) / bucketCount) - spacing;
|
||||
float h = height;
|
||||
|
||||
for (int i = 0; i < bucketCount; i++)
|
||||
{
|
||||
m_2D.DrawFilledBox(x, y, w, h, m_ColorBarBackground);
|
||||
x += w;
|
||||
x += spacing;
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawData(float width, float height, int[] buckets, int totalFrameCount, float min, float max, Color barColor, float spacing)
|
||||
{
|
||||
float range = max - min;
|
||||
|
||||
//int bucketCount = (range == 0f) ? 1 : buckets.Length;
|
||||
int bucketCount = buckets.Length;
|
||||
|
||||
float x = (spacing / 2);
|
||||
float y = 0;
|
||||
float w = ((width + spacing) / bucketCount) - spacing;
|
||||
float h = height;
|
||||
|
||||
float bucketWidth = (range / bucketCount);
|
||||
Rect rect = GUILayoutUtility.GetLastRect();
|
||||
for (int bucketAt = 0; bucketAt < bucketCount; bucketAt++)
|
||||
{
|
||||
var count = buckets[bucketAt];
|
||||
|
||||
float barHeight = (h * count) / totalFrameCount;
|
||||
if (count > 0) // Make sure we always slow a small bar if non zero
|
||||
barHeight = Mathf.Max(1.0f, barHeight);
|
||||
m_2D.DrawFilledBox(x, y, w, barHeight, barColor);
|
||||
|
||||
float bucketStart = min + (bucketAt * bucketWidth);
|
||||
float bucketEnd = bucketStart + bucketWidth;
|
||||
|
||||
var tooltip = string.Format("{0}-{1}\n{2} {3}\n\nBar width: {4}",
|
||||
m_Units.ToTooltipString(bucketStart, false),
|
||||
m_Units.ToTooltipString(bucketEnd, true),
|
||||
count,
|
||||
count == 1 ? "frame" : "frames",
|
||||
m_Units.ToTooltipString(bucketWidth, true)
|
||||
);
|
||||
|
||||
var content = new GUIContent("", tooltip);
|
||||
GUI.Label(new Rect(rect.x + x, rect.y + y, w, h), content);
|
||||
|
||||
x += w;
|
||||
x += spacing;
|
||||
}
|
||||
}
|
||||
|
||||
public void Draw(float width, float height, int[] buckets, int totalFrameCount, float min, float max, Color barColor)
|
||||
{
|
||||
DrawStart(width);
|
||||
|
||||
float spacing = 2;
|
||||
|
||||
if (m_2D.DrawStart(width, height, Draw2D.Origin.BottomLeft))
|
||||
{
|
||||
DrawBackground(width, height, buckets.Length, min, max, spacing);
|
||||
DrawData(width, height, buckets, totalFrameCount, min, max, barColor, spacing);
|
||||
m_2D.DrawEnd();
|
||||
}
|
||||
|
||||
DrawEnd(width, min, max, spacing);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dbcb296fb16194f04af7827f37fb2187
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using UnityEngine.Assertions;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
internal class MarkerColumnFilter
|
||||
{
|
||||
public enum Mode
|
||||
{
|
||||
TimeAndCount,
|
||||
Time,
|
||||
Totals,
|
||||
TimeWithTotals,
|
||||
CountTotals,
|
||||
CountPerFrame,
|
||||
Depth,
|
||||
Threads,
|
||||
Custom,
|
||||
};
|
||||
|
||||
public static readonly string[] ModeNames =
|
||||
{
|
||||
"Time and Count",
|
||||
"Time",
|
||||
"Totals",
|
||||
"Time With Totals",
|
||||
"Count Totals",
|
||||
"Count Per Frame",
|
||||
"Depths",
|
||||
"Threads",
|
||||
"Custom",
|
||||
};
|
||||
public static readonly int[] ModeValues = (int[])Enum.GetValues(typeof(Mode));
|
||||
|
||||
public Mode mode;
|
||||
public int[] visibleColumns;
|
||||
|
||||
public MarkerColumnFilter(Mode newMode)
|
||||
{
|
||||
Assert.AreEqual(ModeNames.Length, ModeValues.Length, "Number of ModeNames should match number of enum values ModeValues: You probably forgot to update one of them.");
|
||||
|
||||
mode = newMode;
|
||||
if (mode == Mode.Custom)
|
||||
mode = Mode.TimeAndCount;
|
||||
|
||||
visibleColumns = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 814ccc2d0c490cf4b871d13ab2e4b7c3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,343 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
[Serializable]
|
||||
internal class MarkerData : IComparable<MarkerData>
|
||||
{
|
||||
public string name;
|
||||
public int nameLowerCaseHash; // lower case name hash for faster comparisons
|
||||
|
||||
public double msTotal; // total time of this marker on a frame
|
||||
public int count; // total number of marker calls in the timeline (multiple per frame)
|
||||
public int countMin; // min count per frame
|
||||
public int countMax; // max count per frame
|
||||
public float countMean; // mean over all frames
|
||||
public int countMedian; // median over all frames
|
||||
public int countLowerQuartile; // over all frames
|
||||
public int countUpperQuartile; // over all frames
|
||||
public float countStandardDeviation;
|
||||
public int lastFrame;
|
||||
public int presentOnFrameCount; // number of frames containing this marker
|
||||
public int firstFrameIndex;
|
||||
public float msMean; // mean over all frames
|
||||
public float msMedian; // median over all frames
|
||||
public float msLowerQuartile; // over all frames
|
||||
public float msUpperQuartile; // over all frames
|
||||
public float msMin; // min total time per frame
|
||||
public float msMax; // max total time per frame
|
||||
public float msStandardDeviation;
|
||||
public int minIndividualFrameIndex;
|
||||
public int maxIndividualFrameIndex;
|
||||
public float msMinIndividual; // min individual function call
|
||||
public float msMaxIndividual; // max individual function call
|
||||
public float msAtMedian; // time at median frame
|
||||
public int medianFrameIndex; // frame this markers median value is found on
|
||||
public int minFrameIndex;
|
||||
public int maxFrameIndex;
|
||||
public int minDepth;
|
||||
public int maxDepth;
|
||||
public List<string> threads;
|
||||
|
||||
const int bucketCount = 20;
|
||||
public int[] buckets; // Each bucket contains 'number of frames' for 'sum of markers in the frame' in that range
|
||||
public int[] countBuckets; // Each bucket contains 'number of frames' for 'count in the frame' in that range
|
||||
public List<FrameTime> frames;
|
||||
|
||||
public double timeRemoved;
|
||||
public double timeIgnored;
|
||||
|
||||
public MarkerData(string markerName)
|
||||
{
|
||||
buckets = new int[bucketCount];
|
||||
countBuckets = new int[bucketCount];
|
||||
frames = new List<FrameTime>();
|
||||
threads = new List<string>();
|
||||
|
||||
name = markerName;
|
||||
nameLowerCaseHash = markerName.ToLower().GetHashCode();
|
||||
msTotal = 0.0;
|
||||
count = 0;
|
||||
countMin = 0;
|
||||
countMax = 0;
|
||||
countMean = 0f;
|
||||
countMedian = 0;
|
||||
countLowerQuartile = 0;
|
||||
countUpperQuartile = 0;
|
||||
countStandardDeviation = 0f;
|
||||
lastFrame = -1;
|
||||
presentOnFrameCount = 0;
|
||||
firstFrameIndex = -1;
|
||||
msMean = 0f;
|
||||
msMedian = 0f;
|
||||
msLowerQuartile = 0f;
|
||||
msUpperQuartile = 0f;
|
||||
msMin = float.MaxValue;
|
||||
msMax = float.MinValue;
|
||||
msStandardDeviation = 0f;
|
||||
minIndividualFrameIndex = 0;
|
||||
maxIndividualFrameIndex = 0;
|
||||
msMinIndividual = float.MaxValue;
|
||||
msMaxIndividual = float.MinValue;
|
||||
msAtMedian = 0f;
|
||||
medianFrameIndex = 0;
|
||||
minFrameIndex = 0;
|
||||
maxFrameIndex = 0;
|
||||
minDepth = 0;
|
||||
maxDepth = 0;
|
||||
|
||||
for (int b = 0; b < buckets.Length; b++)
|
||||
{
|
||||
buckets[b] = 0;
|
||||
countBuckets[b] = 0;
|
||||
}
|
||||
|
||||
timeRemoved = 0.0;
|
||||
timeIgnored = 0.0;
|
||||
}
|
||||
|
||||
/// <summary>Compare the time duration between the marker median times. Used for sorting in descending order</summary>
|
||||
/// <param name="other"> The other MarkerData to compare </param>
|
||||
/// <returns>-1 if this is larger, 0 if the same, 1 if this is smaller</returns>
|
||||
public int CompareTo(MarkerData other)
|
||||
{
|
||||
if (msMedian == other.msMedian)
|
||||
{
|
||||
if (medianFrameIndex == other.medianFrameIndex)
|
||||
{
|
||||
// Tertiary sort by name order
|
||||
return name.CompareTo(other.name);
|
||||
}
|
||||
|
||||
// Secondary sort by frame index order
|
||||
return medianFrameIndex.CompareTo(other.medianFrameIndex);
|
||||
}
|
||||
|
||||
return -msMedian.CompareTo(other.msMedian);
|
||||
}
|
||||
|
||||
public float GetFrameMs(int frameIndex)
|
||||
{
|
||||
foreach (var frameData in frames)
|
||||
{
|
||||
if (frameData.frameIndex == frameIndex)
|
||||
return frameData.ms;
|
||||
}
|
||||
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public void ComputeBuckets(float min, float max)
|
||||
{
|
||||
float first = min;
|
||||
float last = max;
|
||||
float range = last - first;
|
||||
|
||||
int maxBucketIndex = (buckets.Length - 1);
|
||||
|
||||
for (int bucketIndex = 0; bucketIndex < buckets.Length; bucketIndex++)
|
||||
{
|
||||
buckets[bucketIndex] = 0;
|
||||
}
|
||||
|
||||
float scale = range > 0 ? buckets.Length / range : 0;
|
||||
// using a for loop instead of foreach is surprisingly faster on Mono
|
||||
for (int i = 0, n = frames.Count; i < n; i++)
|
||||
{
|
||||
var frameTime = frames[i];
|
||||
var ms = frameTime.ms;
|
||||
//int frameIndex = frameTime.frameIndex;
|
||||
|
||||
int bucketIndex = (int)((ms - first) * scale);
|
||||
if (bucketIndex < 0 || bucketIndex > maxBucketIndex)
|
||||
{
|
||||
// This can happen if a single marker range is longer than the frame start end (which could occur if running on a separate thread)
|
||||
// It can also occur for the highest entry in the range (max-min/range) = 1
|
||||
// if (ms > max) // Check for the spilling case
|
||||
// Debug.Log(string.Format("Marker {0} : {1}ms exceeds range {2}-{3} on frame {4}", marker.name, ms, first, last, 1+frameIndex));
|
||||
|
||||
if (bucketIndex > maxBucketIndex)
|
||||
bucketIndex = maxBucketIndex;
|
||||
else
|
||||
bucketIndex = 0;
|
||||
}
|
||||
buckets[bucketIndex] += 1;
|
||||
}
|
||||
|
||||
if (range == 0)
|
||||
{
|
||||
// All buckets will be the same
|
||||
for (int bucketIndex = 1; bucketIndex < buckets.Length; bucketIndex++)
|
||||
{
|
||||
buckets[bucketIndex] = buckets[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ComputeCountBuckets(int min, int max)
|
||||
{
|
||||
float first = min;
|
||||
float last = max;
|
||||
float range = last - first;
|
||||
|
||||
int maxBucketIndex = (countBuckets.Length - 1);
|
||||
|
||||
for (int bucketIndex = 0; bucketIndex < countBuckets.Length; bucketIndex++)
|
||||
{
|
||||
countBuckets[bucketIndex] = 0;
|
||||
}
|
||||
|
||||
float scale = range > 0 ? countBuckets.Length / range : 0;
|
||||
// using a for loop instead of foreach is surprisingly faster on Mono
|
||||
for (int i = 0, n = frames.Count; i < n; i++)
|
||||
{
|
||||
var frameTime = frames[i];
|
||||
var count = frameTime.count;
|
||||
|
||||
int bucketIndex = (int)((count - first) * scale);
|
||||
if (bucketIndex < 0 || bucketIndex > maxBucketIndex)
|
||||
{
|
||||
if (bucketIndex > maxBucketIndex)
|
||||
bucketIndex = maxBucketIndex;
|
||||
else
|
||||
bucketIndex = 0;
|
||||
}
|
||||
countBuckets[bucketIndex] += 1;
|
||||
}
|
||||
|
||||
if (range == 0)
|
||||
{
|
||||
// All buckets will be the same
|
||||
for (int bucketIndex = 1; bucketIndex < countBuckets.Length; bucketIndex++)
|
||||
{
|
||||
countBuckets[bucketIndex] = countBuckets[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetFirstThread(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.threads[0] : "";
|
||||
}
|
||||
|
||||
public static float GetMsMax(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.msMax : 0.0f;
|
||||
}
|
||||
|
||||
public static int GetMaxFrameIndex(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.maxFrameIndex : 0;
|
||||
}
|
||||
|
||||
public static float GetMsMin(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.msMin : 0.0f;
|
||||
}
|
||||
|
||||
public static int GetMinFrameIndex(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.minFrameIndex : 0;
|
||||
}
|
||||
|
||||
public static float GetMsMedian(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.msMedian : 0.0f;
|
||||
}
|
||||
|
||||
public static int GetMedianFrameIndex(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.medianFrameIndex : 0;
|
||||
}
|
||||
|
||||
public static float GetMsUpperQuartile(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.msUpperQuartile : 0.0f;
|
||||
}
|
||||
|
||||
public static float GetMsLowerQuartile(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.msLowerQuartile : 0.0f;
|
||||
}
|
||||
|
||||
public static float GetMsMean(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.msMean : 0.0f;
|
||||
}
|
||||
|
||||
public static float GetMsMinIndividual(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.msMinIndividual : 0.0f;
|
||||
}
|
||||
|
||||
public static float GetMsMaxIndividual(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.msMaxIndividual : 0.0f;
|
||||
}
|
||||
|
||||
public static int GetPresentOnFrameCount(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.presentOnFrameCount : 0;
|
||||
}
|
||||
|
||||
public static float GetMsAtMedian(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.msAtMedian : 0.0f;
|
||||
}
|
||||
|
||||
public static int GetCountMin(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.countMin : 0;
|
||||
}
|
||||
|
||||
public static int GetCountMax(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.countMax : 0;
|
||||
}
|
||||
|
||||
public static int GetCount(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.count : 0;
|
||||
}
|
||||
|
||||
public static float GetCountMean(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.countMean : 0.0f;
|
||||
}
|
||||
|
||||
public static double GetMsTotal(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.msTotal : 0.0;
|
||||
}
|
||||
|
||||
public static int GetMinDepth(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.minDepth : 0;
|
||||
}
|
||||
|
||||
public static int GetMaxDepth(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.maxDepth : 0;
|
||||
}
|
||||
public static double GetTimeRemoved(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.timeRemoved : 0.0;
|
||||
}
|
||||
public static double GetTimeIgnored(MarkerData marker)
|
||||
{
|
||||
return marker != null ? marker.timeIgnored : 0.0;
|
||||
}
|
||||
|
||||
public bool IsFullyIgnored()
|
||||
{
|
||||
if (timeIgnored > 0.0)
|
||||
{
|
||||
if (msTotal == 0.0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7636f77e28bd3480f9e22fecae16594a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
[Serializable]
|
||||
internal class MarkerPairing
|
||||
{
|
||||
public string name;
|
||||
public int leftIndex;
|
||||
public int rightIndex;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 60fdacb48a6fe46529c3c01c96f7d123
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,405 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
[Serializable]
|
||||
internal class ProfileAnalysis
|
||||
{
|
||||
FrameSummary m_FrameSummary = new FrameSummary();
|
||||
List<MarkerData> m_Markers = new List<MarkerData>();
|
||||
List<ThreadData> m_Threads = new List<ThreadData>();
|
||||
|
||||
public ProfileAnalysis()
|
||||
{
|
||||
m_FrameSummary.first = 0;
|
||||
m_FrameSummary.last = 0;
|
||||
m_FrameSummary.count = 0;
|
||||
m_FrameSummary.msTotal = 0.0;
|
||||
m_FrameSummary.msMin = float.MaxValue;
|
||||
m_FrameSummary.msMax = 0.0f;
|
||||
m_FrameSummary.minFrameIndex = 0;
|
||||
m_FrameSummary.maxFrameIndex = 0;
|
||||
m_FrameSummary.maxMarkerDepth = 0;
|
||||
m_FrameSummary.totalMarkers = 0;
|
||||
m_FrameSummary.markerCountMax = 0;
|
||||
m_FrameSummary.markerCountMaxMean = 0.0f;
|
||||
for (int b = 0; b < m_FrameSummary.buckets.Length; b++)
|
||||
m_FrameSummary.buckets[b] = 0;
|
||||
|
||||
m_Markers.Clear();
|
||||
m_Threads.Clear();
|
||||
}
|
||||
|
||||
public void SetRange(int firstFrameIndex, int lastFrameIndex)
|
||||
{
|
||||
m_FrameSummary.first = firstFrameIndex;
|
||||
m_FrameSummary.last = lastFrameIndex;
|
||||
|
||||
// Ensure these are initialized to frame indices within the range
|
||||
m_FrameSummary.minFrameIndex = firstFrameIndex;
|
||||
// if this wasn't initialized, and all frames had 0 length, it wouldn't be set in the UpdateSummary step of the analysis and point out of range
|
||||
m_FrameSummary.maxFrameIndex = firstFrameIndex;
|
||||
}
|
||||
|
||||
public void AddMarker(MarkerData marker)
|
||||
{
|
||||
m_Markers.Add(marker);
|
||||
}
|
||||
|
||||
public void AddThread(ThreadData thread)
|
||||
{
|
||||
m_Threads.Add(thread);
|
||||
}
|
||||
|
||||
public void UpdateSummary(int frameIndex, float msFrame)
|
||||
{
|
||||
m_FrameSummary.msTotal += msFrame;
|
||||
m_FrameSummary.count += 1;
|
||||
if (msFrame < m_FrameSummary.msMin)
|
||||
{
|
||||
m_FrameSummary.msMin = msFrame;
|
||||
m_FrameSummary.minFrameIndex = frameIndex;
|
||||
}
|
||||
if (msFrame > m_FrameSummary.msMax)
|
||||
{
|
||||
m_FrameSummary.msMax = msFrame;
|
||||
m_FrameSummary.maxFrameIndex = frameIndex;
|
||||
}
|
||||
|
||||
m_FrameSummary.frames.Add(new FrameTime(frameIndex, msFrame, 1));
|
||||
}
|
||||
|
||||
FrameTime GetPercentageOffset(List<FrameTime> frames, float percent, out int outputFrameIndex)
|
||||
{
|
||||
int index = (int)((frames.Count - 1) * percent / 100);
|
||||
outputFrameIndex = frames[index].frameIndex;
|
||||
|
||||
// True median is half of the sum of the middle 2 frames for an even count. However this would be a value never recorded so we avoid that.
|
||||
return frames[index];
|
||||
}
|
||||
|
||||
float GetThreadPercentageOffset(List<ThreadFrameTime> frames, float percent, out int outputFrameIndex)
|
||||
{
|
||||
int index = (int)((frames.Count - 1) * percent / 100);
|
||||
outputFrameIndex = frames[index].frameIndex;
|
||||
|
||||
// True median is half of the sum of the middle 2 frames for an even count. However this would be a value never recorded so we avoid that.
|
||||
return frames[index].ms;
|
||||
}
|
||||
|
||||
void CalculateStandardDeviations(MarkerData marker)
|
||||
{
|
||||
if (marker.frames.Count <= 1)
|
||||
{
|
||||
marker.msStandardDeviation = 0;
|
||||
marker.countStandardDeviation = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int frameCount = marker.frames.Count;
|
||||
float msMean = marker.msMean;
|
||||
float countMean = marker.countMean;
|
||||
|
||||
double msSum = 0.0;
|
||||
double countSum = 0.0;
|
||||
for (int i = 0; i < frameCount; ++i)
|
||||
{
|
||||
float delta = (marker.frames[i].ms - msMean);
|
||||
msSum += (delta * delta);
|
||||
|
||||
delta = (marker.frames[i].count - countMean);
|
||||
countSum += (delta * delta);
|
||||
}
|
||||
|
||||
double variance = msSum / (frameCount - 1);
|
||||
marker.msStandardDeviation = (float)Math.Sqrt(variance);
|
||||
variance = countSum / (frameCount - 1);
|
||||
marker.countStandardDeviation = (float)Math.Sqrt(variance);
|
||||
}
|
||||
|
||||
public void SetupMarkers()
|
||||
{
|
||||
int countMax = 0;
|
||||
float countMaxMean = 0.0f;
|
||||
|
||||
foreach (MarkerData marker in m_Markers)
|
||||
{
|
||||
marker.msAtMedian = 0.0f;
|
||||
marker.msMin = float.MaxValue;
|
||||
marker.msMax = float.MinValue;
|
||||
marker.minFrameIndex = 0;
|
||||
marker.maxFrameIndex = 0;
|
||||
marker.countMin = int.MaxValue;
|
||||
marker.countMax = int.MinValue;
|
||||
|
||||
foreach (FrameTime frameTime in marker.frames)
|
||||
{
|
||||
var ms = frameTime.ms;
|
||||
int frameIndex = frameTime.frameIndex;
|
||||
|
||||
// Total time for marker over frame
|
||||
if (ms < marker.msMin)
|
||||
{
|
||||
marker.msMin = ms;
|
||||
marker.minFrameIndex = frameIndex;
|
||||
}
|
||||
if (ms > marker.msMax)
|
||||
{
|
||||
marker.msMax = ms;
|
||||
marker.maxFrameIndex = frameIndex;
|
||||
}
|
||||
|
||||
if (frameIndex == m_FrameSummary.medianFrameIndex)
|
||||
marker.msAtMedian = ms;
|
||||
|
||||
var count = frameTime.count;
|
||||
|
||||
// count for marker over frame
|
||||
if (count < marker.countMin)
|
||||
{
|
||||
marker.countMin = count;
|
||||
}
|
||||
if (count > marker.countMax)
|
||||
{
|
||||
marker.countMax = count;
|
||||
}
|
||||
}
|
||||
|
||||
int unusedIndex;
|
||||
|
||||
marker.msMean = marker.presentOnFrameCount > 0 ? (float)(marker.msTotal / marker.presentOnFrameCount) : 0f;
|
||||
marker.frames.Sort(FrameTime.CompareCount);
|
||||
marker.countMedian = GetPercentageOffset(marker.frames, 50, out marker.medianFrameIndex).count;
|
||||
marker.countLowerQuartile = GetPercentageOffset(marker.frames, 25, out unusedIndex).count;
|
||||
marker.countUpperQuartile = GetPercentageOffset(marker.frames, 75, out unusedIndex).count;
|
||||
|
||||
marker.countMean = marker.presentOnFrameCount > 0 ? (float)marker.count / marker.presentOnFrameCount : 0f;
|
||||
marker.frames.Sort(FrameTime.CompareMs);
|
||||
marker.msMedian = GetPercentageOffset(marker.frames, 50, out marker.medianFrameIndex).ms;
|
||||
marker.msLowerQuartile = GetPercentageOffset(marker.frames, 25, out unusedIndex).ms;
|
||||
marker.msUpperQuartile = GetPercentageOffset(marker.frames, 75, out unusedIndex).ms;
|
||||
|
||||
CalculateStandardDeviations(marker);
|
||||
|
||||
if (marker.countMax > countMax)
|
||||
countMax = marker.countMax;
|
||||
if (marker.countMean > countMaxMean)
|
||||
countMaxMean = marker.countMean;
|
||||
}
|
||||
|
||||
m_FrameSummary.markerCountMax = countMax;
|
||||
m_FrameSummary.markerCountMaxMean = countMaxMean;
|
||||
}
|
||||
|
||||
public void SetupMarkerBuckets()
|
||||
{
|
||||
// using a for loop instead of foreach is surprisingly faster on Mono
|
||||
for (int i = 0, n = m_Markers.Count; i < n; i++)
|
||||
{
|
||||
var marker = m_Markers[i];
|
||||
marker.ComputeBuckets(marker.msMin, marker.msMax);
|
||||
marker.ComputeCountBuckets(marker.countMin, marker.countMax);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetupFrameBuckets(float timeScaleMax)
|
||||
{
|
||||
float first = 0;
|
||||
float last = timeScaleMax;
|
||||
float range = last - first;
|
||||
|
||||
int maxBucketIndex = m_FrameSummary.buckets.Length - 1;
|
||||
|
||||
for (int bucketIndex = 0; bucketIndex < m_FrameSummary.buckets.Length; bucketIndex++)
|
||||
{
|
||||
m_FrameSummary.buckets[bucketIndex] = 0;
|
||||
}
|
||||
|
||||
float scale = range > 0 ? m_FrameSummary.buckets.Length / range : 0;
|
||||
// using a for loop instead of foreach is surprisingly faster on Mono
|
||||
for (int i = 0, n = m_FrameSummary.frames.Count; i < n; i++)
|
||||
{
|
||||
var frameData = m_FrameSummary.frames[i];
|
||||
var msFrame = frameData.ms;
|
||||
//var frameIndex = frameData.frameIndex;
|
||||
|
||||
int bucketIndex = (int)((msFrame - first) * scale);
|
||||
if (bucketIndex < 0 || bucketIndex > maxBucketIndex)
|
||||
{
|
||||
// It can occur for the highest entry in the range (max-min/range) = 1
|
||||
// if (ms > max) // Check for the spilling case
|
||||
// Debug.Log(string.Format("Frame {0}ms exceeds range {1}-{2} on frame {3}", msFrame, first, last, frameIndex));
|
||||
if (bucketIndex > maxBucketIndex)
|
||||
bucketIndex = maxBucketIndex;
|
||||
else
|
||||
bucketIndex = 0;
|
||||
}
|
||||
m_FrameSummary.buckets[bucketIndex] += 1;
|
||||
}
|
||||
|
||||
if (range == 0)
|
||||
{
|
||||
// All buckets will be the same
|
||||
for (int bucketIndex = 1; bucketIndex < m_FrameSummary.buckets.Length; bucketIndex++)
|
||||
{
|
||||
m_FrameSummary.buckets[bucketIndex] = m_FrameSummary.buckets[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CalculateThreadMedians()
|
||||
{
|
||||
foreach (var thread in m_Threads)
|
||||
{
|
||||
if (thread.frames.Count > 0)
|
||||
{
|
||||
thread.frames.Sort();
|
||||
int unusedIndex;
|
||||
|
||||
thread.msMin = GetThreadPercentageOffset(thread.frames, 0, out thread.minFrameIndex);
|
||||
thread.msLowerQuartile = GetThreadPercentageOffset(thread.frames, 25, out unusedIndex);
|
||||
thread.msMedian = GetThreadPercentageOffset(thread.frames, 50, out thread.medianFrameIndex);
|
||||
thread.msUpperQuartile = GetThreadPercentageOffset(thread.frames, 75, out unusedIndex);
|
||||
thread.msMax = GetThreadPercentageOffset(thread.frames, 100, out thread.maxFrameIndex);
|
||||
|
||||
// Put back in order of frames
|
||||
thread.frames.Sort((a, b) => a.frameIndex.CompareTo(b.frameIndex));
|
||||
}
|
||||
else
|
||||
{
|
||||
thread.msMin = 0f;
|
||||
thread.msLowerQuartile = 0f;
|
||||
thread.msMedian = 0f;
|
||||
thread.msUpperQuartile = 0f;
|
||||
thread.msMax = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Finalise(float timeScaleMax, int maxMarkerDepth)
|
||||
{
|
||||
if (m_FrameSummary.frames.Count > 0)
|
||||
{
|
||||
m_FrameSummary.frames.Sort();
|
||||
m_FrameSummary.msMean = (float)(m_FrameSummary.msTotal / m_FrameSummary.count);
|
||||
m_FrameSummary.msMedian = GetPercentageOffset(m_FrameSummary.frames, 50, out m_FrameSummary.medianFrameIndex).ms;
|
||||
int unusedIndex;
|
||||
m_FrameSummary.msLowerQuartile = GetPercentageOffset(m_FrameSummary.frames, 25, out unusedIndex).ms;
|
||||
m_FrameSummary.msUpperQuartile = GetPercentageOffset(m_FrameSummary.frames, 75, out unusedIndex).ms;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_FrameSummary.msMean = 0f;
|
||||
m_FrameSummary.msMedian = 0f;
|
||||
m_FrameSummary.msLowerQuartile = 0f;
|
||||
m_FrameSummary.msUpperQuartile = 0f;
|
||||
|
||||
// This started as float.MaxValue and won't have been updated
|
||||
m_FrameSummary.msMin = 0f;
|
||||
}
|
||||
// No longer need the frame time list ?
|
||||
//m_frameSummary.msFrame.Clear();
|
||||
m_FrameSummary.maxMarkerDepth = maxMarkerDepth;
|
||||
|
||||
if (timeScaleMax <= 0.0f)
|
||||
{
|
||||
// If max frame time range not specified then use the max frame value found.
|
||||
timeScaleMax = m_FrameSummary.msMax;
|
||||
}
|
||||
else if (timeScaleMax < m_FrameSummary.msMax)
|
||||
{
|
||||
Debug.Log(string.Format("Expanding timeScaleMax {0} to match max value found {1}", timeScaleMax, m_FrameSummary.msMax));
|
||||
|
||||
// If max frame time range too small we must expand it.
|
||||
timeScaleMax = m_FrameSummary.msMax;
|
||||
}
|
||||
|
||||
SetupMarkers();
|
||||
SetupMarkerBuckets();
|
||||
SetupFrameBuckets(timeScaleMax);
|
||||
|
||||
// Sort in median order (highest first)
|
||||
m_Markers.Sort(SortByAtMedian);
|
||||
|
||||
CalculateThreadMedians();
|
||||
}
|
||||
|
||||
int SortByAtMedian(MarkerData a, MarkerData b)
|
||||
{
|
||||
if (a.msAtMedian == b.msAtMedian)
|
||||
return -a.medianFrameIndex.CompareTo(b.medianFrameIndex);
|
||||
|
||||
return -a.msAtMedian.CompareTo(b.msAtMedian);
|
||||
}
|
||||
|
||||
public List<MarkerData> GetMarkers()
|
||||
{
|
||||
return m_Markers;
|
||||
}
|
||||
|
||||
public List<ThreadData> GetThreads()
|
||||
{
|
||||
return m_Threads;
|
||||
}
|
||||
|
||||
public ThreadData GetThreadByName(string threadNameWithIndex)
|
||||
{
|
||||
foreach (var thread in m_Threads)
|
||||
{
|
||||
if (thread.threadNameWithIndex == threadNameWithIndex)
|
||||
return thread;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public FrameSummary GetFrameSummary()
|
||||
{
|
||||
return m_FrameSummary;
|
||||
}
|
||||
|
||||
public MarkerData GetMarker(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_Markers.Count)
|
||||
return null;
|
||||
|
||||
return m_Markers[index];
|
||||
}
|
||||
|
||||
public int GetMarkerIndexByName(string markerName)
|
||||
{
|
||||
if (markerName == null)
|
||||
return -1;
|
||||
|
||||
for (int index = 0; index < m_Markers.Count; index++)
|
||||
{
|
||||
var marker = m_Markers[index];
|
||||
if (marker.name == markerName)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public MarkerData GetMarkerByName(string markerName)
|
||||
{
|
||||
if (markerName == null)
|
||||
return null;
|
||||
|
||||
for (int index = 0; index < m_Markers.Count; index++)
|
||||
{
|
||||
var marker = m_Markers[index];
|
||||
if (marker.name == markerName)
|
||||
{
|
||||
return marker;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f7cee1025e74448acbbb8bf2b82d9bfc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,553 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditorInternal;
|
||||
using System.Text.RegularExpressions;
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
internal class ProfileAnalyzer
|
||||
{
|
||||
public const int kDepthAll = -1;
|
||||
|
||||
int m_Progress = 0;
|
||||
ProfilerFrameDataIterator m_frameData;
|
||||
List<string> m_threadNames = new List<string>();
|
||||
ProfileAnalysis m_analysis;
|
||||
|
||||
public ProfileAnalyzer()
|
||||
{
|
||||
}
|
||||
|
||||
public void QuickScan()
|
||||
{
|
||||
var frameData = new ProfilerFrameDataIterator();
|
||||
|
||||
m_threadNames.Clear();
|
||||
int frameIndex = 0;
|
||||
int threadCount = frameData.GetThreadCount(0);
|
||||
frameData.SetRoot(frameIndex, 0);
|
||||
|
||||
Dictionary<string, int> threadNameCount = new Dictionary<string, int>();
|
||||
for (int threadIndex = 0; threadIndex < threadCount; ++threadIndex)
|
||||
{
|
||||
frameData.SetRoot(frameIndex, threadIndex);
|
||||
|
||||
var threadName = frameData.GetThreadName();
|
||||
var groupName = frameData.GetGroupName();
|
||||
threadName = ProfileData.GetThreadNameWithGroup(threadName, groupName);
|
||||
|
||||
if (!threadNameCount.ContainsKey(threadName))
|
||||
threadNameCount.Add(threadName, 1);
|
||||
else
|
||||
threadNameCount[threadName] += 1;
|
||||
|
||||
string threadNameWithIndex = ProfileData.ThreadNameWithIndex(threadNameCount[threadName], threadName);
|
||||
threadNameWithIndex = ProfileData.CorrectThreadName(threadNameWithIndex);
|
||||
|
||||
m_threadNames.Add(threadNameWithIndex);
|
||||
}
|
||||
|
||||
frameData.Dispose();
|
||||
}
|
||||
|
||||
public List<string> GetThreadNames()
|
||||
{
|
||||
return m_threadNames;
|
||||
}
|
||||
|
||||
void CalculateFrameTimeStats(ProfileData data, out float median, out float mean, out float standardDeviation)
|
||||
{
|
||||
List<float> frameTimes = new List<float>();
|
||||
for (int frameIndex = 0; frameIndex < data.GetFrameCount(); frameIndex++)
|
||||
{
|
||||
var frame = data.GetFrame(frameIndex);
|
||||
float msFrame = frame.msFrame;
|
||||
frameTimes.Add(msFrame);
|
||||
}
|
||||
frameTimes.Sort();
|
||||
median = frameTimes[frameTimes.Count / 2];
|
||||
|
||||
|
||||
double total = 0.0f;
|
||||
foreach (float msFrame in frameTimes)
|
||||
{
|
||||
total += msFrame;
|
||||
}
|
||||
mean = (float)(total / (double)frameTimes.Count);
|
||||
|
||||
|
||||
if (frameTimes.Count <= 1)
|
||||
{
|
||||
standardDeviation = 0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
total = 0.0f;
|
||||
foreach (float msFrame in frameTimes)
|
||||
{
|
||||
float d = msFrame - mean;
|
||||
total += (d * d);
|
||||
}
|
||||
total /= (frameTimes.Count - 1);
|
||||
standardDeviation = (float)Math.Sqrt(total);
|
||||
}
|
||||
}
|
||||
|
||||
int GetClampedOffsetToFrame(ProfileData profileData, int frameIndex)
|
||||
{
|
||||
int frameOffset = profileData.DisplayFrameToOffset(frameIndex);
|
||||
if (frameOffset < 0)
|
||||
{
|
||||
Debug.Log(string.Format("Frame index {0} offset {1} < 0, clamping", frameIndex, frameOffset));
|
||||
frameOffset = 0;
|
||||
}
|
||||
if (frameOffset >= profileData.GetFrameCount())
|
||||
{
|
||||
Debug.Log(string.Format("Frame index {0} offset {1} >= frame count {2}, clamping", frameIndex, frameOffset, profileData.GetFrameCount()));
|
||||
frameOffset = profileData.GetFrameCount() - 1;
|
||||
}
|
||||
|
||||
return frameOffset;
|
||||
}
|
||||
|
||||
public static bool MatchThreadFilter(string threadNameWithIndex, List<string> threadFilters)
|
||||
{
|
||||
if (threadFilters == null || threadFilters.Count == 0)
|
||||
return false;
|
||||
|
||||
if (threadFilters.Contains(threadNameWithIndex))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsNullOrWhiteSpace(string s)
|
||||
{
|
||||
// return string.IsNullOrWhiteSpace(parentMarker);
|
||||
if (s == null || Regex.IsMatch(s, @"^[\s]*$"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void RemoveMarkerTimeFromParents(MarkerData[] markers, ProfileData profileData, ProfileThread threadData, int markerAt)
|
||||
{
|
||||
// Get the info for the marker we plan to remove (assume thats what we are at)
|
||||
ProfileMarker profileMarker = threadData.markers[markerAt];
|
||||
float markerTime = profileMarker.msMarkerTotal;
|
||||
|
||||
// Traverse parents and remove time from them
|
||||
int currentDepth = profileMarker.depth;
|
||||
for (int parentMarkerAt = markerAt - 1; parentMarkerAt >= 0; parentMarkerAt--)
|
||||
{
|
||||
ProfileMarker parentMarkerData = threadData.markers[parentMarkerAt];
|
||||
if (parentMarkerData.depth == currentDepth - 1)
|
||||
{
|
||||
currentDepth--;
|
||||
if (parentMarkerData.nameIndex < markers.Length) // Had an issue where marker not yet processed(marker from another thread)
|
||||
{
|
||||
MarkerData parentMarker = markers[parentMarkerData.nameIndex];
|
||||
|
||||
// If a depth slice is applied we may not have a parent marker stored
|
||||
if (parentMarker != null)
|
||||
{
|
||||
// Revise the duration of parent to remove time from there too
|
||||
// Note if the marker to remove is nested (i.e. parent of the same name, this could reduce the msTotal, more than we add to the timeIgnored)
|
||||
parentMarker.msTotal -= markerTime;
|
||||
|
||||
// Reduce from the max marker time too
|
||||
// This could be incorrect when there are many instances that contribute the the total time
|
||||
if (parentMarker.msMaxIndividual > markerTime)
|
||||
{
|
||||
parentMarker.msMaxIndividual -= markerTime;
|
||||
}
|
||||
if (parentMarker.msMinIndividual > markerTime)
|
||||
{
|
||||
parentMarker.msMinIndividual -= markerTime;
|
||||
}
|
||||
|
||||
// Revise stored frame time
|
||||
FrameTime frameTime = parentMarker.frames[parentMarker.frames.Count - 1];
|
||||
frameTime = new FrameTime(frameTime.frameIndex, frameTime.ms - markerTime, frameTime.count);
|
||||
parentMarker.frames[parentMarker.frames.Count - 1] = frameTime;
|
||||
|
||||
// Note that we have modified the time
|
||||
parentMarker.timeRemoved += markerTime;
|
||||
|
||||
// Note markerTime can be 0 in some cases.
|
||||
// Make sure timeRemoved is never left at 0.0
|
||||
// This makes sure we can test for non zero to indicate the marker has been removed
|
||||
if (parentMarker.timeRemoved == 0.0)
|
||||
parentMarker.timeRemoved = double.Epsilon;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int RemoveMarker(ProfileThread threadData, int markerAt)
|
||||
{
|
||||
ProfileMarker profileMarker = threadData.markers[markerAt];
|
||||
int at = markerAt;
|
||||
|
||||
// skip marker
|
||||
at++;
|
||||
|
||||
// Skip children
|
||||
int currentDepth = profileMarker.depth;
|
||||
while (at < threadData.markers.Count)
|
||||
{
|
||||
profileMarker = threadData.markers[at];
|
||||
if (profileMarker.depth <= currentDepth)
|
||||
break;
|
||||
|
||||
at++;
|
||||
}
|
||||
|
||||
// Mark the following number to be ignored
|
||||
int markerAndChildCount = at - markerAt;
|
||||
|
||||
return markerAndChildCount;
|
||||
}
|
||||
|
||||
public ProfileAnalysis Analyze(ProfileData profileData, List<int> selectionIndices, List<string> threadFilters, int depthFilter, bool selfTimes = false, string parentMarker = null, float timeScaleMax = 0, string removeMarker = null)
|
||||
{
|
||||
m_Progress = 0;
|
||||
if (profileData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (profileData.GetFrameCount() <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int frameCount = selectionIndices.Count;
|
||||
if (frameCount < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (profileData.HasFrames && !profileData.HasThreads)
|
||||
{
|
||||
if (!ProfileData.Load(profileData.FilePath, out profileData))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
bool processMarkers = (threadFilters != null);
|
||||
|
||||
ProfileAnalysis analysis = new ProfileAnalysis();
|
||||
if (selectionIndices.Count > 0)
|
||||
analysis.SetRange(selectionIndices[0], selectionIndices[selectionIndices.Count - 1]);
|
||||
else
|
||||
analysis.SetRange(0, 0);
|
||||
|
||||
m_threadNames.Clear();
|
||||
|
||||
int maxMarkerDepthFound = 0;
|
||||
var threads = new Dictionary<string, ThreadData>();
|
||||
var markers = new MarkerData[profileData.MarkerNameCount];
|
||||
var removedMarkers = new Dictionary<string, double>();
|
||||
|
||||
var mainThreadIdentifier = new ThreadIdentifier("Main Thread", 1);
|
||||
|
||||
int markerCount = 0;
|
||||
|
||||
bool filteringByParentMarker = false;
|
||||
int parentMarkerIndex = -1;
|
||||
if (!IsNullOrWhiteSpace(parentMarker))
|
||||
{
|
||||
// Returns -1 if this marker doesn't exist in the data set
|
||||
parentMarkerIndex = profileData.GetMarkerIndex(parentMarker);
|
||||
filteringByParentMarker = true;
|
||||
}
|
||||
|
||||
int at = 0;
|
||||
foreach (int frameIndex in selectionIndices)
|
||||
{
|
||||
int frameOffset = profileData.DisplayFrameToOffset(frameIndex);
|
||||
var frameData = profileData.GetFrame(frameOffset);
|
||||
if (frameData == null)
|
||||
continue;
|
||||
var msFrame = frameData.msFrame;
|
||||
|
||||
if (processMarkers)
|
||||
{
|
||||
// get the file reader in case we need to rebuild the markers rather than opening
|
||||
// the file for every marker
|
||||
for (int threadIndex = 0; threadIndex < frameData.threads.Count; threadIndex++)
|
||||
{
|
||||
float msTimeOfMinDepthMarkers = 0.0f;
|
||||
float msIdleTimeOfMinDepthMarkers = 0.0f;
|
||||
|
||||
var threadData = frameData.threads[threadIndex];
|
||||
var threadNameWithIndex = profileData.GetThreadName(threadData);
|
||||
|
||||
ThreadData thread;
|
||||
if (!threads.ContainsKey(threadNameWithIndex))
|
||||
{
|
||||
m_threadNames.Add(threadNameWithIndex);
|
||||
|
||||
thread = new ThreadData(threadNameWithIndex);
|
||||
|
||||
analysis.AddThread(thread);
|
||||
threads[threadNameWithIndex] = thread;
|
||||
|
||||
// Update threadsInGroup for all thread records of the same group name
|
||||
foreach (var threadAt in threads.Values)
|
||||
{
|
||||
if (threadAt == thread)
|
||||
continue;
|
||||
|
||||
if (thread.threadGroupName == threadAt.threadGroupName)
|
||||
{
|
||||
threadAt.threadsInGroup += 1;
|
||||
thread.threadsInGroup += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
thread = threads[threadNameWithIndex];
|
||||
}
|
||||
|
||||
bool include = MatchThreadFilter(threadNameWithIndex, threadFilters);
|
||||
|
||||
int parentMarkerDepth = -1;
|
||||
|
||||
if (threadData.markers.Count != threadData.markerCount)
|
||||
{
|
||||
if (!threadData.ReadMarkers(profileData.FilePath))
|
||||
{
|
||||
Debug.LogError("failed to read markers");
|
||||
}
|
||||
}
|
||||
|
||||
int markerAndChildCount = 0;
|
||||
for (int markerAt = 0, n = threadData.markers.Count; markerAt < n; markerAt++)
|
||||
{
|
||||
var markerData = threadData.markers[markerAt];
|
||||
|
||||
if (markerAndChildCount > 0)
|
||||
markerAndChildCount--;
|
||||
|
||||
string markerName = null;
|
||||
|
||||
float ms = markerData.msMarkerTotal - (selfTimes ? markerData.msChildren : 0);
|
||||
var markerDepth = markerData.depth;
|
||||
if (markerDepth > maxMarkerDepthFound)
|
||||
maxMarkerDepthFound = markerDepth;
|
||||
|
||||
if (markerDepth == 1)
|
||||
{
|
||||
markerName = profileData.GetMarkerName(markerData);
|
||||
if (markerName.Equals("Idle", StringComparison.Ordinal))
|
||||
msIdleTimeOfMinDepthMarkers += ms;
|
||||
else
|
||||
msTimeOfMinDepthMarkers += ms;
|
||||
}
|
||||
|
||||
if (removeMarker != null)
|
||||
{
|
||||
if (markerAndChildCount <= 0) // If we are already removing markers - don't focus on other occurances in the children
|
||||
{
|
||||
if (markerName == null)
|
||||
markerName = profileData.GetMarkerName(markerData);
|
||||
|
||||
if (markerName == removeMarker)
|
||||
{
|
||||
float removeMarkerTime = markerData.msMarkerTotal;
|
||||
|
||||
// Remove this markers time from frame time (if its on the main thread)
|
||||
if (thread.threadNameWithIndex == mainThreadIdentifier.threadNameWithIndex)
|
||||
{
|
||||
msFrame -= removeMarkerTime;
|
||||
}
|
||||
|
||||
if (selfTimes == false) // (Self times would not need thread or parent adjustments)
|
||||
{
|
||||
// And from thread time
|
||||
if (markerName == "Idle")
|
||||
msIdleTimeOfMinDepthMarkers -= removeMarkerTime;
|
||||
else
|
||||
msTimeOfMinDepthMarkers -= removeMarkerTime;
|
||||
|
||||
// And from parents
|
||||
RemoveMarkerTimeFromParents(markers, profileData, threadData, markerAt);
|
||||
}
|
||||
|
||||
markerAndChildCount = RemoveMarker(threadData, markerAt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!include)
|
||||
continue;
|
||||
|
||||
// If only looking for markers below the parent
|
||||
if (filteringByParentMarker)
|
||||
{
|
||||
// If found the parent marker
|
||||
if (markerData.nameIndex == parentMarkerIndex)
|
||||
{
|
||||
// And we are not already below the parent higher in the depth tree
|
||||
if (parentMarkerDepth < 0)
|
||||
{
|
||||
// record the parent marker depth
|
||||
parentMarkerDepth = markerData.depth;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we are now above or beside the parent marker then we are done for this level
|
||||
if (markerData.depth <= parentMarkerDepth)
|
||||
{
|
||||
parentMarkerDepth = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (parentMarkerDepth < 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (depthFilter != kDepthAll && markerDepth != depthFilter)
|
||||
continue;
|
||||
|
||||
MarkerData marker = markers[markerData.nameIndex];
|
||||
if (marker != null)
|
||||
{
|
||||
if (!marker.threads.Contains(threadNameWithIndex))
|
||||
marker.threads.Add(threadNameWithIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (markerName == null)
|
||||
markerName = profileData.GetMarkerName(markerData);
|
||||
marker = new MarkerData(markerName);
|
||||
marker.firstFrameIndex = frameIndex;
|
||||
marker.minDepth = markerDepth;
|
||||
marker.maxDepth = markerDepth;
|
||||
marker.threads.Add(threadNameWithIndex);
|
||||
analysis.AddMarker(marker);
|
||||
markers[markerData.nameIndex] = marker;
|
||||
markerCount += 1;
|
||||
}
|
||||
marker.count += 1;
|
||||
|
||||
if (markerAndChildCount > 0)
|
||||
{
|
||||
marker.timeIgnored += ms;
|
||||
|
||||
// Note ms can be 0 in some cases.
|
||||
// Make sure timeIgnored is never left at 0.0
|
||||
// This makes sure we can test for non zero to indicate the marker has been ignored
|
||||
if (marker.timeIgnored == 0.0)
|
||||
marker.timeIgnored = double.Epsilon;
|
||||
|
||||
// zero out removed marker time
|
||||
// so we don't record in the individual marker times, marker frame times or min/max times
|
||||
// ('min/max times' is calculated later from marker frame times)
|
||||
ms = 0f;
|
||||
}
|
||||
|
||||
marker.msTotal += ms;
|
||||
|
||||
// Individual marker time (not total over frame)
|
||||
if (ms < marker.msMinIndividual)
|
||||
{
|
||||
marker.msMinIndividual = ms;
|
||||
marker.minIndividualFrameIndex = frameIndex;
|
||||
}
|
||||
if (ms > marker.msMaxIndividual)
|
||||
{
|
||||
marker.msMaxIndividual = ms;
|
||||
marker.maxIndividualFrameIndex = frameIndex;
|
||||
}
|
||||
|
||||
// Record highest depth foun
|
||||
if (markerDepth < marker.minDepth)
|
||||
marker.minDepth = markerDepth;
|
||||
if (markerDepth > marker.maxDepth)
|
||||
marker.maxDepth = markerDepth;
|
||||
|
||||
FrameTime frameTime;
|
||||
if (frameIndex != marker.lastFrame)
|
||||
{
|
||||
marker.presentOnFrameCount += 1;
|
||||
frameTime = new FrameTime(frameIndex, ms, 1);
|
||||
marker.frames.Add(frameTime);
|
||||
marker.lastFrame = frameIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
frameTime = marker.frames[marker.frames.Count - 1];
|
||||
frameTime = new FrameTime(frameTime.frameIndex, frameTime.ms + ms, frameTime.count + 1);
|
||||
marker.frames[marker.frames.Count - 1] = frameTime;
|
||||
}
|
||||
}
|
||||
|
||||
if (include)
|
||||
thread.frames.Add(new ThreadFrameTime(frameIndex, msTimeOfMinDepthMarkers, msIdleTimeOfMinDepthMarkers));
|
||||
}
|
||||
}
|
||||
|
||||
analysis.UpdateSummary(frameIndex, msFrame);
|
||||
|
||||
at++;
|
||||
m_Progress = (100 * at) / frameCount;
|
||||
}
|
||||
|
||||
analysis.GetFrameSummary().totalMarkers = profileData.MarkerNameCount;
|
||||
analysis.Finalise(timeScaleMax, maxMarkerDepthFound);
|
||||
|
||||
/*
|
||||
foreach (int frameIndex in selectionIndices)
|
||||
{
|
||||
int frameOffset = profileData.DisplayFrameToOffset(frameIndex);
|
||||
|
||||
var frameData = profileData.GetFrame(frameOffset);
|
||||
foreach (var threadData in frameData.threads)
|
||||
{
|
||||
var threadNameWithIndex = profileData.GetThreadName(threadData);
|
||||
|
||||
if (filterThreads && threadFilter != threadNameWithIndex)
|
||||
continue;
|
||||
|
||||
const bool enterChildren = true;
|
||||
foreach (var markerData in threadData.markers)
|
||||
{
|
||||
var markerName = markerData.name;
|
||||
var ms = markerData.msFrame;
|
||||
var markerDepth = markerData.depth;
|
||||
if (depthFilter != kDepthAll && markerDepth != depthFilter)
|
||||
continue;
|
||||
|
||||
MarkerData marker = markers[markerName];
|
||||
bucketIndex = (range > 0) ? (int)(((marker.buckets.Length-1) * (ms - first)) / range) : 0;
|
||||
if (bucketIndex<0 || bucketIndex > (marker.buckets.Length - 1))
|
||||
{
|
||||
// This can happen if a single marker range is longer than the frame start end (which could occur if running on a separate thread)
|
||||
// Debug.Log(string.Format("Marker {0} : {1}ms exceeds range {2}-{3} on frame {4}", marker.name, ms, first, last, frameIndex));
|
||||
if (bucketIndex > (marker.buckets.Length - 1))
|
||||
bucketIndex = (marker.buckets.Length - 1);
|
||||
else
|
||||
bucketIndex = 0;
|
||||
}
|
||||
marker.individualBuckets[bucketIndex] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
m_Progress = 100;
|
||||
return analysis;
|
||||
}
|
||||
|
||||
public int GetProgress()
|
||||
{
|
||||
return m_Progress;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 317d76fd107c444c7822b9c99d48bd30
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,281 @@
|
||||
using UnityEngine;
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
internal class ProfileAnalyzerExportWindow : EditorWindow
|
||||
{
|
||||
internal static class Styles
|
||||
{
|
||||
public static readonly GUIContent markerTable = new GUIContent("Marker table", "Export data from the single view marker table");
|
||||
public static readonly GUIContent singleFrameTimes = new GUIContent("Single Frame Times", "Export frame time data from the single view");
|
||||
public static readonly GUIContent comparisonTables = new GUIContent("Comparison table", "Export data from the comparsion view marker table");
|
||||
public static readonly GUIContent comparisonFrameTimes = new GUIContent("Comparison Frame Times", "Export frame time data from the comparison view");
|
||||
}
|
||||
|
||||
ProfileAnalyzerWindow m_ProfileAnalyzerWindow;
|
||||
|
||||
ProfileDataView m_ProfileDataView;
|
||||
ProfileDataView m_LeftDataView;
|
||||
ProfileDataView m_RightDataView;
|
||||
|
||||
static public ProfileAnalyzerExportWindow FindOpenWindow()
|
||||
{
|
||||
UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(typeof(ProfileAnalyzerExportWindow));
|
||||
if (windows != null && windows.Length > 0)
|
||||
return windows[0] as ProfileAnalyzerExportWindow;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static public bool IsOpen()
|
||||
{
|
||||
if (FindOpenWindow() != null)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static public ProfileAnalyzerExportWindow Open(float screenX, float screenY, ProfileDataView profileSingleView, ProfileDataView profileLeftView, ProfileDataView profileRightView, ProfileAnalyzerWindow profileAnalyzerWindow)
|
||||
{
|
||||
ProfileAnalyzerExportWindow window = GetWindow<ProfileAnalyzerExportWindow>("Export");
|
||||
window.minSize = new Vector2(200, 180);
|
||||
window.position = new Rect(screenX, screenY, 200, 180);
|
||||
window.m_ProfileAnalyzerWindow = profileAnalyzerWindow;
|
||||
window.SetData(profileSingleView, profileLeftView, profileRightView);
|
||||
window.Show();
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
static public void CloseAll()
|
||||
{
|
||||
ProfileAnalyzerExportWindow window = GetWindow<ProfileAnalyzerExportWindow>("Export");
|
||||
window.Close();
|
||||
}
|
||||
|
||||
public void SetData(ProfileDataView profileDataView, ProfileDataView leftDataView, ProfileDataView rightDataView)
|
||||
{
|
||||
m_ProfileDataView = profileDataView;
|
||||
m_LeftDataView = leftDataView;
|
||||
m_RightDataView = rightDataView;
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true));
|
||||
GUILayout.Label("Export as CSV:");
|
||||
GUILayout.Label("");
|
||||
|
||||
GUILayout.Label("Single View");
|
||||
|
||||
bool enabled = GUI.enabled;
|
||||
if (m_ProfileDataView == null || !m_ProfileDataView.IsDataValid())
|
||||
GUI.enabled = false;
|
||||
if (GUILayout.Button(Styles.markerTable))
|
||||
SaveMarkerTableCSV();
|
||||
GUI.enabled = enabled;
|
||||
|
||||
if (m_ProfileDataView == null || m_ProfileDataView.analysis == null)
|
||||
GUI.enabled = false;
|
||||
if (GUILayout.Button(Styles.singleFrameTimes))
|
||||
SaveFrameTimesCSV();
|
||||
GUI.enabled = enabled;
|
||||
|
||||
GUILayout.Label("Comparison View");
|
||||
|
||||
if (!m_ProfileAnalyzerWindow.CanExportComparisonTable())
|
||||
GUI.enabled = false;
|
||||
if (GUILayout.Button(Styles.comparisonTables))
|
||||
SaveComparisonTableCSV();
|
||||
GUI.enabled = enabled;
|
||||
if (m_LeftDataView == null || !m_LeftDataView.IsDataValid() || m_RightDataView == null || !m_RightDataView.IsDataValid())
|
||||
GUI.enabled = false;
|
||||
if (GUILayout.Button(Styles.comparisonFrameTimes))
|
||||
SaveComparisonFrameTimesCSV();
|
||||
GUI.enabled = enabled;
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
void SaveMarkerTableCSV()
|
||||
{
|
||||
if (m_ProfileDataView.analysis == null)
|
||||
return;
|
||||
|
||||
string path = EditorUtility.SaveFilePanel("Save marker table CSV data", "", "markerTable.csv", "csv");
|
||||
if (path.Length != 0)
|
||||
{
|
||||
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
|
||||
using (StreamWriter file = new StreamWriter(path))
|
||||
{
|
||||
file.Write("Name; ");
|
||||
file.Write("Median Time; Min Time; Max Time; ");
|
||||
file.Write("Median Frame Index; Min Frame Index; Max Frame Index; ");
|
||||
file.Write("Min Depth; Max Depth; ");
|
||||
file.Write("Total Time; ");
|
||||
file.Write("Mean Time; Time Lower Quartile; Time Upper Quartile; ");
|
||||
file.Write("Count Total; Count Median; Count Min; Count Max; ");
|
||||
file.Write("Number of frames containing Marker; ");
|
||||
file.Write("First Frame Index; ");
|
||||
file.Write("Time Min Individual; Time Max Individual; ");
|
||||
file.Write("Min Individual Frame; Max Individual Frame; ");
|
||||
file.WriteLine("Time at Median Frame");
|
||||
|
||||
List<MarkerData> markerData = m_ProfileDataView.analysis.GetMarkers();
|
||||
markerData.Sort();
|
||||
foreach (MarkerData marker in markerData)
|
||||
{
|
||||
var markerName = marker.name;
|
||||
if (markerName.IndexOf('\"') >= 0)
|
||||
// replace all double quotation marks with single ones to avoid this breaking the escaping with double quotation marks
|
||||
markerName = markerName.Replace('\"', '\'');
|
||||
|
||||
int medianFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(marker.medianFrameIndex, m_ProfileDataView);
|
||||
int minFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(marker.minFrameIndex, m_ProfileDataView);
|
||||
int maxFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(marker.maxFrameIndex, m_ProfileDataView);
|
||||
int firstFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(marker.firstFrameIndex, m_ProfileDataView);
|
||||
int minIndividualFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(marker.minIndividualFrameIndex, m_ProfileDataView);
|
||||
int maxIndividualFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(marker.maxIndividualFrameIndex, m_ProfileDataView);
|
||||
|
||||
// "Escape" marker names in case it has commas in it.
|
||||
file.Write("\"{0}\";", markerName);
|
||||
file.Write(string.Format(CultureInfo.InvariantCulture,"{0};{1};{2};",
|
||||
marker.msMedian, marker.msMin, marker.msMax));
|
||||
file.Write("{0};{1};{2};",
|
||||
medianFrameIndex, minFrameIndex, maxFrameIndex);
|
||||
file.Write("{0};{1};",
|
||||
marker.minDepth, marker.maxDepth);
|
||||
file.Write(string.Format(CultureInfo.InvariantCulture, "{0};",
|
||||
marker.msTotal));
|
||||
file.Write(string.Format(CultureInfo.InvariantCulture, "{0};{1};{2};",
|
||||
marker.msMean, marker.msLowerQuartile, marker.msUpperQuartile));
|
||||
file.Write("{0};{1};{2};{3};",
|
||||
marker.count, marker.countMedian, marker.countMin, marker.countMax);
|
||||
file.Write("{0};", marker.presentOnFrameCount);
|
||||
file.Write("{0};", firstFrameIndex);
|
||||
file.Write(string.Format(CultureInfo.InvariantCulture, "{0};{1};",
|
||||
marker.msMinIndividual, marker.msMaxIndividual));
|
||||
file.Write("{0};{1};",
|
||||
minIndividualFrameIndex, maxIndividualFrameIndex);
|
||||
file.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0}", marker.msAtMedian));
|
||||
}
|
||||
}
|
||||
ProfileAnalyzerAnalytics.SendUIButtonEvent(ProfileAnalyzerAnalytics.UIButton.ExportSingleFrames, analytic);
|
||||
}
|
||||
}
|
||||
|
||||
void SaveFrameTimesCSV()
|
||||
{
|
||||
if (m_ProfileDataView == null)
|
||||
return;
|
||||
if (!m_ProfileDataView.IsDataValid())
|
||||
return;
|
||||
|
||||
string path = EditorUtility.SaveFilePanel("Save frame time CSV data", "", "frameTime.csv", "csv");
|
||||
if (path.Length != 0)
|
||||
{
|
||||
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
|
||||
using (StreamWriter file = new StreamWriter(path))
|
||||
{
|
||||
file.WriteLine("Frame Offset; Frame Index; Frame Time (ms); Time from first frame (ms)");
|
||||
float maxFrames = m_ProfileDataView.data.GetFrameCount();
|
||||
|
||||
var frame = m_ProfileDataView.data.GetFrame(0);
|
||||
// msStartTime isn't very accurate so we don't use it
|
||||
|
||||
double msTimePassed = 0.0;
|
||||
for (int frameOffset = 0; frameOffset < maxFrames; frameOffset++)
|
||||
{
|
||||
frame = m_ProfileDataView.data.GetFrame(frameOffset);
|
||||
int frameIndex = m_ProfileDataView.data.OffsetToDisplayFrame(frameOffset);
|
||||
frameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(frameIndex, m_ProfileDataView);
|
||||
|
||||
float msFrame = frame.msFrame;
|
||||
file.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0};{1};{2};{3}",
|
||||
frameOffset, frameIndex, msFrame, msTimePassed));
|
||||
|
||||
msTimePassed += msFrame;
|
||||
}
|
||||
}
|
||||
ProfileAnalyzerAnalytics.SendUIButtonEvent(ProfileAnalyzerAnalytics.UIButton.ExportSingleFrames, analytic);
|
||||
}
|
||||
}
|
||||
|
||||
void SaveComparisonFrameTimesCSV()
|
||||
{
|
||||
if (m_LeftDataView == null || m_RightDataView == null)
|
||||
return;
|
||||
if (!m_LeftDataView.IsDataValid() || !m_RightDataView.IsDataValid())
|
||||
return;
|
||||
|
||||
string path = EditorUtility.SaveFilePanel("Save comparison frame time CSV data", "", "frameTimeComparison.csv", "csv");
|
||||
if (path.Length != 0)
|
||||
{
|
||||
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
|
||||
using (StreamWriter file = new StreamWriter(path))
|
||||
{
|
||||
file.Write("Frame Offset; ");
|
||||
file.Write("Left Frame Index; Right Frame Index; ");
|
||||
file.Write("Left Frame Time (ms); Left time from first frame (ms); ");
|
||||
file.Write("Right Frame Time (ms); Right time from first frame (ms); ");
|
||||
file.WriteLine("Frame Time Diff (ms)");
|
||||
float maxFrames = Math.Max(m_LeftDataView.data.GetFrameCount(), m_RightDataView.data.GetFrameCount());
|
||||
|
||||
var leftFrame = m_LeftDataView.data.GetFrame(0);
|
||||
var rightFrame = m_RightDataView.data.GetFrame(0);
|
||||
|
||||
// msStartTime isn't very accurate so we don't use it
|
||||
|
||||
double msTimePassedLeft = 0.0;
|
||||
double msTimePassedRight = 0.0;
|
||||
|
||||
for (int frameOffset = 0; frameOffset < maxFrames; frameOffset++)
|
||||
{
|
||||
leftFrame = m_LeftDataView.data.GetFrame(frameOffset);
|
||||
rightFrame = m_RightDataView.data.GetFrame(frameOffset);
|
||||
int leftFrameIndex = m_LeftDataView.data.OffsetToDisplayFrame(frameOffset);
|
||||
leftFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(leftFrameIndex, m_LeftDataView);
|
||||
int rightFrameIndex = m_RightDataView.data.OffsetToDisplayFrame(frameOffset);
|
||||
rightFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(rightFrameIndex, m_RightDataView);
|
||||
|
||||
float msFrameLeft = leftFrame != null ? leftFrame.msFrame : 0;
|
||||
float msFrameRight = rightFrame != null ? rightFrame.msFrame : 0;
|
||||
float msFrameDiff = msFrameRight - msFrameLeft;
|
||||
file.Write("{0};", frameOffset);
|
||||
file.Write("{0};{1};", leftFrameIndex, rightFrameIndex);
|
||||
file.Write(string.Format(CultureInfo.InvariantCulture, "{0};{1};", msFrameLeft, msTimePassedLeft));
|
||||
file.Write(string.Format(CultureInfo.InvariantCulture, "{0};{1};", msFrameRight, msTimePassedRight));
|
||||
file.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0}", msFrameDiff));
|
||||
|
||||
msTimePassedLeft += msFrameLeft;
|
||||
msTimePassedRight += msFrameRight;
|
||||
}
|
||||
}
|
||||
ProfileAnalyzerAnalytics.SendUIButtonEvent(ProfileAnalyzerAnalytics.UIButton.ExportComparisonFrames, analytic);
|
||||
}
|
||||
}
|
||||
|
||||
void SaveComparisonTableCSV()
|
||||
{
|
||||
if (m_LeftDataView == null || m_RightDataView == null)
|
||||
return;
|
||||
if (!m_LeftDataView.IsDataValid() || !m_RightDataView.IsDataValid())
|
||||
return;
|
||||
|
||||
string path = EditorUtility.SaveFilePanel("Save comparison table CSV data", "", "tableComparison.csv", "csv");
|
||||
if (path.Length != 0)
|
||||
{
|
||||
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
|
||||
using (StreamWriter file = new StreamWriter(path))
|
||||
{
|
||||
m_ProfileAnalyzerWindow.TryExportComparisonTable(file);
|
||||
}
|
||||
ProfileAnalyzerAnalytics.SendUIButtonEvent(ProfileAnalyzerAnalytics.UIButton.ExportComparisonFrames, analytic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ccd88eea7c8284ac18aff2e257aeb84a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,106 @@
|
||||
Shader "Unlit/ProfileAnalyzerShader"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
_StencilComp ("Stencil Comparison", Float) = 8
|
||||
_Stencil ("Stencil ID", Float) = 0
|
||||
_StencilOp ("Stencil Operation", Float) = 0
|
||||
_StencilWriteMask ("Stencil Write Mask", Float) = 255
|
||||
_StencilReadMask ("Stencil Read Mask", Float) = 255
|
||||
|
||||
_ColorMask ("Color Mask", Float) = 15
|
||||
|
||||
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
|
||||
|
||||
_ClipRect ("Clip Rect", vector) = (-32767, -32767, 32767, 32767)
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags
|
||||
{
|
||||
"Queue"="Transparent"
|
||||
"IgnoreProjector"="True"
|
||||
"RenderType"="Transparent"
|
||||
"PreviewType"="Plane"
|
||||
"CanUseSpriteAtlas"="True"
|
||||
}
|
||||
|
||||
Stencil
|
||||
{
|
||||
Ref [_Stencil]
|
||||
Comp [_StencilComp]
|
||||
Pass [_StencilOp]
|
||||
ReadMask [_StencilReadMask]
|
||||
WriteMask [_StencilWriteMask]
|
||||
}
|
||||
|
||||
//Tags { "RenderType"="Transparent" }
|
||||
LOD 100
|
||||
|
||||
//ZWrite Off
|
||||
//Blend SrcAlpha OneMinusSrcAlpha
|
||||
|
||||
Cull Off
|
||||
Lighting Off
|
||||
ZWrite Off
|
||||
ZTest [unity_GUIZTestMode]
|
||||
Blend SrcAlpha OneMinusSrcAlpha
|
||||
ColorMask [_ColorMask]
|
||||
|
||||
Pass
|
||||
{
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
|
||||
#include "UnityCG.cginc"
|
||||
#include "UnityUI.cginc"
|
||||
|
||||
#pragma multi_compile _ UNITY_UI_ALPHACLIP
|
||||
|
||||
struct appdata
|
||||
{
|
||||
float4 vertex : POSITION;
|
||||
fixed4 color : COLOR;
|
||||
};
|
||||
|
||||
struct v2f
|
||||
{
|
||||
float4 vertex : SV_POSITION;
|
||||
fixed4 color : COLOR;
|
||||
float4 worldPosition : TEXCOORD1;
|
||||
};
|
||||
|
||||
bool _UseClipRect;
|
||||
float4 _ClipRect;
|
||||
|
||||
v2f vert (appdata v)
|
||||
{
|
||||
v2f o;
|
||||
o.worldPosition = v.vertex;
|
||||
o.vertex = UnityObjectToClipPos(v.vertex);
|
||||
o.color.rgba = v.color;
|
||||
return o;
|
||||
}
|
||||
|
||||
//fixed4 frag (v2f i) : SV_Target { return i.color; }
|
||||
|
||||
fixed4 frag (v2f i) : SV_Target
|
||||
{
|
||||
half4 color = i.color;
|
||||
|
||||
if (_UseClipRect)
|
||||
color.a *= UnityGet2DClipping(i.worldPosition.xy, _ClipRect);
|
||||
|
||||
#ifdef UNITY_UI_ALPHACLIP
|
||||
clip (color.a - 0.001);
|
||||
#endif
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 493b79ea9c9be44ed8b702014d3ac49c
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0dc59854f72ef4a1fb6dbb8d0cd6aea3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,761 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditorInternal;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
[Serializable]
|
||||
internal class ProfileData
|
||||
{
|
||||
static int latestVersion = 7;
|
||||
/*
|
||||
Version 1 - Initial version. Thread names index:threadName (Some invalid thread names count:threadName index)
|
||||
Version 2 - Added frame start time.
|
||||
Version 3 - Saved out marker children times in the data (Never needed so rapidly skipped)
|
||||
Version 4 - Removed the child times again (at this point data was saved with 1 less frame at start and end)
|
||||
Version 5 - Updated the thread names to include the thread group as a prefix (index:threadGroup.threadName, index is 1 based, original is 0 based)
|
||||
Version 6 - fixed msStartTime (previously was 'seconds')
|
||||
Version 7 - Data now only skips the frame at the end
|
||||
*/
|
||||
static Regex trailingDigit = new Regex(@"^(.*[^\s])[\s]+([\d]+)$", RegexOptions.Compiled);
|
||||
public int Version { get; private set; }
|
||||
public int FrameIndexOffset { get; private set; }
|
||||
public bool FirstFrameIncomplete;
|
||||
public bool LastFrameIncomplete;
|
||||
List<ProfileFrame> frames = new List<ProfileFrame>();
|
||||
List<string> markerNames = new List<string>();
|
||||
List<string> threadNames = new List<string>();
|
||||
Dictionary<string, int> markerNamesDict = new Dictionary<string, int>();
|
||||
Dictionary<string, int> threadNameDict = new Dictionary<string, int>();
|
||||
public string FilePath { get; private set; }
|
||||
public int MarkerNameCount => markerNames.Count;
|
||||
static float s_Progress = 0;
|
||||
|
||||
public ProfileData()
|
||||
{
|
||||
FrameIndexOffset = 0;
|
||||
FilePath = string.Empty;
|
||||
Version = latestVersion;
|
||||
}
|
||||
|
||||
public ProfileData(string filename)
|
||||
{
|
||||
FrameIndexOffset = 0;
|
||||
FilePath = filename;
|
||||
Version = latestVersion;
|
||||
}
|
||||
|
||||
void Read()
|
||||
{
|
||||
if (string.IsNullOrEmpty(FilePath))
|
||||
throw new Exception("File path is invalid");
|
||||
|
||||
using (var reader = new BinaryReader(File.Open(FilePath, FileMode.Open)))
|
||||
{
|
||||
s_Progress = 0;
|
||||
Version = reader.ReadInt32();
|
||||
if (Version < 0 || Version > latestVersion)
|
||||
{
|
||||
throw new Exception(String.Format("File version unsupported: {0} != {1} expected, at path: {2}", Version, latestVersion, FilePath));
|
||||
}
|
||||
|
||||
FrameIndexOffset = reader.ReadInt32();
|
||||
int frameCount = reader.ReadInt32();
|
||||
frames.Clear();
|
||||
for (int frame = 0; frame < frameCount; frame++)
|
||||
{
|
||||
frames.Add(new ProfileFrame(reader, Version));
|
||||
s_Progress = (float)frame / frameCount;
|
||||
}
|
||||
|
||||
int markerCount = reader.ReadInt32();
|
||||
markerNames.Clear();
|
||||
for (int marker = 0; marker < markerCount; marker++)
|
||||
{
|
||||
markerNames.Add(reader.ReadString());
|
||||
s_Progress = (float)marker / markerCount;
|
||||
}
|
||||
|
||||
int threadCount = reader.ReadInt32();
|
||||
threadNames.Clear();
|
||||
for (int thread = 0; thread < threadCount; thread++)
|
||||
{
|
||||
var threadNameWithIndex = reader.ReadString();
|
||||
|
||||
threadNameWithIndex = CorrectThreadName(threadNameWithIndex);
|
||||
|
||||
threadNames.Add(threadNameWithIndex);
|
||||
s_Progress = (float)thread / threadCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void DeleteTmpFiles()
|
||||
{
|
||||
if (ProfileAnalyzerWindow.FileInTempDir(FilePath))
|
||||
File.Delete(FilePath);
|
||||
}
|
||||
|
||||
bool IsFrameSame(int frameIndex, ProfileData other)
|
||||
{
|
||||
ProfileFrame thisFrame = GetFrame(frameIndex);
|
||||
ProfileFrame otherFrame = other.GetFrame(frameIndex);
|
||||
return thisFrame.IsSame(otherFrame);
|
||||
}
|
||||
|
||||
public bool IsSame(ProfileData other)
|
||||
{
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
int frameCount = GetFrameCount();
|
||||
if (frameCount != other.GetFrameCount())
|
||||
{
|
||||
// Frame counts differ
|
||||
return false;
|
||||
}
|
||||
|
||||
if (frameCount == 0)
|
||||
{
|
||||
// Both empty
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!IsFrameSame(0, other))
|
||||
return false;
|
||||
if (!IsFrameSame(frameCount - 1, other))
|
||||
return false;
|
||||
|
||||
// Close enough if same number of frames and first/last have exactly the same frame time and time offset.
|
||||
// If we see false matches we could add a full has of the data on load/pull
|
||||
return true;
|
||||
}
|
||||
|
||||
static public string ThreadNameWithIndex(int index, string threadName)
|
||||
{
|
||||
return string.Format("{0}:{1}", index, threadName);
|
||||
}
|
||||
|
||||
public void SetFrameIndexOffset(int offset)
|
||||
{
|
||||
FrameIndexOffset = offset;
|
||||
}
|
||||
|
||||
public int GetFrameCount()
|
||||
{
|
||||
return frames.Count;
|
||||
}
|
||||
|
||||
public ProfileFrame GetFrame(int offset)
|
||||
{
|
||||
if (offset < 0 || offset >= frames.Count)
|
||||
return null;
|
||||
|
||||
return frames[offset];
|
||||
}
|
||||
|
||||
public List<string> GetMarkerNames()
|
||||
{
|
||||
return markerNames;
|
||||
}
|
||||
|
||||
public List<string> GetThreadNames()
|
||||
{
|
||||
return threadNames;
|
||||
}
|
||||
|
||||
public int GetThreadCount()
|
||||
{
|
||||
return threadNames.Count;
|
||||
}
|
||||
|
||||
public int OffsetToDisplayFrame(int offset)
|
||||
{
|
||||
return offset + (1 + FrameIndexOffset);
|
||||
}
|
||||
|
||||
public int DisplayFrameToOffset(int displayFrame)
|
||||
{
|
||||
return displayFrame - (1 + FrameIndexOffset);
|
||||
}
|
||||
|
||||
public void AddThreadName(string threadName, ProfileThread thread)
|
||||
{
|
||||
threadName = CorrectThreadName(threadName);
|
||||
|
||||
int index = -1;
|
||||
|
||||
if (!threadNameDict.TryGetValue(threadName, out index))
|
||||
{
|
||||
threadNames.Add(threadName);
|
||||
index = threadNames.Count - 1;
|
||||
|
||||
threadNameDict.Add(threadName, index);
|
||||
}
|
||||
|
||||
thread.threadIndex = index;
|
||||
}
|
||||
|
||||
public void AddMarkerName(string markerName, ProfileMarker marker)
|
||||
{
|
||||
int index = -1;
|
||||
if (!markerNamesDict.TryGetValue(markerName, out index))
|
||||
{
|
||||
markerNames.Add(markerName);
|
||||
index = markerNames.Count - 1;
|
||||
|
||||
markerNamesDict.Add(markerName, index);
|
||||
}
|
||||
|
||||
marker.nameIndex = index;
|
||||
}
|
||||
|
||||
public string GetThreadName(ProfileThread thread)
|
||||
{
|
||||
return threadNames[thread.threadIndex];
|
||||
}
|
||||
|
||||
public string GetMarkerName(ProfileMarker marker)
|
||||
{
|
||||
return markerNames[marker.nameIndex];
|
||||
}
|
||||
|
||||
public int GetMarkerIndex(string markerName)
|
||||
{
|
||||
for (int nameIndex = 0; nameIndex < markerNames.Count; ++nameIndex)
|
||||
{
|
||||
if (markerName == markerNames[nameIndex])
|
||||
return nameIndex;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void Add(ProfileFrame frame)
|
||||
{
|
||||
frames.Add(frame);
|
||||
}
|
||||
|
||||
void WriteInternal(string filepath)
|
||||
{
|
||||
using (var writer = new BinaryWriter(File.Open(filepath, FileMode.OpenOrCreate)))
|
||||
{
|
||||
Version = latestVersion;
|
||||
|
||||
writer.Write(Version);
|
||||
writer.Write(FrameIndexOffset);
|
||||
|
||||
writer.Write(frames.Count);
|
||||
foreach (var frame in frames)
|
||||
{
|
||||
frame.Write(writer);
|
||||
}
|
||||
|
||||
writer.Write(markerNames.Count);
|
||||
foreach (var markerName in markerNames)
|
||||
{
|
||||
writer.Write(markerName);
|
||||
}
|
||||
|
||||
writer.Write(threadNames.Count);
|
||||
foreach (var threadName in threadNames)
|
||||
{
|
||||
writer.Write(threadName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void Write()
|
||||
{
|
||||
//ensure that we can always write to the temp location at least
|
||||
if (string.IsNullOrEmpty(FilePath))
|
||||
FilePath = ProfileAnalyzerWindow.TmpPath;
|
||||
|
||||
WriteInternal(FilePath);
|
||||
}
|
||||
|
||||
internal void WriteTo(string path)
|
||||
{
|
||||
//no point in trying to save on top of ourselves
|
||||
if (path == FilePath)
|
||||
return;
|
||||
|
||||
if (!string.IsNullOrEmpty(FilePath) && File.Exists(FilePath))
|
||||
{
|
||||
if (File.Exists(path))
|
||||
File.Delete(path);
|
||||
|
||||
File.Copy(FilePath, path);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteInternal(path);
|
||||
}
|
||||
FilePath = path;
|
||||
}
|
||||
|
||||
public static string CorrectThreadName(string threadNameWithIndex)
|
||||
{
|
||||
var info = threadNameWithIndex.Split(':');
|
||||
if (info.Length >= 2)
|
||||
{
|
||||
string threadGroupIndexString = info[0];
|
||||
string threadName = info[1];
|
||||
if (threadName.Trim() == "")
|
||||
{
|
||||
// Scan seen with no thread name
|
||||
threadNameWithIndex = string.Format("{0}:[Unknown]", threadGroupIndexString);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Some scans have thread names such as
|
||||
// "1:Worker Thread 0"
|
||||
// "1:Worker Thread 1"
|
||||
// rather than
|
||||
// "1:Worker Thread"
|
||||
// "2:Worker Thread"
|
||||
// Update to the second format so the 'All' case is correctly determined
|
||||
Match m = trailingDigit.Match(threadName);
|
||||
if (m.Success)
|
||||
{
|
||||
string threadNamePrefix = m.Groups[1].Value;
|
||||
int threadGroupIndex = 1 + int.Parse(m.Groups[2].Value);
|
||||
|
||||
threadNameWithIndex = string.Format("{0}:{1}", threadGroupIndex, threadNamePrefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
threadNameWithIndex = threadNameWithIndex.Trim();
|
||||
|
||||
return threadNameWithIndex;
|
||||
}
|
||||
|
||||
public static string GetThreadNameWithGroup(string threadName, string groupName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(groupName))
|
||||
return threadName;
|
||||
|
||||
return string.Format("{0}.{1}", groupName, threadName);
|
||||
}
|
||||
|
||||
public static string GetThreadNameWithoutGroup(string threadNameWithGroup, out string groupName)
|
||||
{
|
||||
string[] tokens = threadNameWithGroup.Split('.');
|
||||
if (tokens.Length <= 1)
|
||||
{
|
||||
groupName = "";
|
||||
return tokens[0];
|
||||
}
|
||||
|
||||
groupName = tokens[0];
|
||||
return tokens[1].TrimStart();
|
||||
}
|
||||
|
||||
internal bool HasFrames
|
||||
{
|
||||
get
|
||||
{
|
||||
return frames != null && frames.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal bool HasThreads
|
||||
{
|
||||
get
|
||||
{
|
||||
return frames[0].threads != null && frames[0].threads.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal bool NeedsMarkerRebuild
|
||||
{
|
||||
get
|
||||
{
|
||||
if (frames.Count > 0 && frames[0].threads.Count > 0)
|
||||
return frames[0].threads[0].markers.Count != frames[0].threads[0].markerCount;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Save(string filename, ProfileData data)
|
||||
{
|
||||
if (data == null)
|
||||
return false;
|
||||
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
return false;
|
||||
|
||||
if (filename.EndsWith(".json"))
|
||||
{
|
||||
var json = JsonUtility.ToJson(data);
|
||||
File.WriteAllText(filename, json);
|
||||
}
|
||||
else if (filename.EndsWith(".padata"))
|
||||
{
|
||||
FileStream stream = File.Create(filename);
|
||||
var formatter = new BinaryFormatter();
|
||||
formatter.Serialize(stream, data);
|
||||
stream.Close();
|
||||
}
|
||||
else if (filename.EndsWith(".pdata"))
|
||||
{
|
||||
data.WriteTo(filename);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool Load(string filename, out ProfileData data)
|
||||
{
|
||||
if (filename.EndsWith(".json"))
|
||||
{
|
||||
string json = File.ReadAllText(filename);
|
||||
data = JsonUtility.FromJson<ProfileData>(json);
|
||||
}
|
||||
else if (filename.EndsWith(".padata"))
|
||||
{
|
||||
FileStream stream = File.OpenRead(filename);
|
||||
var formatter = new BinaryFormatter();
|
||||
data = (ProfileData)formatter.Deserialize(stream);
|
||||
stream.Close();
|
||||
|
||||
if (data.Version != latestVersion)
|
||||
{
|
||||
Debug.Log(String.Format("Unable to load file. Incorrect file version in {0} : (file {1} != {2} expected", filename, data.Version, latestVersion));
|
||||
data = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (filename.EndsWith(".pdata"))
|
||||
{
|
||||
if (!File.Exists(filename))
|
||||
{
|
||||
data = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
data = new ProfileData(filename);
|
||||
data.Read();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = e.Message;
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
Debug.Log(e.Message);
|
||||
data = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string errorMessage;
|
||||
if (filename.EndsWith(".data"))
|
||||
{
|
||||
errorMessage = "Unable to load file. Profiler captures (.data) should be loaded in the Profiler Window and then pulled into the Analyzer via its Pull Data button.";
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = string.Format("Unable to load file. Unsupported file format: '{0}'.", Path.GetExtension(filename));
|
||||
}
|
||||
|
||||
Debug.Log(errorMessage);
|
||||
data = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
data.Finalise();
|
||||
return true;
|
||||
}
|
||||
|
||||
void PushMarker(Stack<ProfileMarker> markerStack, ProfileMarker markerData)
|
||||
{
|
||||
Debug.Assert(markerData.depth == markerStack.Count + 1);
|
||||
markerStack.Push(markerData);
|
||||
}
|
||||
|
||||
ProfileMarker PopMarkerAndRecordTimeInParent(Stack<ProfileMarker> markerStack)
|
||||
{
|
||||
ProfileMarker child = markerStack.Pop();
|
||||
|
||||
ProfileMarker parentMarker = (markerStack.Count > 0) ? markerStack.Peek() : null;
|
||||
|
||||
// Record the last markers time in its parent
|
||||
if (parentMarker != null)
|
||||
parentMarker.msChildren += child.msMarkerTotal;
|
||||
|
||||
return parentMarker;
|
||||
}
|
||||
|
||||
public void Finalise()
|
||||
{
|
||||
CalculateMarkerChildTimes();
|
||||
markerNamesDict.Clear();
|
||||
}
|
||||
|
||||
void CalculateMarkerChildTimes()
|
||||
{
|
||||
var markerStack = new Stack<ProfileMarker>();
|
||||
|
||||
for (int frameOffset = 0; frameOffset <= frames.Count; ++frameOffset)
|
||||
{
|
||||
var frameData = GetFrame(frameOffset);
|
||||
if (frameData == null)
|
||||
continue;
|
||||
|
||||
for (int threadIndex = 0; threadIndex < frameData.threads.Count; threadIndex++)
|
||||
{
|
||||
var threadData = frameData.threads[threadIndex];
|
||||
|
||||
// The markers are in depth first order and the depth is known
|
||||
// So we can infer a parent child relationship
|
||||
// Zero them first
|
||||
foreach (ProfileMarker markerData in threadData.markers)
|
||||
{
|
||||
markerData.msChildren = 0.0f;
|
||||
}
|
||||
|
||||
// Update the child times
|
||||
markerStack.Clear();
|
||||
foreach (ProfileMarker markerData in threadData.markers)
|
||||
{
|
||||
int depth = markerData.depth;
|
||||
|
||||
// Update depth stack and record child times in the parent
|
||||
if (depth >= markerStack.Count)
|
||||
{
|
||||
// If at same level then remove the last item at this level
|
||||
if (depth == markerStack.Count)
|
||||
{
|
||||
PopMarkerAndRecordTimeInParent(markerStack);
|
||||
}
|
||||
|
||||
// Assume we can't move down depth without markers between levels.
|
||||
}
|
||||
else if (depth < markerStack.Count)
|
||||
{
|
||||
// We can move up depth several layers so need to pop off all those markers
|
||||
while (markerStack.Count >= depth)
|
||||
{
|
||||
PopMarkerAndRecordTimeInParent(markerStack);
|
||||
}
|
||||
}
|
||||
|
||||
PushMarker(markerStack, markerData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static float GetLoadingProgress()
|
||||
{
|
||||
return s_Progress;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class ProfileFrame
|
||||
{
|
||||
public List<ProfileThread> threads = new List<ProfileThread>();
|
||||
public double msStartTime;
|
||||
public float msFrame;
|
||||
|
||||
public ProfileFrame()
|
||||
{
|
||||
msStartTime = 0.0;
|
||||
msFrame = 0f;
|
||||
}
|
||||
|
||||
public bool IsSame(ProfileFrame otherFrame)
|
||||
{
|
||||
if (msStartTime != otherFrame.msStartTime)
|
||||
return false;
|
||||
if (msFrame != otherFrame.msFrame)
|
||||
return false;
|
||||
if (threads.Count != otherFrame.threads.Count)
|
||||
return false;
|
||||
|
||||
// Close enough.
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Add(ProfileThread thread)
|
||||
{
|
||||
threads.Add(thread);
|
||||
}
|
||||
|
||||
public void Write(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(msStartTime);
|
||||
writer.Write(msFrame);
|
||||
writer.Write(threads.Count);
|
||||
foreach (var thread in threads)
|
||||
{
|
||||
thread.Write(writer);
|
||||
}
|
||||
;
|
||||
}
|
||||
|
||||
public ProfileFrame(BinaryReader reader, int fileVersion)
|
||||
{
|
||||
if (fileVersion > 1)
|
||||
{
|
||||
if (fileVersion >= 6)
|
||||
{
|
||||
msStartTime = reader.ReadDouble();
|
||||
}
|
||||
else
|
||||
{
|
||||
double sStartTime = reader.ReadDouble();
|
||||
msStartTime = sStartTime * 1000.0;
|
||||
}
|
||||
}
|
||||
|
||||
msFrame = reader.ReadSingle();
|
||||
int threadCount = reader.ReadInt32();
|
||||
threads.Clear();
|
||||
for (int thread = 0; thread < threadCount; thread++)
|
||||
{
|
||||
threads.Add(new ProfileThread(reader, fileVersion));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class ProfileThread
|
||||
{
|
||||
[NonSerialized]
|
||||
public List<ProfileMarker> markers = new List<ProfileMarker>();
|
||||
public int threadIndex;
|
||||
public long streamPos;
|
||||
public int markerCount = 0;
|
||||
public int fileVersion;
|
||||
|
||||
public ProfileThread()
|
||||
{
|
||||
}
|
||||
|
||||
public void Write(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(threadIndex);
|
||||
writer.Write(markers.Count);
|
||||
foreach (var marker in markers)
|
||||
{
|
||||
marker.Write(writer);
|
||||
}
|
||||
;
|
||||
}
|
||||
|
||||
public ProfileThread(BinaryReader reader, int fileversion)
|
||||
{
|
||||
streamPos = reader.BaseStream.Position;
|
||||
fileVersion = fileversion;
|
||||
threadIndex = reader.ReadInt32();
|
||||
markerCount = reader.ReadInt32();
|
||||
markers.Clear();
|
||||
for (int marker = 0; marker < markerCount; marker++)
|
||||
{
|
||||
markers.Add(new ProfileMarker(reader, fileVersion));
|
||||
}
|
||||
}
|
||||
|
||||
public bool ReadMarkers(string path)
|
||||
{
|
||||
if (streamPos == 0)
|
||||
return false; // the stream positions havent been written yet.
|
||||
var stream = File.OpenRead(path);
|
||||
BinaryReader br = new BinaryReader(stream);
|
||||
|
||||
br.BaseStream.Position = streamPos;
|
||||
threadIndex = br.ReadInt32();
|
||||
markerCount = br.ReadInt32();
|
||||
|
||||
markers.Clear();
|
||||
for (int marker = 0; marker < markerCount; marker++)
|
||||
{
|
||||
markers.Add(new ProfileMarker(br, fileVersion));
|
||||
}
|
||||
|
||||
br.Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AddMarker(ProfileMarker markerData)
|
||||
{
|
||||
markers.Add(markerData);
|
||||
markerCount++;
|
||||
}
|
||||
|
||||
public void RebuildMarkers(string path)
|
||||
{
|
||||
if (!File.Exists(path)) return;
|
||||
FileStream stream = File.OpenRead(path);
|
||||
using (var reader = new BinaryReader(stream))
|
||||
{
|
||||
reader.BaseStream.Position = streamPos;
|
||||
threadIndex = reader.ReadInt32();
|
||||
markerCount = reader.ReadInt32();
|
||||
markers.Clear();
|
||||
for (int marker = 0; marker < markerCount; marker++)
|
||||
{
|
||||
markers.Add(new ProfileMarker(reader, fileVersion));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class ProfileMarker
|
||||
{
|
||||
public int nameIndex;
|
||||
public float msMarkerTotal;
|
||||
public int depth;
|
||||
[NonSerialized]
|
||||
public float msChildren; // Recalculated on load so not saved in file
|
||||
|
||||
public ProfileMarker()
|
||||
{
|
||||
}
|
||||
|
||||
public static ProfileMarker Create(float durationMS, int depth)
|
||||
{
|
||||
var item = new ProfileMarker
|
||||
{
|
||||
msMarkerTotal = durationMS,
|
||||
depth = depth,
|
||||
msChildren = 0.0f
|
||||
};
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public static ProfileMarker Create(ProfilerFrameDataIterator frameData)
|
||||
{
|
||||
return Create(frameData.durationMS, frameData.depth);
|
||||
}
|
||||
|
||||
public void Write(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(nameIndex);
|
||||
writer.Write(msMarkerTotal);
|
||||
writer.Write(depth);
|
||||
}
|
||||
|
||||
public ProfileMarker(BinaryReader reader, int fileVersion)
|
||||
{
|
||||
nameIndex = reader.ReadInt32();
|
||||
msMarkerTotal = reader.ReadSingle();
|
||||
depth = reader.ReadInt32();
|
||||
if (fileVersion == 3) // In this version we saved the msChildren value but we don't need to as we now recalculate on load
|
||||
msChildren = reader.ReadSingle();
|
||||
else
|
||||
msChildren = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5cea2082789f54c25b65fd1fef416863
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,172 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
[Serializable]
|
||||
internal class ProfileDataView
|
||||
{
|
||||
public string path;
|
||||
public ProfileData data;
|
||||
public ProfileAnalysis analysisFullNew;
|
||||
public ProfileAnalysis analysisFull;
|
||||
public ProfileAnalysis analysisNew;
|
||||
public ProfileAnalysis analysis;
|
||||
public List<int> selectedIndices = new List<int> { 0, 0 };
|
||||
[NonSerialized]
|
||||
public bool inSyncWithProfilerData;
|
||||
public bool containsWaitForFPS { get; private set; }
|
||||
public bool containsWaitForPresent { get; private set; }
|
||||
|
||||
public ProfileDataView()
|
||||
{
|
||||
}
|
||||
|
||||
public ProfileDataView(ProfileDataView dataView)
|
||||
{
|
||||
path = dataView.path;
|
||||
data = dataView.data;
|
||||
analysisFullNew = dataView.analysisFullNew;
|
||||
analysisFull = dataView.analysisFull;
|
||||
analysisNew = dataView.analysisNew;
|
||||
analysis = dataView.analysis;
|
||||
selectedIndices = new List<int>(dataView.selectedIndices);
|
||||
inSyncWithProfilerData = dataView.inSyncWithProfilerData;
|
||||
containsWaitForFPS = dataView.containsWaitForFPS;
|
||||
containsWaitForPresent = dataView.containsWaitForPresent;
|
||||
}
|
||||
|
||||
public void FindKeyMarkers()
|
||||
{
|
||||
containsWaitForFPS = data.GetMarkerIndex("WaitForTargetFPS") != -1;
|
||||
containsWaitForPresent = data.GetMarkerIndex("Gfx.WaitForPresentOnGfxThread") != -1;
|
||||
}
|
||||
|
||||
public bool IsDataValid()
|
||||
{
|
||||
if (data == null)
|
||||
return false;
|
||||
|
||||
if (data.GetFrameCount() == 0)
|
||||
return false;
|
||||
|
||||
if (data.NeedsMarkerRebuild)
|
||||
{
|
||||
if (!ProfileData.Load(data.FilePath, out data))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool HasValidSelection()
|
||||
{
|
||||
if (selectedIndices.Count == 2 && selectedIndices[0] == 0 && selectedIndices[1] == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool HasSelection()
|
||||
{
|
||||
if (selectedIndices.Count == 0)
|
||||
return false;
|
||||
if (selectedIndices.Count == data.GetFrameCount())
|
||||
return false;
|
||||
|
||||
return HasValidSelection();
|
||||
}
|
||||
|
||||
public bool AllSelected()
|
||||
{
|
||||
if (selectedIndices.Count != data.GetFrameCount())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int GetMaxDepth()
|
||||
{
|
||||
return (analysis == null) ? 1 : analysis.GetFrameSummary().maxMarkerDepth;
|
||||
}
|
||||
|
||||
int Clamp(int value, int min, int max)
|
||||
{
|
||||
if (value < min)
|
||||
value = min;
|
||||
else if (value > max)
|
||||
value = max;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public int ClampToValidDepthValue(int depthFilter)
|
||||
{
|
||||
// ProfileAnalyzer.kDepthAll is special case that we don't test for here
|
||||
|
||||
// If we have no depth values then return -1 for all (as clamp expects min<max)
|
||||
int maxDepth = GetMaxDepth();
|
||||
if (maxDepth < 1)
|
||||
return ProfileAnalyzer.kDepthAll;
|
||||
|
||||
return Clamp(depthFilter, 1, maxDepth);
|
||||
}
|
||||
|
||||
bool SelectAllFramesContainingMarker(string markerName, ProfileAnalysis inAnalysis)
|
||||
{
|
||||
if (inAnalysis == null)
|
||||
return false;
|
||||
|
||||
selectedIndices.Clear();
|
||||
|
||||
MarkerData markerData = inAnalysis.GetMarkerByName(markerName);
|
||||
if (markerData == null)
|
||||
return true;
|
||||
|
||||
foreach (var frameTime in markerData.frames)
|
||||
{
|
||||
selectedIndices.Add(frameTime.frameIndex);
|
||||
}
|
||||
|
||||
// Order from lowest to highest so the start/end frame display makes sense
|
||||
selectedIndices.Sort();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SelectAllFramesContainingMarker(string markerName, bool inSelection)
|
||||
{
|
||||
return SelectAllFramesContainingMarker(markerName, inSelection ? analysis : analysisFull);
|
||||
}
|
||||
|
||||
int ClampToRange(int value, int min, int max)
|
||||
{
|
||||
if (value < min)
|
||||
value = min;
|
||||
if (value > max)
|
||||
value = max;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public void ClearSelection()
|
||||
{
|
||||
selectedIndices.Clear();
|
||||
}
|
||||
|
||||
public void SelectFullRange()
|
||||
{
|
||||
selectedIndices.Clear();
|
||||
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
for (int offset = 0; offset < data.GetFrameCount(); offset++)
|
||||
{
|
||||
selectedIndices.Add(data.OffsetToDisplayFrame(offset));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 59e8a7346ec034a4387b6ca1ab20b83e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,939 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
class ProfileTreeViewItem : TreeViewItem
|
||||
{
|
||||
public MarkerData data { get; set; }
|
||||
public GUIContent[] cachedRowString;
|
||||
|
||||
public ProfileTreeViewItem(int id, int depth, string displayName, MarkerData data) : base(id, depth, displayName)
|
||||
{
|
||||
this.data = data;
|
||||
cachedRowString = null;
|
||||
}
|
||||
}
|
||||
|
||||
class ProfileTable : TreeView
|
||||
{
|
||||
Draw2D m_2D;
|
||||
ProfileDataView m_DataView;
|
||||
bool m_HideRemovedMarkers;
|
||||
ProfileAnalyzerWindow m_ProfileAnalyzerWindow;
|
||||
Color m_BarColor;
|
||||
float m_MaxMedian;
|
||||
int m_MaxCount;
|
||||
float m_MaxCountMean;
|
||||
double m_MaxTotal;
|
||||
|
||||
const float kRowHeights = 20f;
|
||||
readonly List<TreeViewItem> m_Rows = new List<TreeViewItem>(100);
|
||||
|
||||
// All columns
|
||||
public enum MyColumns
|
||||
{
|
||||
Name,
|
||||
State,
|
||||
Depth,
|
||||
Median,
|
||||
MedianBar,
|
||||
Mean,
|
||||
StandardDeviation,
|
||||
Min,
|
||||
Max,
|
||||
Range,
|
||||
Count,
|
||||
CountBar,
|
||||
CountMean,
|
||||
CountMeanBar,
|
||||
CountStandardDeviation,
|
||||
FirstFrame,
|
||||
AtMedian,
|
||||
Total,
|
||||
TotalBar,
|
||||
Threads,
|
||||
}
|
||||
|
||||
static int m_MaxColumns;
|
||||
|
||||
public enum SortOption
|
||||
{
|
||||
Name,
|
||||
State,
|
||||
Depth,
|
||||
Median,
|
||||
Mean,
|
||||
Min,
|
||||
Max,
|
||||
StandardDeviation,
|
||||
Range,
|
||||
Count,
|
||||
CountMean,
|
||||
CountStandardDeviation,
|
||||
FirstFrame,
|
||||
AtMedian,
|
||||
Total,
|
||||
Threads,
|
||||
}
|
||||
|
||||
// Sort options per column
|
||||
SortOption[] m_SortOptions =
|
||||
{
|
||||
SortOption.Name,
|
||||
SortOption.State,
|
||||
SortOption.Depth,
|
||||
SortOption.Median,
|
||||
SortOption.Median,
|
||||
SortOption.Mean,
|
||||
SortOption.StandardDeviation,
|
||||
SortOption.Min,
|
||||
SortOption.Max,
|
||||
SortOption.Range,
|
||||
SortOption.Count,
|
||||
SortOption.Count,
|
||||
SortOption.CountMean,
|
||||
SortOption.CountMean,
|
||||
SortOption.CountStandardDeviation,
|
||||
SortOption.FirstFrame,
|
||||
SortOption.AtMedian,
|
||||
SortOption.Total,
|
||||
SortOption.Total,
|
||||
SortOption.Threads,
|
||||
};
|
||||
|
||||
internal static class Styles
|
||||
{
|
||||
public static readonly GUIContent menuItemSelectFramesInAll = new GUIContent("Select Frames that contain this marker (within whole data set)", "");
|
||||
public static readonly GUIContent menuItemSelectFramesInCurrent = new GUIContent("Select Frames that contain this marker (within current selection)", "");
|
||||
//public static readonly GUIContent menuItemClearSelection = new GUIContent("Clear Selection");
|
||||
public static readonly GUIContent menuItemSelectFramesAll = new GUIContent("Select All");
|
||||
public static readonly GUIContent menuItemAddToIncludeFilter = new GUIContent("Add to Include Filter", "");
|
||||
public static readonly GUIContent menuItemAddToExcludeFilter = new GUIContent("Add to Exclude Filter", "");
|
||||
public static readonly GUIContent menuItemRemoveFromIncludeFilter = new GUIContent("Remove from Include Filter", "");
|
||||
public static readonly GUIContent menuItemRemoveFromExcludeFilter = new GUIContent("Remove from Exclude Filter", "");
|
||||
public static readonly GUIContent menuItemSetAsParentMarkerFilter = new GUIContent("Set as Parent Marker Filter", "");
|
||||
public static readonly GUIContent menuItemClearParentMarkerFilter = new GUIContent("Clear Parent Marker Filter", "");
|
||||
public static readonly GUIContent menuItemSetAsRemoveMarker = new GUIContent("Remove Marker", "");
|
||||
public static readonly GUIContent menuItemCopyToClipboard = new GUIContent("Copy to Clipboard", "");
|
||||
}
|
||||
|
||||
public ProfileTable(TreeViewState state, MultiColumnHeader multicolumnHeader, ProfileDataView dataView, bool hideRemovedMarkers, ProfileAnalyzerWindow profileAnalyzerWindow, Draw2D draw2D, Color barColor) : base(state, multicolumnHeader)
|
||||
{
|
||||
m_2D = draw2D;
|
||||
m_DataView = dataView;
|
||||
m_HideRemovedMarkers = hideRemovedMarkers;
|
||||
m_ProfileAnalyzerWindow = profileAnalyzerWindow;
|
||||
m_BarColor = barColor;
|
||||
|
||||
m_MaxColumns = Enum.GetValues(typeof(MyColumns)).Length;
|
||||
Assert.AreEqual(m_SortOptions.Length, m_MaxColumns, "Ensure number of sort options are in sync with number of MyColumns enum values");
|
||||
|
||||
// Custom setup
|
||||
rowHeight = kRowHeights;
|
||||
showAlternatingRowBackgrounds = true;
|
||||
showBorder = true;
|
||||
customFoldoutYOffset = (kRowHeights - EditorGUIUtility.singleLineHeight) * 0.5f; // center foldout in the row since we also center content. See RowGUI
|
||||
// extraSpaceBeforeIconAndLabel = 0;
|
||||
multicolumnHeader.sortingChanged += OnSortingChanged;
|
||||
multicolumnHeader.visibleColumnsChanged += OnVisibleColumnsChanged;
|
||||
|
||||
Reload();
|
||||
}
|
||||
|
||||
protected override TreeViewItem BuildRoot()
|
||||
{
|
||||
int idForhiddenRoot = -1;
|
||||
int depthForHiddenRoot = -1;
|
||||
ProfileTreeViewItem root = new ProfileTreeViewItem(idForhiddenRoot, depthForHiddenRoot, "root", null);
|
||||
|
||||
m_MaxMedian = 0.0f;
|
||||
m_MaxTotal = 0.0;
|
||||
m_MaxCount = 0;
|
||||
m_MaxCountMean = 0.0f;
|
||||
var markers = m_DataView.analysis.GetMarkers();
|
||||
for (int index = 0; index < markers.Count; ++index)
|
||||
{
|
||||
var marker = markers[index];
|
||||
if (!m_ProfileAnalyzerWindow.DoesMarkerPassFilter(marker.name))
|
||||
continue;
|
||||
|
||||
if (m_HideRemovedMarkers && marker.IsFullyIgnored())
|
||||
continue;
|
||||
|
||||
var item = new ProfileTreeViewItem(index, 0, marker.name, marker);
|
||||
root.AddChild(item);
|
||||
float ms = item.data.msMedian;
|
||||
if (ms > m_MaxMedian)
|
||||
m_MaxMedian = ms;
|
||||
|
||||
double msTotal = item.data.msTotal;
|
||||
if (msTotal > m_MaxTotal)
|
||||
m_MaxTotal = msTotal;
|
||||
|
||||
int count = item.data.count;
|
||||
if (count > m_MaxCount)
|
||||
m_MaxCount = count;
|
||||
|
||||
float countMean = item.data.countMean;
|
||||
if (countMean > m_MaxCountMean)
|
||||
m_MaxCountMean = countMean;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
|
||||
{
|
||||
m_Rows.Clear();
|
||||
|
||||
if (rootItem != null && rootItem.children != null)
|
||||
{
|
||||
foreach (ProfileTreeViewItem node in rootItem.children)
|
||||
{
|
||||
m_Rows.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
SortIfNeeded(m_Rows);
|
||||
|
||||
return m_Rows;
|
||||
}
|
||||
|
||||
void OnSortingChanged(MultiColumnHeader _multiColumnHeader)
|
||||
{
|
||||
SortIfNeeded(GetRows());
|
||||
}
|
||||
|
||||
protected virtual void OnVisibleColumnsChanged(MultiColumnHeader multiColumnHeader)
|
||||
{
|
||||
m_ProfileAnalyzerWindow.SetSingleModeColumns(multiColumnHeader.state.visibleColumns);
|
||||
multiColumnHeader.ResizeToFit();
|
||||
}
|
||||
|
||||
void SortIfNeeded(IList<TreeViewItem> rows)
|
||||
{
|
||||
if (rows.Count <= 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (multiColumnHeader.sortedColumnIndex == -1)
|
||||
{
|
||||
return; // No column to sort for (just use the order the data are in)
|
||||
}
|
||||
|
||||
// Sort the roots of the existing tree items
|
||||
SortByMultipleColumns();
|
||||
|
||||
// Update the data with the sorted content
|
||||
rows.Clear();
|
||||
foreach (ProfileTreeViewItem node in rootItem.children)
|
||||
{
|
||||
rows.Add(node);
|
||||
}
|
||||
|
||||
Repaint();
|
||||
}
|
||||
|
||||
string GetThreadName(ProfileTreeViewItem item)
|
||||
{
|
||||
return m_ProfileAnalyzerWindow.GetUIThreadName(item.data.threads[0]);
|
||||
}
|
||||
|
||||
string GetThreadNames(ProfileTreeViewItem item)
|
||||
{
|
||||
var uiNames = new List<string>();
|
||||
foreach (string threadNameWithIndex in item.data.threads)
|
||||
{
|
||||
string uiName = m_ProfileAnalyzerWindow.GetUIThreadName(threadNameWithIndex);
|
||||
|
||||
uiNames.Add(uiName);
|
||||
}
|
||||
uiNames.Sort(m_ProfileAnalyzerWindow.CompareUINames);
|
||||
|
||||
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
||||
bool first = true;
|
||||
foreach (var uiName in uiNames)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
sb.Append(", ");
|
||||
sb.Append(uiName);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
void SortByMultipleColumns()
|
||||
{
|
||||
var sortedColumns = multiColumnHeader.state.sortedColumns;
|
||||
|
||||
if (sortedColumns.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var myTypes = rootItem.children.Cast<ProfileTreeViewItem>();
|
||||
var orderedQuery = InitialOrder(myTypes, sortedColumns);
|
||||
for (int i = 1; i < sortedColumns.Length; i++)
|
||||
{
|
||||
SortOption sortOption = m_SortOptions[sortedColumns[i]];
|
||||
bool ascending = multiColumnHeader.IsSortedAscending(sortedColumns[i]);
|
||||
|
||||
switch (sortOption)
|
||||
{
|
||||
case SortOption.Name:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.name, ascending);
|
||||
break;
|
||||
case SortOption.State:
|
||||
orderedQuery = orderedQuery.ThenBy(l => State(l), ascending);
|
||||
break;
|
||||
case SortOption.Depth:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.minDepth, ascending);
|
||||
break;
|
||||
case SortOption.Mean:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.msMean, ascending);
|
||||
break;
|
||||
case SortOption.Median:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.msMedian, ascending);
|
||||
break;
|
||||
case SortOption.StandardDeviation:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.msStandardDeviation, ascending);
|
||||
break;
|
||||
case SortOption.Min:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.msMin, ascending);
|
||||
break;
|
||||
case SortOption.Max:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.msMax, ascending);
|
||||
break;
|
||||
case SortOption.Range:
|
||||
orderedQuery = orderedQuery.ThenBy(l => (l.data.msMax - l.data.msMin), ascending);
|
||||
break;
|
||||
case SortOption.Count:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.count, ascending);
|
||||
break;
|
||||
case SortOption.CountMean:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.countMean, ascending);
|
||||
break;
|
||||
case SortOption.CountStandardDeviation:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.countStandardDeviation, ascending);
|
||||
break;
|
||||
case SortOption.FirstFrame:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.firstFrameIndex, ascending);
|
||||
break;
|
||||
case SortOption.AtMedian:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.msAtMedian, ascending);
|
||||
break;
|
||||
case SortOption.Total:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.data.msTotal, ascending);
|
||||
break;
|
||||
case SortOption.Threads:
|
||||
orderedQuery = orderedQuery.ThenBy(l => l.cachedRowString != null ? l.cachedRowString[(int)MyColumns.Threads].text : GetThreadNames(l), ascending);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rootItem.children = orderedQuery.Cast<TreeViewItem>().ToList();
|
||||
}
|
||||
|
||||
IOrderedEnumerable<ProfileTreeViewItem> InitialOrder(IEnumerable<ProfileTreeViewItem> myTypes, int[] history)
|
||||
{
|
||||
SortOption sortOption = m_SortOptions[history[0]];
|
||||
bool ascending = multiColumnHeader.IsSortedAscending(history[0]);
|
||||
switch (sortOption)
|
||||
{
|
||||
case SortOption.Name:
|
||||
return myTypes.Order(l => l.data.name, ascending);
|
||||
case SortOption.State:
|
||||
return myTypes.Order(l => State(l), ascending);
|
||||
case SortOption.Depth:
|
||||
return myTypes.Order(l => l.data.minDepth, ascending);
|
||||
case SortOption.Mean:
|
||||
return myTypes.Order(l => l.data.msMean, ascending);
|
||||
case SortOption.Median:
|
||||
return myTypes.Order(l => l.data.msMedian, ascending);
|
||||
case SortOption.StandardDeviation:
|
||||
return myTypes.Order(l => l.data.msStandardDeviation, ascending);
|
||||
case SortOption.Min:
|
||||
return myTypes.Order(l => l.data.msMin, ascending);
|
||||
case SortOption.Max:
|
||||
return myTypes.Order(l => l.data.msMax, ascending);
|
||||
case SortOption.Range:
|
||||
return myTypes.Order(l => (l.data.msMax - l.data.msMin), ascending);
|
||||
case SortOption.Count:
|
||||
return myTypes.Order(l => l.data.count, ascending);
|
||||
case SortOption.CountMean:
|
||||
return myTypes.Order(l => l.data.countMean, ascending);
|
||||
case SortOption.CountStandardDeviation:
|
||||
return myTypes.Order(l => l.data.countStandardDeviation, ascending);
|
||||
case SortOption.FirstFrame:
|
||||
return myTypes.Order(l => l.data.firstFrameIndex, ascending);
|
||||
case SortOption.AtMedian:
|
||||
return myTypes.Order(l => l.data.msAtMedian, ascending);
|
||||
case SortOption.Total:
|
||||
return myTypes.Order(l => l.data.msTotal, ascending);
|
||||
case SortOption.Threads:
|
||||
return myTypes.Order(l => l.cachedRowString != null ? l.cachedRowString[(int)MyColumns.Threads].text : GetThreadNames(l), ascending);
|
||||
default:
|
||||
Assert.IsTrue(false, "Unhandled enum");
|
||||
break;
|
||||
}
|
||||
|
||||
// default
|
||||
return myTypes.Order(l => l.data.name, ascending);
|
||||
}
|
||||
|
||||
int State(ProfileTreeViewItem item)
|
||||
{
|
||||
if (item.data.timeRemoved > 0.0)
|
||||
{
|
||||
return -3;
|
||||
}
|
||||
if (item.data.timeIgnored > 0.0)
|
||||
{
|
||||
if (item.data.IsFullyIgnored())
|
||||
return -2;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public bool ShowingHorizontalScroll
|
||||
{
|
||||
get
|
||||
{
|
||||
return showingHorizontalScrollBar;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RowGUI(RowGUIArgs args)
|
||||
{
|
||||
var item = (ProfileTreeViewItem)args.item;
|
||||
|
||||
var clipRect = m_2D.GetClipRect();
|
||||
clipRect.y = state.scrollPos.y;
|
||||
clipRect.x = state.scrollPos.x;
|
||||
m_2D.SetClipRect(clipRect);
|
||||
|
||||
if (item.cachedRowString == null)
|
||||
{
|
||||
GenerateStrings(item);
|
||||
}
|
||||
|
||||
for (int i = 0; i < args.GetNumVisibleColumns(); ++i)
|
||||
{
|
||||
CellGUI(args.GetCellRect(i), item, (MyColumns)args.GetColumn(i), ref args);
|
||||
}
|
||||
m_2D.ClearClipRect();
|
||||
}
|
||||
|
||||
string ToDisplayUnits(float ms, bool showUnits = false, bool showFullValueWhenBelowZero = false)
|
||||
{
|
||||
return m_ProfileAnalyzerWindow.ToDisplayUnits(ms, showUnits, 0, showFullValueWhenBelowZero);
|
||||
}
|
||||
|
||||
string ToDisplayUnits(double ms, bool showUnits = false, bool showFullValueWhenBelowZero = false)
|
||||
{
|
||||
return m_ProfileAnalyzerWindow.ToDisplayUnits(ms, showUnits, 0, showFullValueWhenBelowZero);
|
||||
}
|
||||
|
||||
string ToTooltipDisplayUnits(float ms, bool showUnits = false, int onFrame = -1)
|
||||
{
|
||||
return m_ProfileAnalyzerWindow.ToTooltipDisplayUnits(ms, showUnits, onFrame);
|
||||
}
|
||||
|
||||
string ToTooltipDisplayUnits(double ms, bool showUnits = false, int onFrame = -1)
|
||||
{
|
||||
return ToTooltipDisplayUnits((float)ms, showUnits, onFrame);
|
||||
}
|
||||
|
||||
GUIContent ToDisplayUnitsWithTooltips(float ms, bool showUnits = false, int onFrame = -1)
|
||||
{
|
||||
return m_ProfileAnalyzerWindow.ToDisplayUnitsWithTooltips(ms, showUnits, onFrame);
|
||||
}
|
||||
|
||||
GUIContent ToDisplayUnitsWithTooltips(double ms, bool showUnits = false, int onFrame = -1)
|
||||
{
|
||||
return ToDisplayUnitsWithTooltips((float)ms, showUnits, onFrame);
|
||||
}
|
||||
|
||||
void CopyToClipboard(Event current, string text)
|
||||
{
|
||||
EditorGUIUtility.systemCopyBuffer = text;
|
||||
}
|
||||
|
||||
GenericMenu GenerateActiveContextMenu(string markerName, Event evt, GUIContent content)
|
||||
{
|
||||
GenericMenu menu = new GenericMenu();
|
||||
|
||||
menu.AddItem(Styles.menuItemSelectFramesInAll, false, () => m_ProfileAnalyzerWindow.SelectFramesContainingMarker(markerName, false));
|
||||
menu.AddItem(Styles.menuItemSelectFramesInCurrent, false, () => m_ProfileAnalyzerWindow.SelectFramesContainingMarker(markerName, true));
|
||||
|
||||
if (m_ProfileAnalyzerWindow.AllSelected())
|
||||
menu.AddDisabledItem(Styles.menuItemSelectFramesAll);
|
||||
else
|
||||
menu.AddItem(Styles.menuItemSelectFramesAll, false, () => m_ProfileAnalyzerWindow.SelectAllFrames());
|
||||
|
||||
menu.AddSeparator("");
|
||||
if (!m_ProfileAnalyzerWindow.GetNameFilters().Contains(markerName, StringComparer.OrdinalIgnoreCase))
|
||||
menu.AddItem(Styles.menuItemAddToIncludeFilter, false, () => m_ProfileAnalyzerWindow.AddToIncludeFilter(markerName));
|
||||
else
|
||||
menu.AddItem(Styles.menuItemRemoveFromIncludeFilter, false, () => m_ProfileAnalyzerWindow.RemoveFromIncludeFilter(markerName));
|
||||
if (!m_ProfileAnalyzerWindow.GetNameExcludes().Contains(markerName, StringComparer.OrdinalIgnoreCase))
|
||||
menu.AddItem(Styles.menuItemAddToExcludeFilter, false, () => m_ProfileAnalyzerWindow.AddToExcludeFilter(markerName));
|
||||
else
|
||||
menu.AddItem(Styles.menuItemRemoveFromExcludeFilter, false, () => m_ProfileAnalyzerWindow.RemoveFromExcludeFilter(markerName));
|
||||
menu.AddSeparator("");
|
||||
menu.AddItem(Styles.menuItemSetAsParentMarkerFilter, false, () => m_ProfileAnalyzerWindow.SetAsParentMarkerFilter(markerName));
|
||||
menu.AddItem(Styles.menuItemClearParentMarkerFilter, false, () => m_ProfileAnalyzerWindow.SetAsParentMarkerFilter(""));
|
||||
menu.AddSeparator("");
|
||||
menu.AddItem(Styles.menuItemSetAsRemoveMarker, false, () => m_ProfileAnalyzerWindow.SetAsRemoveMarker(markerName));
|
||||
menu.AddSeparator("");
|
||||
if (markerName != null && !string.IsNullOrEmpty(markerName))
|
||||
menu.AddItem(Styles.menuItemCopyToClipboard, false, () => CopyToClipboard(evt, markerName));
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
GenericMenu GenerateDisabledContextMenu(string markerName, GUIContent content)
|
||||
{
|
||||
GenericMenu menu = new GenericMenu();
|
||||
|
||||
menu.AddDisabledItem(Styles.menuItemSelectFramesInAll);
|
||||
menu.AddDisabledItem(Styles.menuItemSelectFramesInCurrent);
|
||||
menu.AddDisabledItem(Styles.menuItemSelectFramesAll);
|
||||
|
||||
menu.AddSeparator("");
|
||||
if (!m_ProfileAnalyzerWindow.GetNameFilters().Contains(markerName, StringComparer.OrdinalIgnoreCase))
|
||||
menu.AddDisabledItem(Styles.menuItemAddToIncludeFilter);
|
||||
else
|
||||
menu.AddDisabledItem(Styles.menuItemRemoveFromIncludeFilter);
|
||||
if (!m_ProfileAnalyzerWindow.GetNameExcludes().Contains(markerName, StringComparer.OrdinalIgnoreCase))
|
||||
menu.AddDisabledItem(Styles.menuItemAddToExcludeFilter);
|
||||
else
|
||||
menu.AddDisabledItem(Styles.menuItemRemoveFromExcludeFilter);
|
||||
menu.AddSeparator("");
|
||||
menu.AddDisabledItem(Styles.menuItemSetAsParentMarkerFilter);
|
||||
menu.AddDisabledItem(Styles.menuItemClearParentMarkerFilter);
|
||||
menu.AddSeparator("");
|
||||
menu.AddDisabledItem(Styles.menuItemSetAsRemoveMarker);
|
||||
menu.AddSeparator("");
|
||||
if (content != null && !string.IsNullOrEmpty(content.text))
|
||||
menu.AddDisabledItem(Styles.menuItemCopyToClipboard);
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
|
||||
void ShowContextMenu(Rect cellRect, string markerName, GUIContent content)
|
||||
{
|
||||
Event current = Event.current;
|
||||
if (cellRect.Contains(current.mousePosition) && current.type == EventType.ContextClick)
|
||||
{
|
||||
GenericMenu menu;
|
||||
if (!m_ProfileAnalyzerWindow.IsAnalysisRunning())
|
||||
menu = GenerateActiveContextMenu(markerName, current, content);
|
||||
else
|
||||
menu = GenerateDisabledContextMenu(markerName, content);
|
||||
|
||||
menu.ShowAsContext();
|
||||
|
||||
current.Use();
|
||||
}
|
||||
}
|
||||
|
||||
void ShowText(Rect rect, string text)
|
||||
{
|
||||
EditorGUI.LabelField(rect, text);
|
||||
//EditorGUI.TextArea(rect, text);
|
||||
}
|
||||
|
||||
void ShowText(Rect rect, GUIContent content)
|
||||
{
|
||||
EditorGUI.LabelField(rect, content);
|
||||
//ShowText(rect, content.text);
|
||||
}
|
||||
|
||||
void GenerateStrings(ProfileTreeViewItem item)
|
||||
{
|
||||
item.cachedRowString = new GUIContent[m_MaxColumns];
|
||||
|
||||
int medianFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(item.data.medianFrameIndex, m_DataView);
|
||||
int minFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(item.data.minFrameIndex, m_DataView);
|
||||
int maxFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(item.data.maxFrameIndex, m_DataView);
|
||||
int firstFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(item.data.firstFrameIndex, m_DataView);
|
||||
int frameSummaryMedianFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(m_DataView.analysis.GetFrameSummary().medianFrameIndex, m_DataView);
|
||||
|
||||
if (item.data.timeRemoved > 0.0)
|
||||
{
|
||||
item.cachedRowString[(int)MyColumns.Name] = new GUIContent(item.data.name + " [Modified]", item.data.name + "\n\nTime reduced by removing child marker time");
|
||||
item.cachedRowString[(int)MyColumns.State] = new GUIContent("Modified", "Time reduced by removing child marker time");
|
||||
}
|
||||
else if (item.data.timeIgnored > 0.0)
|
||||
{
|
||||
if (item.data.IsFullyIgnored())
|
||||
{
|
||||
item.cachedRowString[(int)MyColumns.Name] = new GUIContent(item.data.name + " [Removed]", item.data.name + "\n\nAll marker time removed");
|
||||
item.cachedRowString[(int)MyColumns.State] = new GUIContent("Removed", "All marker time removed");
|
||||
}
|
||||
else
|
||||
{
|
||||
item.cachedRowString[(int)MyColumns.Name] = new GUIContent(item.data.name + " [Partial Removal]", item.data.name + "\n\nSome marker time removed (some instances)");
|
||||
item.cachedRowString[(int)MyColumns.State] = new GUIContent("Partial Removal", "Some marker time removed (some instances)");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
item.cachedRowString[(int)MyColumns.Name] = new GUIContent(item.data.name, item.data.name);
|
||||
item.cachedRowString[(int)MyColumns.State] = new GUIContent("", "");
|
||||
}
|
||||
item.cachedRowString[(int)MyColumns.Mean] = ToDisplayUnitsWithTooltips(item.data.msMean, false);
|
||||
item.cachedRowString[(int)MyColumns.Depth] = (item.data.minDepth == item.data.maxDepth) ? new GUIContent(string.Format("{0}", item.data.minDepth), "") : new GUIContent(string.Format("{0}-{1}", item.data.minDepth, item.data.maxDepth), "");
|
||||
item.cachedRowString[(int)MyColumns.Median] = ToDisplayUnitsWithTooltips(item.data.msMedian, false, medianFrameIndex);
|
||||
string tooltip = ToTooltipDisplayUnits(item.data.msMedian, true, medianFrameIndex);
|
||||
item.cachedRowString[(int)MyColumns.MedianBar] = new GUIContent("", tooltip);
|
||||
item.cachedRowString[(int)MyColumns.StandardDeviation] = ToDisplayUnitsWithTooltips(item.data.msStandardDeviation, false);
|
||||
item.cachedRowString[(int)MyColumns.Min] = ToDisplayUnitsWithTooltips(item.data.msMin, false, minFrameIndex);
|
||||
item.cachedRowString[(int)MyColumns.Max] = ToDisplayUnitsWithTooltips(item.data.msMax, false, maxFrameIndex);
|
||||
item.cachedRowString[(int)MyColumns.Range] = ToDisplayUnitsWithTooltips(item.data.msMax - item.data.msMin);
|
||||
item.cachedRowString[(int)MyColumns.Count] = new GUIContent(string.Format("{0}", item.data.count), "");
|
||||
item.cachedRowString[(int)MyColumns.CountBar] = new GUIContent("", string.Format("{0}", item.data.count));
|
||||
item.cachedRowString[(int)MyColumns.CountMean] = new GUIContent(string.Format(CultureInfo.InvariantCulture, "{0:f0}", item.data.countMean), "");
|
||||
item.cachedRowString[(int)MyColumns.CountMeanBar] = new GUIContent("", string.Format(CultureInfo.InvariantCulture, "{0:f0}", item.data.countMean));
|
||||
item.cachedRowString[(int)MyColumns.CountStandardDeviation] = new GUIContent(string.Format(CultureInfo.InvariantCulture, "{0:f0}", item.data.countStandardDeviation), string.Format(CultureInfo.InvariantCulture, "{0}", item.data.countStandardDeviation));
|
||||
item.cachedRowString[(int)MyColumns.FirstFrame] = new GUIContent(firstFrameIndex.ToString());
|
||||
item.cachedRowString[(int)MyColumns.AtMedian] = ToDisplayUnitsWithTooltips(item.data.msAtMedian, false, frameSummaryMedianFrameIndex);
|
||||
item.cachedRowString[(int)MyColumns.Total] = ToDisplayUnitsWithTooltips(item.data.msTotal);
|
||||
tooltip = ToTooltipDisplayUnits(item.data.msTotal, true, medianFrameIndex);
|
||||
item.cachedRowString[(int)MyColumns.TotalBar] = new GUIContent("", tooltip);
|
||||
|
||||
string threadNames = GetThreadNames(item);
|
||||
item.cachedRowString[(int)MyColumns.Threads] = new GUIContent(threadNames, threadNames);
|
||||
}
|
||||
|
||||
void ShowBar(Rect rect, float ms, float range, GUIContent content)
|
||||
{
|
||||
if (ms > 0.0f)
|
||||
{
|
||||
if (m_2D.DrawStart(rect))
|
||||
{
|
||||
float w = Math.Max(1.0f, rect.width * ms / range);
|
||||
m_2D.DrawFilledBox(0, 1, w, rect.height - 1, m_BarColor);
|
||||
m_2D.DrawEnd();
|
||||
}
|
||||
}
|
||||
GUI.Label(rect, content);
|
||||
}
|
||||
|
||||
void CellGUI(Rect cellRect, ProfileTreeViewItem item, MyColumns column, ref RowGUIArgs args)
|
||||
{
|
||||
// Center cell rect vertically (makes it easier to place controls, icons etc in the cells)
|
||||
CenterRectUsingSingleLineHeight(ref cellRect);
|
||||
|
||||
GUIContent content = item.cachedRowString[(int)column];
|
||||
switch (column)
|
||||
{
|
||||
case MyColumns.Name:
|
||||
{
|
||||
args.rowRect = cellRect;
|
||||
//base.RowGUI(args);
|
||||
//content = new GUIContent(item.data.name, item.data.name);
|
||||
ShowText(cellRect, content);
|
||||
}
|
||||
break;
|
||||
|
||||
case MyColumns.State:
|
||||
case MyColumns.Mean:
|
||||
case MyColumns.Depth:
|
||||
case MyColumns.Median:
|
||||
case MyColumns.StandardDeviation:
|
||||
case MyColumns.Min:
|
||||
case MyColumns.Max:
|
||||
case MyColumns.Range:
|
||||
case MyColumns.Count:
|
||||
case MyColumns.CountMean:
|
||||
case MyColumns.CountStandardDeviation:
|
||||
case MyColumns.AtMedian:
|
||||
case MyColumns.Total:
|
||||
case MyColumns.Threads:
|
||||
ShowText(cellRect, content);
|
||||
break;
|
||||
case MyColumns.MedianBar:
|
||||
ShowBar(cellRect, item.data.msMedian, m_MaxMedian, content);
|
||||
break;
|
||||
case MyColumns.TotalBar:
|
||||
ShowBar(cellRect, (float)item.data.msTotal, (float)m_MaxTotal, content);
|
||||
break;
|
||||
case MyColumns.CountBar:
|
||||
ShowBar(cellRect, item.data.count, m_MaxCount, content);
|
||||
break;
|
||||
case MyColumns.CountMeanBar:
|
||||
ShowBar(cellRect, item.data.countMean, m_MaxCountMean, content);
|
||||
break;
|
||||
case MyColumns.FirstFrame:
|
||||
if (!m_ProfileAnalyzerWindow.IsProfilerWindowOpen() || !m_DataView.inSyncWithProfilerData)
|
||||
GUI.enabled = false;
|
||||
if (GUI.Button(cellRect, content))
|
||||
{
|
||||
m_ProfileAnalyzerWindow.SelectMarkerByIndex(item.id);
|
||||
m_ProfileAnalyzerWindow.JumpToFrame(item.data.firstFrameIndex, m_DataView.data);
|
||||
}
|
||||
|
||||
GUI.enabled = true;
|
||||
break;
|
||||
}
|
||||
|
||||
ShowContextMenu(cellRect, item.data.name, content);
|
||||
}
|
||||
|
||||
// Misc
|
||||
//--------
|
||||
|
||||
protected override bool CanMultiSelect(TreeViewItem item)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
struct HeaderData
|
||||
{
|
||||
public readonly GUIContent content;
|
||||
public readonly float width;
|
||||
public readonly float minWidth;
|
||||
public readonly bool autoResize;
|
||||
public readonly bool allowToggleVisibility;
|
||||
public readonly bool ascending;
|
||||
|
||||
public HeaderData(string name, string tooltip = "", float width = 50, float minWidth = 30, bool autoResize = true, bool allowToggleVisibility = true, bool ascending = false)
|
||||
{
|
||||
content = new GUIContent(name, tooltip);
|
||||
this.width = width;
|
||||
this.minWidth = minWidth;
|
||||
this.autoResize = autoResize;
|
||||
this.allowToggleVisibility = allowToggleVisibility;
|
||||
this.ascending = ascending;
|
||||
}
|
||||
}
|
||||
|
||||
public static MultiColumnHeaderState CreateDefaultMultiColumnHeaderState(MarkerColumnFilter modeFilter)
|
||||
{
|
||||
var columnList = new List<MultiColumnHeaderState.Column>();
|
||||
HeaderData[] headerData = new HeaderData[]
|
||||
{
|
||||
new HeaderData("Marker Name", "Marker Name\n\nFrame marker time is total of all instances in frame", width: 300, minWidth: 100, autoResize: false, allowToggleVisibility: false, ascending: true),
|
||||
new HeaderData("State", "Status of marker entry (if modified or removed from frame time due to 'Remove' filter)"),
|
||||
new HeaderData("Depth", "Marker depth in marker hierarchy\n\nMay appear at multiple levels"),
|
||||
new HeaderData("Median", "Central marker time over all selected frames\n\nAlways present in data set\n1st of 2 central values for even frame count"),
|
||||
new HeaderData("Median Bar", "Central marker time over all selected frames", width: 50),
|
||||
new HeaderData("Mean", "Per frame marker time / number of non zero frames"),
|
||||
new HeaderData("SD", "Standard deviation in marker times"),
|
||||
new HeaderData("Min", "Minimum marker time"),
|
||||
new HeaderData("Max", "Maximum marker time"),
|
||||
new HeaderData("Range", "Difference between maximum and minimum"),
|
||||
new HeaderData("Count", "Marker count over all selected frames\n\nMultiple can occur per frame"),
|
||||
new HeaderData("Count Bar", "Marker count over all selected frames\n\nMultiple can occur per frame"),
|
||||
new HeaderData("Count Frame", "Average number of markers per frame\n\ntotal count / number of non zero frames", width: 70, minWidth: 50),
|
||||
new HeaderData("Count Frame Bar", "Average number of markers per frame\n\ntotal count / number of non zero frames", width: 70, minWidth: 50),
|
||||
new HeaderData("Count SD", "Standard deviation in marker per frame counts"),
|
||||
new HeaderData("1st", "First frame index that the marker appears on"),
|
||||
new HeaderData("At Median Frame", "Marker time on the median frame\n\nI.e. Marker total duration on the average frame", width: 90, minWidth: 50),
|
||||
new HeaderData("Total", "Marker total time over all selected frames"),
|
||||
new HeaderData("Total Bar", "Marker total time over all selected frames"),
|
||||
new HeaderData("Threads", "Threads the marker occurs on (with filtering applied)"),
|
||||
};
|
||||
foreach (var header in headerData)
|
||||
{
|
||||
columnList.Add(new MultiColumnHeaderState.Column
|
||||
{
|
||||
headerContent = header.content,
|
||||
headerTextAlignment = TextAlignment.Left,
|
||||
sortedAscending = header.ascending,
|
||||
sortingArrowAlignment = TextAlignment.Left,
|
||||
width = header.width,
|
||||
minWidth = header.minWidth,
|
||||
autoResize = header.autoResize,
|
||||
allowToggleVisibility = header.allowToggleVisibility
|
||||
});
|
||||
}
|
||||
;
|
||||
var columns = columnList.ToArray();
|
||||
|
||||
m_MaxColumns = Enum.GetValues(typeof(MyColumns)).Length;
|
||||
Assert.AreEqual(columns.Length, m_MaxColumns, "Number of columns should match number of enum values: You probably forgot to update one of them.");
|
||||
|
||||
var state = new MultiColumnHeaderState(columns);
|
||||
SetMode(modeFilter, state);
|
||||
return state;
|
||||
}
|
||||
|
||||
protected override void SelectionChanged(IList<int> selectedIds)
|
||||
{
|
||||
base.SelectionChanged(selectedIds);
|
||||
|
||||
if (selectedIds.Count > 0)
|
||||
{
|
||||
m_ProfileAnalyzerWindow.SelectMarkerByIndex(selectedIds[0]);
|
||||
// A newly selected marker changes the marker summary's GUI content, conflicting with the previous layout pass. We need to exit GUI and re-layout.
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
}
|
||||
|
||||
static int[] GetDefaultVisibleColumns(MarkerColumnFilter.Mode mode)
|
||||
{
|
||||
int[] visibleColumns;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
default:
|
||||
case MarkerColumnFilter.Mode.Custom:
|
||||
case MarkerColumnFilter.Mode.TimeAndCount:
|
||||
visibleColumns = new int[]
|
||||
{
|
||||
(int)MyColumns.Name,
|
||||
(int)MyColumns.Depth,
|
||||
(int)MyColumns.Median,
|
||||
(int)MyColumns.MedianBar,
|
||||
(int)MyColumns.Mean,
|
||||
(int)MyColumns.Min,
|
||||
(int)MyColumns.Max,
|
||||
(int)MyColumns.Range,
|
||||
(int)MyColumns.Count,
|
||||
(int)MyColumns.CountMean,
|
||||
(int)MyColumns.AtMedian,
|
||||
};
|
||||
break;
|
||||
case MarkerColumnFilter.Mode.Time:
|
||||
visibleColumns = new int[]
|
||||
{
|
||||
(int)MyColumns.Name,
|
||||
(int)MyColumns.Depth,
|
||||
(int)MyColumns.Median,
|
||||
(int)MyColumns.MedianBar,
|
||||
(int)MyColumns.Min,
|
||||
(int)MyColumns.Max,
|
||||
(int)MyColumns.Range,
|
||||
(int)MyColumns.AtMedian,
|
||||
};
|
||||
break;
|
||||
case MarkerColumnFilter.Mode.Totals:
|
||||
visibleColumns = new int[]
|
||||
{
|
||||
(int)MyColumns.Name,
|
||||
(int)MyColumns.Depth,
|
||||
(int)MyColumns.Total,
|
||||
(int)MyColumns.TotalBar,
|
||||
};
|
||||
break;
|
||||
case MarkerColumnFilter.Mode.TimeWithTotals:
|
||||
visibleColumns = new int[]
|
||||
{
|
||||
(int)MyColumns.Name,
|
||||
(int)MyColumns.Depth,
|
||||
(int)MyColumns.Median,
|
||||
(int)MyColumns.MedianBar,
|
||||
(int)MyColumns.Min,
|
||||
(int)MyColumns.Max,
|
||||
(int)MyColumns.Range,
|
||||
(int)MyColumns.AtMedian,
|
||||
(int)MyColumns.Total,
|
||||
(int)MyColumns.TotalBar,
|
||||
};
|
||||
break;
|
||||
case MarkerColumnFilter.Mode.CountTotals:
|
||||
visibleColumns = new int[]
|
||||
{
|
||||
(int)MyColumns.Name,
|
||||
(int)MyColumns.Depth,
|
||||
(int)MyColumns.Count,
|
||||
(int)MyColumns.CountBar,
|
||||
};
|
||||
break;
|
||||
case MarkerColumnFilter.Mode.CountPerFrame:
|
||||
visibleColumns = new int[]
|
||||
{
|
||||
(int)MyColumns.Name,
|
||||
(int)MyColumns.Depth,
|
||||
(int)MyColumns.CountMean,
|
||||
(int)MyColumns.CountMeanBar,
|
||||
};
|
||||
break;
|
||||
case MarkerColumnFilter.Mode.Depth:
|
||||
visibleColumns = new int[]
|
||||
{
|
||||
(int)MyColumns.Name,
|
||||
(int)MyColumns.Depth,
|
||||
};
|
||||
break;
|
||||
case MarkerColumnFilter.Mode.Threads:
|
||||
visibleColumns = new int[]
|
||||
{
|
||||
(int)MyColumns.Name,
|
||||
(int)MyColumns.Threads,
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
return visibleColumns;
|
||||
}
|
||||
|
||||
static void SetMode(MarkerColumnFilter modeFilter, MultiColumnHeaderState state)
|
||||
{
|
||||
switch (modeFilter.mode)
|
||||
{
|
||||
case MarkerColumnFilter.Mode.Custom:
|
||||
if (modeFilter.visibleColumns == null)
|
||||
state.visibleColumns = GetDefaultVisibleColumns(modeFilter.mode);
|
||||
else
|
||||
state.visibleColumns = modeFilter.visibleColumns;
|
||||
break;
|
||||
default:
|
||||
state.visibleColumns = GetDefaultVisibleColumns(modeFilter.mode);
|
||||
break;
|
||||
}
|
||||
|
||||
if (modeFilter.visibleColumns == null)
|
||||
modeFilter.visibleColumns = state.visibleColumns;
|
||||
}
|
||||
|
||||
public void SetMode(MarkerColumnFilter modeFilter)
|
||||
{
|
||||
SetMode(modeFilter, multiColumnHeader.state);
|
||||
multiColumnHeader.ResizeToFit();
|
||||
}
|
||||
}
|
||||
|
||||
static class MyExtensionMethods
|
||||
{
|
||||
public static IOrderedEnumerable<T> Order<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector, bool ascending)
|
||||
{
|
||||
if (ascending)
|
||||
{
|
||||
return source.OrderBy(selector);
|
||||
}
|
||||
else
|
||||
{
|
||||
return source.OrderByDescending(selector);
|
||||
}
|
||||
}
|
||||
|
||||
public static IOrderedEnumerable<T> ThenBy<T, TKey>(this IOrderedEnumerable<T> source, Func<T, TKey> selector, bool ascending)
|
||||
{
|
||||
if (ascending)
|
||||
{
|
||||
return source.ThenBy(selector);
|
||||
}
|
||||
else
|
||||
{
|
||||
return source.ThenByDescending(selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 981ca102f47f145c1977153854cce718
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,854 @@
|
||||
using UnityEditorInternal;
|
||||
using System.Reflection;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine.Profiling;
|
||||
using UnityEditor.Profiling;
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
using Unity.Profiling.Editor;
|
||||
// stub so that ProfilerWindow can be moved to this namespace in trunk without a need to change PA
|
||||
namespace Unity.Profiling.Editor {}
|
||||
#endif
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
internal class ProfilerWindowInterface
|
||||
{
|
||||
bool m_ProfilerWindowInitialized = false;
|
||||
const float k_NsToMs = 1000000;
|
||||
ProgressBarDisplay m_progressBar;
|
||||
|
||||
[NonSerialized] bool m_SendingSelectionEventToProfilerWindowInProgress = false;
|
||||
[NonSerialized] int m_LastSelectedFrameInProfilerWindow = 0;
|
||||
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
[NonSerialized] ProfilerWindow m_ProfilerWindow;
|
||||
[NonSerialized] IProfilerFrameTimeViewSampleSelectionController m_CpuProfilerModule;
|
||||
#else
|
||||
Type m_ProfilerWindowType;
|
||||
EditorWindow m_ProfilerWindow;
|
||||
FieldInfo m_CurrentFrameFieldInfo;
|
||||
FieldInfo m_TimeLineGUIFieldInfo;
|
||||
FieldInfo m_SelectedEntryFieldInfo;
|
||||
FieldInfo m_SelectedNameFieldInfo;
|
||||
FieldInfo m_SelectedTimeFieldInfo;
|
||||
FieldInfo m_SelectedDurationFieldInfo;
|
||||
FieldInfo m_SelectedInstanceIdFieldInfo;
|
||||
FieldInfo m_SelectedFrameIdFieldInfo;
|
||||
FieldInfo m_SelectedThreadIndexFieldInfo;
|
||||
FieldInfo m_SelectedNativeIndexFieldInfo;
|
||||
FieldInfo m_SelectedInstanceCountFieldInfo;
|
||||
FieldInfo m_SelectedInstanceCountForThreadFieldInfo;
|
||||
FieldInfo m_SelectedInstanceCountForFrameFieldInfo;
|
||||
FieldInfo m_SelectedMetaDataFieldInfo;
|
||||
FieldInfo m_SelectedThreadCountFieldInfo;
|
||||
FieldInfo m_SelectedCallstackInfoFieldInfo;
|
||||
|
||||
MethodInfo m_GetProfilerModuleInfo;
|
||||
Type m_CPUProfilerModuleType;
|
||||
#endif
|
||||
|
||||
public ProfilerWindowInterface(ProgressBarDisplay progressBar)
|
||||
{
|
||||
m_progressBar = progressBar;
|
||||
|
||||
#if !UNITY_2021_1_OR_NEWER
|
||||
Assembly assem = typeof(Editor).Assembly;
|
||||
m_ProfilerWindowType = assem.GetType("UnityEditor.ProfilerWindow");
|
||||
m_CurrentFrameFieldInfo = m_ProfilerWindowType.GetField("m_CurrentFrame", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
m_TimeLineGUIFieldInfo = m_ProfilerWindowType.GetField("m_CPUTimelineGUI", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (m_TimeLineGUIFieldInfo == null)
|
||||
{
|
||||
// m_CPUTimelineGUI isn't present in 2019.3.0a8 onward
|
||||
m_GetProfilerModuleInfo = m_ProfilerWindowType.GetMethod("GetProfilerModule", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (m_GetProfilerModuleInfo == null)
|
||||
{
|
||||
Debug.Log("Unable to initialise link to Profiler Timeline, no GetProfilerModule found");
|
||||
}
|
||||
|
||||
m_CPUProfilerModuleType = assem.GetType("UnityEditorInternal.Profiling.CPUProfilerModule");
|
||||
m_TimeLineGUIFieldInfo = m_CPUProfilerModuleType.GetField("m_TimelineGUI", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (m_TimeLineGUIFieldInfo == null)
|
||||
{
|
||||
Debug.Log("Unable to initialise link to Profiler Timeline");
|
||||
}
|
||||
}
|
||||
|
||||
if (m_TimeLineGUIFieldInfo != null)
|
||||
m_SelectedEntryFieldInfo = m_TimeLineGUIFieldInfo.FieldType.GetField("m_SelectedEntry", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (m_SelectedEntryFieldInfo != null)
|
||||
{
|
||||
m_SelectedNameFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("name", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedTimeFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("time", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedDurationFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("duration", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedInstanceIdFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("instanceId", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedFrameIdFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("frameId", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
// confusingly this is called threadId but is the thread _index_
|
||||
m_SelectedThreadIndexFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("threadId", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedNativeIndexFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("nativeIndex", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedInstanceCountFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("instanceCount", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedInstanceCountForThreadFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("instanceCountForThread", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedInstanceCountForFrameFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("instanceCountForFrame", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedThreadCountFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("threadCount", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedMetaDataFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("metaData", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
m_SelectedCallstackInfoFieldInfo = m_SelectedEntryFieldInfo.FieldType.GetField("callstackInfo", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public bool IsReady()
|
||||
{
|
||||
return m_ProfilerWindow != null && m_ProfilerWindowInitialized;
|
||||
}
|
||||
|
||||
public void GetProfilerWindowHandle()
|
||||
{
|
||||
Profiler.BeginSample("GetProfilerWindowHandle");
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
if (m_CpuProfilerModule != null)
|
||||
{
|
||||
m_CpuProfilerModule.selectionChanged -= OnSelectionChangedInCpuProfilerModule;
|
||||
m_CpuProfilerModule = null;
|
||||
}
|
||||
|
||||
var windows = Resources.FindObjectsOfTypeAll<ProfilerWindow>();
|
||||
if (windows != null && windows.Length > 0)
|
||||
m_ProfilerWindow = windows[0];
|
||||
if (m_ProfilerWindow != null)
|
||||
{
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
var cpuModuleIdentifier = ProfilerWindow.cpuModuleIdentifier;
|
||||
#else
|
||||
var cpuModuleIdentifier = ProfilerWindow.cpuModuleName;
|
||||
#endif
|
||||
m_CpuProfilerModule =
|
||||
m_ProfilerWindow.GetFrameTimeViewSampleSelectionController(cpuModuleIdentifier);
|
||||
m_CpuProfilerModule.selectionChanged -= OnSelectionChangedInCpuProfilerModule;
|
||||
m_CpuProfilerModule.selectionChanged += OnSelectionChangedInCpuProfilerModule;
|
||||
|
||||
m_ProfilerWindow.Repaint();
|
||||
m_ProfilerWindowInitialized = false;
|
||||
// wait a frame for the Profiler to get Repainted
|
||||
EditorApplication.delayCall += () => m_ProfilerWindowInitialized = true;
|
||||
}
|
||||
#else
|
||||
UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(m_ProfilerWindowType);
|
||||
if (windows != null && windows.Length > 0)
|
||||
m_ProfilerWindow = (EditorWindow)windows[0];
|
||||
m_ProfilerWindowInitialized = true;
|
||||
#endif
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
public void OpenProfilerOrUseExisting()
|
||||
{
|
||||
// Note we use existing if possible to fix a bug after domain reload
|
||||
// Where calling EditorWindow.GetWindow directly causes a second window to open
|
||||
if (m_ProfilerWindow == null)
|
||||
{
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
m_ProfilerWindow = EditorWindow.GetWindow<ProfilerWindow>();
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
var cpuModuleIdentifier = ProfilerWindow.cpuModuleIdentifier;
|
||||
#else
|
||||
var cpuModuleIdentifier = ProfilerWindow.cpuModuleName;
|
||||
#endif
|
||||
m_CpuProfilerModule = m_ProfilerWindow.GetFrameTimeViewSampleSelectionController(cpuModuleIdentifier);
|
||||
m_CpuProfilerModule.selectionChanged -= OnSelectionChangedInCpuProfilerModule;
|
||||
m_CpuProfilerModule.selectionChanged += OnSelectionChangedInCpuProfilerModule;
|
||||
#else
|
||||
// Create new
|
||||
m_ProfilerWindow = EditorWindow.GetWindow(m_ProfilerWindowType);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public bool GetFrameRangeFromProfiler(out int first, out int last)
|
||||
{
|
||||
if (m_ProfilerWindow != null)
|
||||
{
|
||||
first = 1 + ProfilerDriver.firstFrameIndex;
|
||||
last = 1 + ProfilerDriver.lastFrameIndex;
|
||||
return true;
|
||||
}
|
||||
|
||||
first = 1;
|
||||
last = 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void CloseProfiler()
|
||||
{
|
||||
if (m_ProfilerWindow != null)
|
||||
m_ProfilerWindow.Close();
|
||||
}
|
||||
|
||||
#if !UNITY_2021_1_OR_NEWER
|
||||
object GetTimeLineGUI()
|
||||
{
|
||||
object timeLineGUI = null;
|
||||
|
||||
if (m_CPUProfilerModuleType != null)
|
||||
{
|
||||
object[] parametersArray = new object[] { ProfilerArea.CPU };
|
||||
var getCPUProfilerModuleInfo = m_GetProfilerModuleInfo.MakeGenericMethod(m_CPUProfilerModuleType);
|
||||
var cpuModule = getCPUProfilerModuleInfo.Invoke(m_ProfilerWindow, parametersArray);
|
||||
|
||||
timeLineGUI = m_TimeLineGUIFieldInfo.GetValue(cpuModule);
|
||||
}
|
||||
else if (m_TimeLineGUIFieldInfo != null)
|
||||
{
|
||||
timeLineGUI = m_TimeLineGUIFieldInfo.GetValue(m_ProfilerWindow);
|
||||
}
|
||||
|
||||
return timeLineGUI;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
private void OnSelectionChangedInCpuProfilerModule(IProfilerFrameTimeViewSampleSelectionController controller, ProfilerTimeSampleSelection selection)
|
||||
{
|
||||
if (controller == m_CpuProfilerModule && !m_SendingSelectionEventToProfilerWindowInProgress)
|
||||
{
|
||||
if (selection != null && selection.markerNamePath != null && selection.markerNamePath.Count > 0)
|
||||
{
|
||||
selectedMarkerChanged(selection.markerNamePath[selection.markerNamePath.Count - 1], selection.threadGroupName, selection.threadName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public event Action<string, string, string> selectedMarkerChanged = delegate {};
|
||||
|
||||
public void PollProfilerWindowMarkerName()
|
||||
{
|
||||
#if !UNITY_2021_1_OR_NEWER
|
||||
if (m_ProfilerWindow != null)
|
||||
{
|
||||
var timeLineGUI = GetTimeLineGUI();
|
||||
if (timeLineGUI != null && m_SelectedEntryFieldInfo != null)
|
||||
{
|
||||
var selectedEntry = m_SelectedEntryFieldInfo.GetValue(timeLineGUI);
|
||||
if (selectedEntry != null && m_SelectedNameFieldInfo != null)
|
||||
{
|
||||
string threadGroupName = null;
|
||||
string threadName = null;
|
||||
if (m_SelectedFrameIdFieldInfo != null && m_SelectedThreadIndexFieldInfo != null)
|
||||
{
|
||||
using (RawFrameDataView frameData = ProfilerDriver.GetRawFrameDataView((int)m_SelectedFrameIdFieldInfo.GetValue(selectedEntry), (int)m_SelectedThreadIndexFieldInfo.GetValue(selectedEntry)))
|
||||
{
|
||||
if (frameData != null && frameData.valid)
|
||||
{
|
||||
threadGroupName = frameData.threadGroupName;
|
||||
threadName = frameData.threadName;
|
||||
}
|
||||
}
|
||||
}
|
||||
selectedMarkerChanged(m_SelectedNameFieldInfo.GetValue(selectedEntry).ToString(), threadGroupName, threadName);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public ProfileData PullFromProfiler(int firstFrameDisplayIndex, int lastFrameDisplayIndex)
|
||||
{
|
||||
Profiler.BeginSample("ProfilerWindowInterface.PullFromProfiler");
|
||||
|
||||
bool recording = IsRecording();
|
||||
if (recording)
|
||||
StopRecording();
|
||||
|
||||
int firstFrameIndex = Mathf.Max(firstFrameDisplayIndex - 1, 0);
|
||||
int lastFrameIndex = lastFrameDisplayIndex - 1;
|
||||
ProfileData profileData = GetData(firstFrameIndex, lastFrameIndex);
|
||||
|
||||
if (recording)
|
||||
StartRecording();
|
||||
|
||||
Profiler.EndSample();
|
||||
return profileData;
|
||||
}
|
||||
|
||||
public int GetThreadCountForFrame(int frameIndex)
|
||||
{
|
||||
if (!IsReady())
|
||||
return 0;
|
||||
|
||||
ProfilerFrameDataIterator frameData = new ProfilerFrameDataIterator();
|
||||
frameData.SetRoot(frameIndex, 0);
|
||||
return frameData.GetThreadCount(frameIndex);
|
||||
}
|
||||
|
||||
public ProfileFrame GetProfileFrameForThread(int frameIndex, int threadIndex)
|
||||
{
|
||||
if (!IsReady())
|
||||
return null;
|
||||
|
||||
var frame = new ProfileFrame();
|
||||
using (RawFrameDataView frameData = ProfilerDriver.GetRawFrameDataView(frameIndex, threadIndex))
|
||||
{
|
||||
frame.msStartTime = frameData.frameStartTimeMs;
|
||||
frame.msFrame = frameData.frameTimeMs;
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
ProfileData GetDataRaw(ProfileData data, int firstFrameIndex, int lastFrameIndex)
|
||||
{
|
||||
bool firstError = true;
|
||||
|
||||
data.SetFrameIndexOffset(firstFrameIndex);
|
||||
|
||||
var depthStack = new Stack<int>();
|
||||
|
||||
var threadNameCount = new Dictionary<string, int>();
|
||||
var markerIdToNameIndex = new Dictionary<int, int>();
|
||||
|
||||
for (int frameIndex = firstFrameIndex; frameIndex <= lastFrameIndex; ++frameIndex)
|
||||
{
|
||||
m_progressBar.AdvanceProgressBar();
|
||||
|
||||
int threadIndex = 0;
|
||||
|
||||
threadNameCount.Clear();
|
||||
ProfileFrame frame = null;
|
||||
while (true)
|
||||
{
|
||||
using (RawFrameDataView frameData = ProfilerDriver.GetRawFrameDataView(frameIndex, threadIndex))
|
||||
{
|
||||
if (threadIndex == 0)
|
||||
{
|
||||
if ((frameIndex == firstFrameIndex || frameIndex == lastFrameIndex)
|
||||
&& firstFrameIndex != lastFrameIndex && (!frameData.valid || frameData.frameTimeNs == 0))
|
||||
{
|
||||
// skip incomplete frames when they are at the beginning or end of the capture
|
||||
if (++frameIndex <= lastFrameIndex)
|
||||
{
|
||||
data.FirstFrameIncomplete = true;
|
||||
data.SetFrameIndexOffset(frameIndex);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// break out entirely if this is the last frame
|
||||
data.LastFrameIncomplete = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
frame = new ProfileFrame();
|
||||
if (frameData.valid)
|
||||
{
|
||||
frame.msStartTime = frameData.frameStartTimeMs;
|
||||
frame.msFrame = frameData.frameTimeMs;
|
||||
}
|
||||
data.Add(frame);
|
||||
}
|
||||
|
||||
if (!frameData.valid)
|
||||
break;
|
||||
|
||||
string threadNameWithIndex = null;
|
||||
string threadName = frameData.threadName;
|
||||
if (threadName.Trim() == "")
|
||||
{
|
||||
Debug.Log(string.Format("Warning: Unnamed thread found on frame {0}. Corrupted data suspected, ignoring frame", frameIndex));
|
||||
threadIndex++;
|
||||
continue;
|
||||
}
|
||||
var groupName = frameData.threadGroupName;
|
||||
threadName = ProfileData.GetThreadNameWithGroup(threadName, groupName);
|
||||
|
||||
int nameCount = 0;
|
||||
threadNameCount.TryGetValue(threadName, out nameCount);
|
||||
threadNameCount[threadName] = nameCount + 1;
|
||||
|
||||
threadNameWithIndex = ProfileData.ThreadNameWithIndex(threadNameCount[threadName], threadName);
|
||||
|
||||
var thread = new ProfileThread();
|
||||
data.AddThreadName(threadNameWithIndex, thread);
|
||||
|
||||
frame.Add(thread);
|
||||
|
||||
// The markers are in depth first order
|
||||
depthStack.Clear();
|
||||
// first sample is the thread name
|
||||
for (int i = 1; i < frameData.sampleCount; i++)
|
||||
{
|
||||
float durationMS = frameData.GetSampleTimeMs(i);
|
||||
int markerId = frameData.GetSampleMarkerId(i);
|
||||
if (durationMS < 0)
|
||||
{
|
||||
if (firstError)
|
||||
{
|
||||
int displayIndex = data.OffsetToDisplayFrame(frameIndex);
|
||||
|
||||
string name = frameData.GetSampleName(i);
|
||||
Debug.LogFormat("Ignoring Invalid marker time found for {0} on frame {1} on thread {2} ({3} < 0)",
|
||||
name, displayIndex, threadName, durationMS);
|
||||
|
||||
firstError = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int depth = 1 + depthStack.Count;
|
||||
var markerData = ProfileMarker.Create(durationMS, depth);
|
||||
|
||||
// Use name index directly if we have already stored this named marker before
|
||||
int nameIndex;
|
||||
if (markerIdToNameIndex.TryGetValue(markerId, out nameIndex))
|
||||
{
|
||||
markerData.nameIndex = nameIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
string name = frameData.GetSampleName(i);
|
||||
data.AddMarkerName(name, markerData);
|
||||
markerIdToNameIndex[markerId] = markerData.nameIndex;
|
||||
}
|
||||
|
||||
thread.AddMarker(markerData);
|
||||
}
|
||||
|
||||
int childrenCount = frameData.GetSampleChildrenCount(i);
|
||||
if (childrenCount > 0)
|
||||
{
|
||||
depthStack.Push(childrenCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (depthStack.Count > 0)
|
||||
{
|
||||
int remainingChildren = depthStack.Pop();
|
||||
if (remainingChildren > 1)
|
||||
{
|
||||
depthStack.Push(remainingChildren - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
threadIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
data.Finalise();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
ProfileData GetDataOriginal(ProfileData data, int firstFrameIndex, int lastFrameIndex)
|
||||
{
|
||||
ProfilerFrameDataIterator frameData = new ProfilerFrameDataIterator();
|
||||
bool firstError = true;
|
||||
|
||||
data.SetFrameIndexOffset(firstFrameIndex);
|
||||
|
||||
Dictionary<string, int> threadNameCount = new Dictionary<string, int>();
|
||||
for (int frameIndex = firstFrameIndex; frameIndex <= lastFrameIndex; ++frameIndex)
|
||||
{
|
||||
m_progressBar.AdvanceProgressBar();
|
||||
|
||||
int threadCount = frameData.GetThreadCount(frameIndex);
|
||||
frameData.SetRoot(frameIndex, 0);
|
||||
|
||||
var msFrame = frameData.frameTimeMS;
|
||||
|
||||
if ((frameIndex == firstFrameIndex || frameIndex == lastFrameIndex)
|
||||
&& firstFrameIndex != lastFrameIndex && msFrame == 0)
|
||||
{
|
||||
var nextFrame = frameIndex + 1;
|
||||
// skip incomplete frames when they are at the beginning or end of the capture
|
||||
if (nextFrame <= lastFrameIndex)
|
||||
{
|
||||
data.FirstFrameIncomplete = true;
|
||||
data.SetFrameIndexOffset(nextFrame);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// break out entirely if this is the last frame
|
||||
data.LastFrameIncomplete = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if (frameIndex == lastFrameIndex)
|
||||
{
|
||||
// Check if last frame appears to be invalid data
|
||||
float median;
|
||||
float mean;
|
||||
float standardDeviation;
|
||||
CalculateFrameTimeStats(data, out median, out mean, out standardDeviation);
|
||||
float execessiveDeviation = (3f * standardDeviation);
|
||||
if (msFrame > (median + execessiveDeviation))
|
||||
{
|
||||
Debug.LogFormat("Dropping last frame as it is significantly larger than the median of the rest of the data set {0} > {1} (median {2} + 3 * standard deviation {3})", msFrame, median + execessiveDeviation, median, standardDeviation);
|
||||
break;
|
||||
}
|
||||
if (msFrame < (median - execessiveDeviation))
|
||||
{
|
||||
Debug.LogFormat("Dropping last frame as it is significantly smaller than the median of the rest of the data set {0} < {1} (median {2} - 3 * standard deviation {3})", msFrame, median - execessiveDeviation, median, standardDeviation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
ProfileFrame frame = new ProfileFrame();
|
||||
frame.msStartTime = 1000.0 * frameData.GetFrameStartS(frameIndex);
|
||||
frame.msFrame = msFrame;
|
||||
data.Add(frame);
|
||||
|
||||
threadNameCount.Clear();
|
||||
for (int threadIndex = 0; threadIndex < threadCount; ++threadIndex)
|
||||
{
|
||||
frameData.SetRoot(frameIndex, threadIndex);
|
||||
|
||||
var threadName = frameData.GetThreadName();
|
||||
if (threadName.Trim() == "")
|
||||
{
|
||||
Debug.Log(string.Format("Warning: Unnamed thread found on frame {0}. Corrupted data suspected, ignoring frame", frameIndex));
|
||||
continue;
|
||||
}
|
||||
|
||||
var groupName = frameData.GetGroupName();
|
||||
threadName = ProfileData.GetThreadNameWithGroup(threadName, groupName);
|
||||
|
||||
ProfileThread thread = new ProfileThread();
|
||||
frame.Add(thread);
|
||||
|
||||
int nameCount = 0;
|
||||
threadNameCount.TryGetValue(threadName, out nameCount);
|
||||
threadNameCount[threadName] = nameCount + 1;
|
||||
|
||||
data.AddThreadName(ProfileData.ThreadNameWithIndex(threadNameCount[threadName], threadName), thread);
|
||||
|
||||
const bool enterChildren = true;
|
||||
// The markers are in depth first order and the depth is known
|
||||
// So we can infer a parent child relationship
|
||||
while (frameData.Next(enterChildren))
|
||||
{
|
||||
if (frameData.durationMS < 0)
|
||||
{
|
||||
if (firstError)
|
||||
{
|
||||
int displayIndex = data.OffsetToDisplayFrame(frameIndex);
|
||||
|
||||
Debug.LogFormat("Ignoring Invalid marker time found for {0} on frame {1} on thread {2} ({3} < 0) : Instance id : {4}",
|
||||
frameData.name, displayIndex, threadName, frameData.durationMS, frameData.instanceId);
|
||||
|
||||
firstError = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
var markerData = ProfileMarker.Create(frameData);
|
||||
|
||||
data.AddMarkerName(frameData.name, markerData);
|
||||
thread.AddMarker(markerData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.Finalise();
|
||||
|
||||
frameData.Dispose();
|
||||
return data;
|
||||
}
|
||||
|
||||
ProfileData GetData(int firstFrameIndex, int lastFrameIndex)
|
||||
{
|
||||
ProfileData data = new ProfileData(ProfileAnalyzerWindow.TmpPath);
|
||||
GetDataRaw(data, firstFrameIndex, lastFrameIndex);
|
||||
data.Write();
|
||||
return data;
|
||||
}
|
||||
|
||||
public float GetFrameTimeRaw(int frameIndex)
|
||||
{
|
||||
using (RawFrameDataView frameData = ProfilerDriver.GetRawFrameDataView(frameIndex, 0))
|
||||
{
|
||||
if (!frameData.valid)
|
||||
return 0f;
|
||||
|
||||
return frameData.frameTimeMs;
|
||||
}
|
||||
}
|
||||
|
||||
public float GetFrameTime(int frameIndex)
|
||||
{
|
||||
return GetFrameTimeRaw(frameIndex);
|
||||
}
|
||||
|
||||
struct ThreadIndexIterator
|
||||
{
|
||||
public ProfilerFrameDataIterator frameData;
|
||||
public int threadIndex;
|
||||
}
|
||||
|
||||
IEnumerator<ThreadIndexIterator> GetNextThreadIndexFittingThreadFilters(int frameIndex, List<string> threadFilters)
|
||||
{
|
||||
ProfilerFrameDataIterator frameData = new ProfilerFrameDataIterator();
|
||||
|
||||
int threadCount = frameData.GetThreadCount(frameIndex);
|
||||
Dictionary<string, int> threadNameCount = new Dictionary<string, int>();
|
||||
for (int threadIndex = 0; threadIndex < threadCount; ++threadIndex)
|
||||
{
|
||||
frameData.SetRoot(frameIndex, threadIndex);
|
||||
|
||||
var threadName = frameData.GetThreadName();
|
||||
// Name here could be "Worker Thread 1"
|
||||
|
||||
var groupName = frameData.GetGroupName();
|
||||
threadName = ProfileData.GetThreadNameWithGroup(threadName, groupName);
|
||||
|
||||
int nameCount = 0;
|
||||
threadNameCount.TryGetValue(threadName, out nameCount);
|
||||
threadNameCount[threadName] = nameCount + 1;
|
||||
|
||||
var threadNameWithIndex = ProfileData.ThreadNameWithIndex(threadNameCount[threadName], threadName);
|
||||
|
||||
// To compare on the filter we need to remove the postfix on the thread name
|
||||
// "3:Worker Thread 0" -> "1:Worker Thread"
|
||||
// The index of the thread (0) is used +1 as a prefix
|
||||
// The preceding number (3) is the count of number of threads with this name
|
||||
// Unfortunately multiple threads can have the same name
|
||||
threadNameWithIndex = ProfileData.CorrectThreadName(threadNameWithIndex);
|
||||
|
||||
if (threadFilters.Contains(threadNameWithIndex))
|
||||
{
|
||||
yield return new ThreadIndexIterator {frameData = frameData, threadIndex = threadIndex};
|
||||
}
|
||||
}
|
||||
frameData.Dispose();
|
||||
}
|
||||
|
||||
bool GetMarkerInfo(string markerName, int frameIndex, List<string> threadFilters, out int outThreadIndex, out int outNativeIndex, out float time, out float duration, out int instanceId)
|
||||
{
|
||||
outThreadIndex = 0;
|
||||
outNativeIndex = 0;
|
||||
time = 0.0f;
|
||||
duration = 0.0f;
|
||||
instanceId = 0;
|
||||
bool found = false;
|
||||
|
||||
var iterator = GetNextThreadIndexFittingThreadFilters(frameIndex, threadFilters);
|
||||
while (iterator.MoveNext())
|
||||
{
|
||||
const bool enterChildren = true;
|
||||
while (iterator.Current.frameData.Next(enterChildren))
|
||||
{
|
||||
if (iterator.Current.frameData.name == markerName)
|
||||
{
|
||||
time = iterator.Current.frameData.startTimeMS;
|
||||
duration = iterator.Current.frameData.durationMS;
|
||||
instanceId = iterator.Current.frameData.instanceId;
|
||||
outNativeIndex = iterator.Current.frameData.sampleId;
|
||||
outThreadIndex = iterator.Current.threadIndex;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
public bool SetProfilerWindowMarkerName(string markerName, List<string> threadFilters)
|
||||
{
|
||||
m_SendingSelectionEventToProfilerWindowInProgress = true;
|
||||
if (m_ProfilerWindow == null)
|
||||
return false;
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
var cpuModuleIdentifier = ProfilerWindow.cpuModuleIdentifier;
|
||||
var selectedModuleIdentifier = m_ProfilerWindow.selectedModuleIdentifier;
|
||||
#else
|
||||
var cpuModuleIdentifier = ProfilerWindow.cpuModuleName;
|
||||
var selectedModuleIdentifier = m_ProfilerWindow.selectedModuleName;
|
||||
#endif
|
||||
m_CpuProfilerModule = m_ProfilerWindow.GetFrameTimeViewSampleSelectionController(cpuModuleIdentifier);
|
||||
if (m_CpuProfilerModule != null && selectedModuleIdentifier == cpuModuleIdentifier
|
||||
&& m_ProfilerWindow.firstAvailableFrameIndex >= 0)
|
||||
{
|
||||
// Read profiler data direct from profile to find time/duration
|
||||
int currentFrameIndex = (int)m_ProfilerWindow.selectedFrameIndex;
|
||||
|
||||
var iterator = GetNextThreadIndexFittingThreadFilters(currentFrameIndex, threadFilters);
|
||||
while (iterator.MoveNext())
|
||||
{
|
||||
using (var rawFrameDataView = ProfilerDriver.GetRawFrameDataView(currentFrameIndex, iterator.Current.threadIndex))
|
||||
{
|
||||
if (m_CpuProfilerModule.SetSelection(currentFrameIndex,
|
||||
rawFrameDataView.threadGroupName, rawFrameDataView.threadName, markerName,
|
||||
threadId: rawFrameDataView.threadId))
|
||||
{
|
||||
m_ProfilerWindow.Repaint();
|
||||
m_SendingSelectionEventToProfilerWindowInProgress = false;
|
||||
return true; // setting the selection was successful, nothing more to do here.
|
||||
}
|
||||
}
|
||||
}
|
||||
// selection couldn't be found, so clear the current one to avoid confusion
|
||||
m_CpuProfilerModule.ClearSelection();
|
||||
m_ProfilerWindow.Repaint();
|
||||
}
|
||||
#else
|
||||
var timeLineGUI = GetTimeLineGUI();
|
||||
if (timeLineGUI == null)
|
||||
{
|
||||
m_SendingSelectionEventToProfilerWindowInProgress = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_SelectedEntryFieldInfo != null)
|
||||
{
|
||||
var selectedEntry = m_SelectedEntryFieldInfo.GetValue(timeLineGUI);
|
||||
if (selectedEntry != null)
|
||||
{
|
||||
// Read profiler data direct from profile to find time/duration
|
||||
int currentFrameIndex = (int)m_CurrentFrameFieldInfo.GetValue(m_ProfilerWindow);
|
||||
float time;
|
||||
float duration;
|
||||
int instanceId;
|
||||
int nativeIndex;
|
||||
int threadIndex;
|
||||
if (GetMarkerInfo(markerName, currentFrameIndex, threadFilters, out threadIndex, out nativeIndex, out time, out duration, out instanceId))
|
||||
{
|
||||
/*
|
||||
Debug.Log(string.Format("Setting profiler to {0} on {1} at frame {2} at {3}ms for {4}ms ({5})",
|
||||
markerName, currentFrameIndex, threadFilter, time, duration, instanceId));
|
||||
*/
|
||||
|
||||
if (m_SelectedNameFieldInfo != null)
|
||||
m_SelectedNameFieldInfo.SetValue(selectedEntry, markerName);
|
||||
if (m_SelectedTimeFieldInfo != null)
|
||||
m_SelectedTimeFieldInfo.SetValue(selectedEntry, time);
|
||||
if (m_SelectedDurationFieldInfo != null)
|
||||
m_SelectedDurationFieldInfo.SetValue(selectedEntry, duration);
|
||||
if (m_SelectedInstanceIdFieldInfo != null)
|
||||
m_SelectedInstanceIdFieldInfo.SetValue(selectedEntry, instanceId);
|
||||
if (m_SelectedFrameIdFieldInfo != null)
|
||||
m_SelectedFrameIdFieldInfo.SetValue(selectedEntry, currentFrameIndex);
|
||||
if (m_SelectedNativeIndexFieldInfo != null)
|
||||
m_SelectedNativeIndexFieldInfo.SetValue(selectedEntry, nativeIndex);
|
||||
if (m_SelectedThreadIndexFieldInfo != null)
|
||||
m_SelectedThreadIndexFieldInfo.SetValue(selectedEntry, threadIndex);
|
||||
|
||||
// TODO : Update to fill in the total and number of instances.
|
||||
// For now we force Instance count to 1 to avoid the incorrect info showing.
|
||||
if (m_SelectedInstanceCountFieldInfo != null)
|
||||
m_SelectedInstanceCountFieldInfo.SetValue(selectedEntry, 1);
|
||||
if (m_SelectedInstanceCountForThreadFieldInfo != null)
|
||||
m_SelectedInstanceCountForThreadFieldInfo.SetValue(selectedEntry, 1);
|
||||
if (m_SelectedInstanceCountForFrameFieldInfo != null)
|
||||
m_SelectedInstanceCountForFrameFieldInfo.SetValue(selectedEntry, 1);
|
||||
if (m_SelectedThreadCountFieldInfo != null)
|
||||
m_SelectedThreadCountFieldInfo.SetValue(selectedEntry, 1);
|
||||
if (m_SelectedMetaDataFieldInfo != null)
|
||||
m_SelectedMetaDataFieldInfo.SetValue(selectedEntry, "");
|
||||
if (m_SelectedCallstackInfoFieldInfo != null)
|
||||
m_SelectedCallstackInfoFieldInfo.SetValue(selectedEntry, "");
|
||||
|
||||
m_ProfilerWindow.Repaint();
|
||||
m_SendingSelectionEventToProfilerWindowInProgress = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
m_SendingSelectionEventToProfilerWindowInProgress = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool JumpToFrame(int index)
|
||||
{
|
||||
if (m_ProfilerWindow == null)
|
||||
return false;
|
||||
|
||||
if (index - 1 < ProfilerDriver.firstFrameIndex)
|
||||
return false;
|
||||
if (index - 1 > ProfilerDriver.lastFrameIndex)
|
||||
return false;
|
||||
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
m_ProfilerWindow.selectedFrameIndex = index - 1;
|
||||
#else
|
||||
m_CurrentFrameFieldInfo.SetValue(m_ProfilerWindow, index - 1);
|
||||
#endif
|
||||
m_ProfilerWindow.Repaint();
|
||||
return true;
|
||||
}
|
||||
|
||||
public int selectedFrame
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_ProfilerWindow == null)
|
||||
return 0;
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
return (int)m_ProfilerWindow.selectedFrameIndex + 1;
|
||||
#else
|
||||
return (int)m_CurrentFrameFieldInfo.GetValue(m_ProfilerWindow) + 1;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<int> selectedFrameChanged = delegate {};
|
||||
|
||||
public void PollSelectedFrameChanges()
|
||||
{
|
||||
var currentlySelectedFrame = selectedFrame;
|
||||
if (m_LastSelectedFrameInProfilerWindow != currentlySelectedFrame && !m_SendingSelectionEventToProfilerWindowInProgress)
|
||||
{
|
||||
m_LastSelectedFrameInProfilerWindow = currentlySelectedFrame;
|
||||
selectedFrameChanged(currentlySelectedFrame);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRecording()
|
||||
{
|
||||
return ProfilerDriver.enabled;
|
||||
}
|
||||
|
||||
public void StopRecording()
|
||||
{
|
||||
// Stop recording first
|
||||
ProfilerDriver.enabled = false;
|
||||
}
|
||||
|
||||
public void StartRecording()
|
||||
{
|
||||
// Stop recording first
|
||||
ProfilerDriver.enabled = true;
|
||||
}
|
||||
|
||||
public void OnDisable()
|
||||
{
|
||||
if (m_ProfilerWindow != null)
|
||||
{
|
||||
m_ProfilerWindow = null;
|
||||
}
|
||||
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
if (m_CpuProfilerModule != null)
|
||||
{
|
||||
m_CpuProfilerModule.selectionChanged -= OnSelectionChangedInCpuProfilerModule;
|
||||
m_CpuProfilerModule = null;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d8e7ffaacab264ab6ba5aa70f432785f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,36 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
internal class ProgressBarDisplay
|
||||
{
|
||||
int m_TotalFrames;
|
||||
int m_CurrentFrame;
|
||||
string m_Title;
|
||||
string m_Description;
|
||||
|
||||
public void InitProgressBar(string title, string description, int frames)
|
||||
{
|
||||
m_CurrentFrame = 0;
|
||||
m_TotalFrames = frames;
|
||||
|
||||
m_Title = title;
|
||||
m_Description = description;
|
||||
|
||||
EditorUtility.DisplayProgressBar(m_Title, m_Description, m_CurrentFrame);
|
||||
}
|
||||
|
||||
public void AdvanceProgressBar()
|
||||
{
|
||||
m_CurrentFrame++;
|
||||
int currentFrame = Mathf.Clamp(0, m_CurrentFrame, m_TotalFrames);
|
||||
float progress = m_TotalFrames > 0 ? (float)currentFrame / m_TotalFrames : 0f;
|
||||
EditorUtility.DisplayProgressBar(m_Title, m_Description, progress);
|
||||
}
|
||||
|
||||
public void ClearProgressBar()
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c27dfe7bd1055403b9dac63caf69c135
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
[Serializable]
|
||||
internal class ThreadData
|
||||
{
|
||||
public string threadNameWithIndex;
|
||||
public int threadGroupIndex;
|
||||
public string threadGroupName;
|
||||
public int threadsInGroup;
|
||||
public List<ThreadFrameTime> frames = new List<ThreadFrameTime>();
|
||||
|
||||
public float msMedian;
|
||||
public float msLowerQuartile;
|
||||
public float msUpperQuartile;
|
||||
public float msMin;
|
||||
public float msMax;
|
||||
|
||||
public int medianFrameIndex;
|
||||
public int minFrameIndex;
|
||||
public int maxFrameIndex;
|
||||
|
||||
public ThreadData(string _threadName)
|
||||
{
|
||||
threadNameWithIndex = _threadName;
|
||||
|
||||
var info = threadNameWithIndex.Split(':');
|
||||
threadGroupIndex = int.Parse(info[0]);
|
||||
threadGroupName = info[1];
|
||||
threadsInGroup = 1;
|
||||
|
||||
msMedian = 0.0f;
|
||||
msLowerQuartile = 0.0f;
|
||||
msUpperQuartile = 0.0f;
|
||||
msMin = 0.0f;
|
||||
msMax = 0.0f;
|
||||
|
||||
medianFrameIndex = -1;
|
||||
minFrameIndex = -1;
|
||||
maxFrameIndex = -1;
|
||||
}
|
||||
|
||||
struct ThreadFrameTimeFrameComparer : IComparer<ThreadFrameTime>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int Compare(ThreadFrameTime x, ThreadFrameTime y)
|
||||
{
|
||||
if (x.frameIndex == y.frameIndex)
|
||||
return 0;
|
||||
return x.frameIndex > y.frameIndex ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ThreadFrameTime? GetFrame(int frameIndex)
|
||||
{
|
||||
var index = frames.BinarySearch(new ThreadFrameTime(frameIndex, 0, 0), new ThreadFrameTimeFrameComparer());
|
||||
return index >= 0 ? (ThreadFrameTime?)frames[index] : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0c2bd11491424d188d23f3ae3e16b79
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.Performance.ProfileAnalyzer
|
||||
{
|
||||
[Serializable]
|
||||
internal struct ThreadFrameTime : IComparable<ThreadFrameTime>
|
||||
{
|
||||
public int frameIndex;
|
||||
public float ms;
|
||||
public float msIdle;
|
||||
|
||||
public ThreadFrameTime(int index, float msTime, float msTimeIdle)
|
||||
{
|
||||
frameIndex = index;
|
||||
ms = msTime;
|
||||
msIdle = msTimeIdle;
|
||||
}
|
||||
|
||||
public int CompareTo(ThreadFrameTime other)
|
||||
{
|
||||
return ms.CompareTo(other.ms);
|
||||
}
|
||||
}
|
||||
}
|
||||