您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
508 行
18 KiB
508 行
18 KiB
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using BoatAttack.Benchmark;
|
|
using UnityEditor;
|
|
using UnityEditor.Build.Reporting;
|
|
using UnityEditor.PackageManager;
|
|
using UnityEditor.SceneManagement;
|
|
using UnityEditorInternal;
|
|
using UnityEngine;
|
|
using PackageInfo = UnityEditor.PackageManager.PackageInfo;
|
|
|
|
public class BenchmarkWindow : EditorWindow
|
|
{
|
|
private static readonly string benchmarkLoaderPath = "Assets/scenes/benchmark/loader.unity";
|
|
private static readonly string benchmarkPrefabPath = "Assets/objects/misc/[benchmark].prefab";
|
|
|
|
[MenuItem("Tools/Benchmark")]
|
|
static void Init()
|
|
{
|
|
var window = (BenchmarkWindow)GetWindow(typeof(BenchmarkWindow));
|
|
window.Show();
|
|
}
|
|
|
|
class Styles
|
|
{
|
|
public static readonly GUIContent[] toolbarOptions = {new GUIContent("Tools"), new GUIContent("Results"), };
|
|
public static GUIStyle richDefault;
|
|
}
|
|
|
|
private static string assetGuidKey = "boatattack.benchmark.assetguid";
|
|
private static string assetGUID = "";
|
|
private static BenchmarkConfigData _benchConfigData;
|
|
|
|
private int currentRunIndex = 0;
|
|
|
|
public int currentToolbar;
|
|
private const int ToolbarWidth = 150;
|
|
private List<PerfResults> PerfResults = new List<PerfResults>();
|
|
private string[] resultFiles;
|
|
private List<string[]> resultItems = new List<string[]>();
|
|
private int currentFile;
|
|
private int currentResult;
|
|
private int currentRun = 0;
|
|
private BuildTarget target;
|
|
|
|
// TempUI vars
|
|
private bool resultInfoHeader;
|
|
private bool resultDataHeader;
|
|
private bool toolsBuildHeader;
|
|
private bool toolsSettingsHeader;
|
|
|
|
private void OnEnable()
|
|
{
|
|
target = EditorUserBuildSettings.activeBuildTarget;
|
|
assetGUID = EditorPrefs.GetString(assetGuidKey);
|
|
if (assetGUID == "")
|
|
assetGUID = AssetDatabase.GUIDFromAssetPath("Assets/resources/benchmarksettings.asset").ToString();
|
|
|
|
_benchConfigData = AssetDatabase.LoadAssetAtPath<BenchmarkConfigData>(AssetDatabase.GUIDToAssetPath(assetGUID));
|
|
}
|
|
|
|
private void OnGUI()
|
|
{
|
|
Styles.richDefault ??= new GUIStyle(EditorStyles.label) {richText = true, alignment = TextAnchor.MiddleCenter};
|
|
|
|
EditorGUILayout.Space(5);
|
|
|
|
var toolbarRect = EditorGUILayout.GetControlRect();
|
|
toolbarRect.position += new Vector2((toolbarRect.width - ToolbarWidth) * 0.5f, 0f);
|
|
toolbarRect.width = ToolbarWidth;
|
|
|
|
currentToolbar = GUI.Toolbar(toolbarRect, currentToolbar,
|
|
Styles.toolbarOptions);
|
|
|
|
switch (currentToolbar)
|
|
{
|
|
case 0:
|
|
DrawTools();
|
|
break;
|
|
case 1:
|
|
DrawResults();
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void DrawTools()
|
|
{
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
toolsBuildHeader = EditorGUILayout.BeginFoldoutHeaderGroup(toolsBuildHeader, "Build");
|
|
if (toolsBuildHeader)
|
|
{
|
|
DrawBuildSettings();
|
|
}
|
|
EditorGUILayout.EndFoldoutHeaderGroup();
|
|
EditorGUILayout.EndVertical();
|
|
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
toolsSettingsHeader = EditorGUILayout.BeginFoldoutHeaderGroup(toolsSettingsHeader, "Benchmark Settings");
|
|
if (toolsSettingsHeader)
|
|
{
|
|
DrawBenchmarkSettings();
|
|
}
|
|
EditorGUILayout.EndFoldoutHeaderGroup();
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
|
|
private void DrawBuildSettings()
|
|
{
|
|
if (_benchConfigData != null)
|
|
{
|
|
target = (BuildTarget) EditorGUILayout.EnumPopup(target);
|
|
var maskOptions = _benchConfigData.benchmarkData.Select(data => data.benchmarkName).ToArray();
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
|
|
EditorGUILayout.LabelField("Benchmark Suite");
|
|
//var mask = -1;
|
|
//mask = EditorGUILayout.MaskField("Benchmark Suite", mask, maskOptions);
|
|
if (GUILayout.Button($"Build & Run"))
|
|
{
|
|
SetupBenchmarkPrefab();
|
|
BuildBenchmark();
|
|
}
|
|
|
|
if (GUILayout.Button($"Run in Editor"))
|
|
{
|
|
SetupBenchmarkPrefab();
|
|
RunBenchmark();
|
|
}
|
|
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
|
|
currentRunIndex = EditorGUILayout.Popup("Benchmark Scene", currentRunIndex, maskOptions);
|
|
if (GUILayout.Button($"Build & Run"))
|
|
{
|
|
SetupBenchmarkPrefab(currentRunIndex);
|
|
BuildStaticBenchmark(currentRunIndex);
|
|
}
|
|
|
|
if (GUILayout.Button($"Run in Editor"))
|
|
{
|
|
//setup the things
|
|
SetupBenchmarkPrefab(currentRunIndex);
|
|
RunBenchmark();
|
|
}
|
|
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
else
|
|
{
|
|
EditorGUILayout.HelpBox("No Benchmark configuration file present in settings section.", MessageType.Warning, true);
|
|
}
|
|
}
|
|
|
|
private static void DrawBenchmarkSettings()
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
_benchConfigData = (BenchmarkConfigData) EditorGUILayout.ObjectField(new GUIContent("Benchmark Data File"), _benchConfigData,
|
|
typeof(BenchmarkConfigData), false);
|
|
if(EditorGUI.EndChangeCheck())
|
|
{
|
|
AssetDatabase.TryGetGUIDAndLocalFileIdentifier(_benchConfigData, out assetGUID, out long _);
|
|
EditorPrefs.SetString(assetGuidKey, assetGUID);
|
|
}
|
|
|
|
if (_benchConfigData)
|
|
{
|
|
var editor = Editor.CreateEditor(_benchConfigData);
|
|
editor.DrawDefaultInspector();
|
|
}
|
|
}
|
|
|
|
private static void SetupBenchmarkPrefab(int index = -1)
|
|
{
|
|
// setup benchmark prefab
|
|
using var benchmarkPrefab = new PrefabUtility.EditPrefabContentsScope(benchmarkPrefabPath);
|
|
if (!benchmarkPrefab.prefabContentsRoot.TryGetComponent(out Benchmark b)) return;
|
|
b.simpleRun = index != -1;
|
|
b.simpleRunScene = index;
|
|
b.urpVersion = GetURPPackageVersion();
|
|
}
|
|
|
|
private void RunBenchmark()
|
|
{
|
|
EditorSceneManager.playModeStartScene = GetBenchmarkLoader();
|
|
EditorApplication.EnterPlaymode();
|
|
}
|
|
|
|
private void BuildBenchmark()
|
|
{
|
|
var buildOptions = new BuildPlayerOptions();
|
|
|
|
var sceneList = new List<string> {benchmarkLoaderPath};
|
|
sceneList.AddRange(_benchConfigData.benchmarkData.Select(benchSettings => benchSettings.scene));
|
|
buildOptions.scenes = sceneList.ToArray();
|
|
|
|
Build(ref buildOptions);
|
|
}
|
|
|
|
private void BuildStaticBenchmark(int index)
|
|
{
|
|
var buildOptions = new BuildPlayerOptions();
|
|
var sceneList = new List<string>
|
|
{
|
|
benchmarkLoaderPath,
|
|
_benchConfigData.benchmarkData[index].scene
|
|
};
|
|
buildOptions.scenes = sceneList.ToArray();
|
|
Build(ref buildOptions);
|
|
}
|
|
|
|
private void Build(ref BuildPlayerOptions options)
|
|
{
|
|
string ext;
|
|
switch (target)
|
|
{
|
|
case BuildTarget.Android:
|
|
ext = ".apk";
|
|
break;
|
|
default:
|
|
ext = "";
|
|
break;
|
|
}
|
|
|
|
options.locationPathName = $"Builds/Benchmark/{target:G}/BoatattackBenchmark{ext}";
|
|
options.target = target;
|
|
options.targetGroup = BuildPipeline.GetBuildTargetGroup(target);
|
|
options.options |= (BuildOptions.Development | BuildOptions.AutoRunPlayer);
|
|
|
|
var curTar = EditorUserBuildSettings.activeBuildTarget;
|
|
if (target != curTar)
|
|
{
|
|
EditorUserBuildSettings.SwitchActiveBuildTarget(options.targetGroup, options.target);
|
|
}
|
|
AutoBuildAddressables.Popup();
|
|
var report = BuildPipeline.BuildPlayer(options);
|
|
var summary = report.summary;
|
|
|
|
EditorUserBuildSettings.SwitchActiveBuildTarget(BuildPipeline.GetBuildTargetGroup(curTar), curTar);
|
|
|
|
switch (summary.result)
|
|
{
|
|
case BuildResult.Succeeded:
|
|
Debug.Log("Benchmark Build Complete");
|
|
break;
|
|
case BuildResult.Failed:
|
|
Debug.LogError("Benchmark Build Failed");
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void DrawResults()
|
|
{
|
|
if (PerfResults == null || PerfResults.Count == 0 || resultFiles?.Length == 0 || resultItems?.Count == 0)
|
|
{
|
|
UpdateFiles();
|
|
}
|
|
|
|
if (PerfResults != null && PerfResults.Count > 0)
|
|
{
|
|
EditorGUILayout.BeginHorizontal();
|
|
currentFile = EditorGUILayout.Popup(new GUIContent("File"), currentFile, resultFiles);
|
|
currentResult = EditorGUILayout.Popup(new GUIContent("Results"), currentResult, resultItems[currentFile]);
|
|
if (GUILayout.Button("reload", GUILayout.Width(100)))
|
|
{
|
|
UpdateFiles();
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
EditorGUILayout.Space(4);
|
|
|
|
DrawPerfInfo(PerfResults[currentFile].perfStats[currentResult].info);
|
|
|
|
DrawPerf(PerfResults[currentFile].perfStats[currentResult]);
|
|
}
|
|
else
|
|
{
|
|
GUILayout.Label("No Stats found, please run a benchmark.");
|
|
}
|
|
}
|
|
|
|
private void DrawPerfInfo(TestInfo info)
|
|
{
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
|
|
resultInfoHeader = EditorGUILayout.BeginFoldoutHeaderGroup(resultInfoHeader, "Info");
|
|
if (resultInfoHeader)
|
|
{
|
|
var fields = info.GetType().GetFields();
|
|
var half = fields.Length / 2;
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUILayout.BeginVertical();
|
|
for (var index = 0; index < fields.Length; index++)
|
|
{
|
|
var prop = fields[index];
|
|
EditorGUILayout.LabelField(prop.Name, prop.GetValue(info).ToString(), EditorStyles.boldLabel);
|
|
if (index == half)
|
|
{
|
|
EditorGUILayout.EndVertical();
|
|
EditorGUILayout.BeginVertical();
|
|
}
|
|
}
|
|
EditorGUILayout.EndVertical();
|
|
EditorGUILayout.EndHorizontal();
|
|
}
|
|
EditorGUILayout.EndFoldoutHeaderGroup();
|
|
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
|
|
private void DrawPerf(PerfBasic perfData)
|
|
{
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
|
|
var options = new string[perfData.RunData.Length + 1];
|
|
options[0] = "Smooth all runs";
|
|
for (int i = 1; i < perfData.RunData.Length + 1; i++)
|
|
{
|
|
options[i] = $"Run {i.ToString()}";
|
|
}
|
|
|
|
resultDataHeader = EditorGUILayout.BeginFoldoutHeaderGroup(resultDataHeader, "Data");
|
|
if (resultDataHeader)
|
|
{
|
|
EditorGUILayout.Space(4);
|
|
|
|
RunData runData;
|
|
var runtime = 0.0f;
|
|
var avg = 0.0f;
|
|
var min = new FrameData(-1, 0f);
|
|
var max = new FrameData(-1, 0f);
|
|
if (currentRun > 0)
|
|
{
|
|
runData = perfData.RunData[currentRun - 1];
|
|
runtime = runData.RunTime;
|
|
avg = runData.AvgMs;
|
|
min = runData.MinFrame;
|
|
max = runData.MaxFrame;
|
|
}
|
|
else
|
|
{
|
|
avg += perfData.RunData.Sum(data => data.AvgMs / perfData.RunData.Length);
|
|
runtime += perfData.RunData.Sum(data => data.RunTime / perfData.RunData.Length);
|
|
}
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
var index = EditorGUILayout.Popup("Display", currentRun, options);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
currentRun = Mathf.Clamp(index, 0, options.Length + 1);
|
|
}
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
var graphRect = EditorGUILayout.GetControlRect(false, 500f);
|
|
DrawGraph(graphRect, perfData, 0, avg * 2f);
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
{
|
|
EditorGUILayout.LabelField($"<b>Runtime:</b> {runtime:F2}s", Styles.richDefault,
|
|
GUILayout.MaxWidth(120.0f));
|
|
EditorGUILayout.LabelField($"<b>Average:</b> {avg:F2}ms", Styles.richDefault,
|
|
GUILayout.MaxWidth(120.0f));
|
|
EditorGUILayout.LabelField($"<b>Minimum(fastest):</b> {min.ms:F2}ms (@frame: {min.frameIndex})",
|
|
Styles.richDefault);
|
|
EditorGUILayout.LabelField($"<b>Maximum(slowest):</b> {max.ms:F2}ms (@frame: {max.frameIndex})",
|
|
Styles.richDefault);
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
EditorGUILayout.Space(4f);
|
|
}
|
|
EditorGUILayout.EndFoldoutHeaderGroup();
|
|
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
|
|
#region GraphDrawing
|
|
|
|
private void DrawGraph(Rect rect, PerfBasic data, float minMS, float maxMS)
|
|
{
|
|
var values = data.RunData;
|
|
var padding = 20f;
|
|
rect.max -= Vector2.one * padding;
|
|
rect.xMax -= 40f;
|
|
rect.min += Vector2.one * padding;
|
|
|
|
|
|
//draw value markers
|
|
GUI.backgroundColor = new Color(0f, 0f, 0f, 1f);
|
|
GUI.Box(rect, "");
|
|
//GUI.DrawTexture(rect, Texture2D.grayTexture, ScaleMode.StretchToFill);
|
|
|
|
DrawGraphMarkers(rect, minMS, maxMS, 5);
|
|
|
|
var c = new Color(0.129f, 0.588f, 0.952f, 1.0f);
|
|
if (currentRun == 0)
|
|
{
|
|
var averageValue = new float[values[0].rawSamples.Length];
|
|
foreach (var frames in values)
|
|
{
|
|
for (int i = 0; i < averageValue.Length; i++)
|
|
{
|
|
averageValue[i] += frames.rawSamples[i] / values.Length;
|
|
}
|
|
}
|
|
DrawGraphLine(rect, averageValue, minMS, maxMS, c);
|
|
}
|
|
else
|
|
{
|
|
DrawGraphLine(rect, values[currentRun-1].rawSamples, minMS, maxMS, c);
|
|
DrawMinMaxMarkers(rect, values[currentRun-1], data.Frames);
|
|
}
|
|
}
|
|
|
|
void DrawGraphLine(Rect rect, float[] points, float min, float max, Color color)
|
|
{
|
|
var graphPoints = new Vector3[points.Length];
|
|
for (var j = 0; j < points.Length; j++)
|
|
{
|
|
var valA = rect.yMax - rect.height * GetGraphLerpValue(points[j], min, max);
|
|
|
|
var xLerp = new Vector2(j, j + 1) / (points.Length - 1);
|
|
var xA = Mathf.Lerp(rect.xMin, rect.xMax, xLerp.x);
|
|
var posA = new Vector2(xA, valA);
|
|
graphPoints[j] = posA;
|
|
}
|
|
Handles.color = color;
|
|
Handles.DrawAAPolyLine(graphPoints);
|
|
}
|
|
|
|
private static void DrawMinMaxMarkers(Rect rect, RunData data, int frames)
|
|
{
|
|
var c = Color.red;
|
|
c.a = 0.5f;
|
|
Handles.color = c;
|
|
var maxX = Mathf.Lerp(rect.xMin, rect.xMax, (float)data.MaxFrame.frameIndex / frames);
|
|
Handles.DrawLine(new Vector2(maxX, rect.yMin), new Vector2(maxX, rect.yMax));
|
|
|
|
c = Color.green;
|
|
c.a = 0.5f;
|
|
Handles.color = c;
|
|
var minX = Mathf.Lerp(rect.xMin, rect.xMax, (float)data.MinFrame.frameIndex / frames);
|
|
Handles.DrawLine(new Vector2(minX, rect.yMin), new Vector2(minX, rect.yMax));
|
|
}
|
|
|
|
private static void DrawGraphMarkers(Rect rect, float min, float max, int count)
|
|
{
|
|
count--;
|
|
for (int i = 0; i <= count; i++)
|
|
{
|
|
var y = Mathf.Lerp(rect.yMax, rect.yMin, (float)i / count);
|
|
Handles.color = new Color(1f, 1f, 1f, 0.5f);
|
|
Handles.DrawDottedLine(new Vector2(rect.xMin, y), new Vector2(rect.xMax, y), 4);
|
|
y -= EditorGUIUtility.singleLineHeight * 0.5f;
|
|
var val = Mathf.Lerp(min, max, (float) i / count);
|
|
GUI.Label(new Rect(new Vector2(rect.xMax, y), new Vector2(80, EditorGUIUtility.singleLineHeight)), $"{val:F1}ms");
|
|
}
|
|
}
|
|
|
|
private float GetGraphLerpValue(float ms)
|
|
{
|
|
return GetGraphLerpValue(ms, 0f, 33.33f);
|
|
}
|
|
|
|
private static float GetGraphLerpValue(float ms, float msMin, float msMax)
|
|
{
|
|
var msA = ms;
|
|
return Mathf.InverseLerp(msMin, msMax, msA);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Utilitiess
|
|
|
|
private void UpdateFiles()
|
|
{
|
|
PerfResults = Benchmark.LoadAllBenchmarkStats();
|
|
resultFiles = PerfResults.Select(result => result.fileName).ToArray();
|
|
foreach (var file in PerfResults)
|
|
{
|
|
resultItems.Add(file.perfStats.Select(perf => perf.info.BenchmarkName).ToArray());
|
|
}
|
|
}
|
|
|
|
private static SceneAsset GetBenchmarkLoader()
|
|
{
|
|
return (SceneAsset)AssetDatabase.LoadAssetAtPath(benchmarkLoaderPath, typeof(SceneAsset));
|
|
}
|
|
|
|
public static string GetURPPackageVersion()
|
|
{
|
|
List<PackageInfo> packageJsons = AssetDatabase.FindAssets("package")
|
|
.Select(AssetDatabase.GUIDToAssetPath).Where(x => AssetDatabase.LoadAssetAtPath<TextAsset>(x) != null)
|
|
.Select(PackageInfo.FindForAssetPath).ToList();
|
|
|
|
var URPInfo = packageJsons.Find(x => x.name == "com.unity.render-pipelines.universal");
|
|
|
|
return URPInfo.version;
|
|
}
|
|
|
|
#endregion
|
|
}
|