SHOW:
|
|
- or go back to the newest paste.
| 1 | using UnityEditor; | |
| 2 | using UnityEngine; | |
| 3 | using UnityEngine.Profiling; | |
| 4 | using System.Collections.Generic; | |
| 5 | using System.Linq; | |
| 6 | using UnityEngine.Rendering; | |
| 7 | ||
| 8 | public class GpuTimingWindow : EditorWindow | |
| 9 | {
| |
| 10 | private const int SampleCount = 1; | |
| 11 | private FrameTiming[] frameTimings = new FrameTiming[SampleCount]; | |
| 12 | private double gpuFrameTime; | |
| 13 | private double cpuFrameTime; | |
| 14 | ||
| 15 | private const int HistorySize = 50; | |
| 16 | private Queue<float> cpuHistory = new Queue<float>(HistorySize); | |
| 17 | private Queue<float> gpuHistory = new Queue<float>(HistorySize); | |
| 18 | ||
| 19 | private Shader shaderToProfile; | |
| 20 | private CustomSampler shaderSampler; | |
| 21 | ||
| 22 | [MenuItem("Window/Analysis/GPU Timing Viewer")]
| |
| 23 | public static void ShowWindow() | |
| 24 | {
| |
| 25 | var window = GetWindow<GpuTimingWindow>("GPU Timing");
| |
| 26 | window.Show(); | |
| 27 | } | |
| 28 | ||
| 29 | private void OnEnable() | |
| 30 | {
| |
| 31 | EditorApplication.update += UpdateTimings; | |
| 32 | } | |
| 33 | ||
| 34 | private void OnDisable() | |
| 35 | {
| |
| 36 | EditorApplication.update -= UpdateTimings; | |
| 37 | } | |
| 38 | ||
| 39 | private void UpdateTimings() | |
| 40 | {
| |
| 41 | // Capture frame timings from the last frame | |
| 42 | FrameTimingManager.CaptureFrameTimings(); | |
| 43 | uint count = FrameTimingManager.GetLatestTimings((uint)frameTimings.Length, frameTimings); | |
| 44 | ||
| 45 | if (count > 0) | |
| 46 | {
| |
| 47 | var timing = frameTimings[0]; | |
| 48 | gpuFrameTime = timing.gpuFrameTime; | |
| 49 | cpuFrameTime = timing.cpuFrameTime; | |
| 50 | ||
| 51 | AddToHistory(cpuHistory, (float)cpuFrameTime); | |
| 52 | AddToHistory(gpuHistory, (float)gpuFrameTime); | |
| 53 | ||
| 54 | Repaint(); | |
| 55 | } | |
| 56 | } | |
| 57 | ||
| 58 | private void AddToHistory(Queue<float> queue, float value) | |
| 59 | {
| |
| 60 | if (queue.Count >= HistorySize) | |
| 61 | queue.Dequeue(); | |
| 62 | queue.Enqueue(value); | |
| 63 | } | |
| 64 | ||
| 65 | private float GetAverage(Queue<float> queue) | |
| 66 | {
| |
| 67 | if (queue.Count == 0) return 0f; | |
| 68 | return queue.Average(); | |
| 69 | } | |
| 70 | ||
| 71 | private void OnGUI() | |
| 72 | {
| |
| 73 | GUILayout.Label("GPU & CPU Frame Timing", EditorStyles.boldLabel);
| |
| 74 | ||
| 75 | EditorGUILayout.LabelField("CPU Frame Time (ms):", cpuFrameTime.ToString("F2"));
| |
| 76 | EditorGUILayout.LabelField("GPU Frame Time (ms):", gpuFrameTime.ToString("F2"));
| |
| 77 | ||
| 78 | float avgCpu = GetAverage(cpuHistory); | |
| 79 | float avgGpu = GetAverage(gpuHistory); | |
| 80 | ||
| 81 | EditorGUILayout.Space(); | |
| 82 | EditorGUILayout.LabelField("CPU Avg (200 frames, ms):", avgCpu.ToString("F2"));
| |
| 83 | EditorGUILayout.LabelField("GPU Avg (200 frames, ms):", avgGpu.ToString("F2"));
| |
| 84 | ||
| 85 | float targetFrameRate = 1000f / Mathf.Max(0.0001f, avgGpu); | |
| 86 | EditorGUILayout.LabelField("Approx. Avg GPU FPS:", targetFrameRate.ToString("F1"));
| |
| 87 | ||
| 88 | GUILayout.Space(10); | |
| 89 | Rect rect = GUILayoutUtility.GetRect(100, 30); | |
| 90 | DrawTimingBar(rect, (float)cpuFrameTime, (float)gpuFrameTime); | |
| 91 | ||
| 92 | GUILayout.Space(20); | |
| 93 | GUILayout.Label("Frame Timing History (last 200 frames)", EditorStyles.boldLabel);
| |
| 94 | Rect graphRect = GUILayoutUtility.GetRect(position.width - 20, 100); | |
| 95 | DrawGraph(graphRect, cpuHistory, gpuHistory); | |
| 96 | } | |
| 97 | ||
| 98 | private void DrawTimingBar(Rect rect, float cpuMs, float gpuMs) | |
| 99 | {
| |
| 100 | float maxMs = Mathf.Max(cpuMs, gpuMs, 16.67f); // baseline at ~60 FPS | |
| 101 | float cpuWidth = Mathf.Clamp01(cpuMs / maxMs) * rect.width; | |
| 102 | float gpuWidth = Mathf.Clamp01(gpuMs / maxMs) * rect.width; | |
| 103 | ||
| 104 | EditorGUI.DrawRect(new Rect(rect.x, rect.y, cpuWidth, rect.height / 2), new Color(0.3f, 0.6f, 1f)); | |
| 105 | EditorGUI.DrawRect(new Rect(rect.x, rect.y + rect.height / 2, gpuWidth, rect.height / 2), new Color(1f, 0.4f, 0.4f)); | |
| 106 | ||
| 107 | EditorGUI.DropShadowLabel(rect, $"CPU {cpuMs:F2} ms | GPU {gpuMs:F2} ms");
| |
| 108 | } | |
| 109 | ||
| 110 | private void DrawGraph(Rect rect, Queue<float> cpuData, Queue<float> gpuData) | |
| 111 | {
| |
| 112 | Handles.BeginGUI(); | |
| 113 | GUI.Box(rect, GUIContent.none); | |
| 114 | ||
| 115 | if (cpuData.Count > 1 && gpuData.Count > 1) | |
| 116 | {
| |
| 117 | float[] cpuArray = cpuData.ToArray(); | |
| 118 | float[] gpuArray = gpuData.ToArray(); | |
| 119 | int count = Mathf.Min(cpuArray.Length, gpuArray.Length); | |
| 120 | ||
| 121 | float maxVal = 0f; | |
| 122 | for (int i = 0; i < count; i++) | |
| 123 | maxVal = Mathf.Max(maxVal, Mathf.Max(cpuArray[i], gpuArray[i])); | |
| 124 | ||
| 125 | if (maxVal < 16.67f) | |
| 126 | maxVal = 16.67f; // ensure ~60 fps baseline is visible | |
| 127 | ||
| 128 | Vector3 prevCpu = Vector3.zero; | |
| 129 | Vector3 prevGpu = Vector3.zero; | |
| 130 | for (int i = 0; i < count; i++) | |
| 131 | {
| |
| 132 | float x = Mathf.Lerp(rect.x, rect.xMax, i / (float)(count - 1)); | |
| 133 | float cpuY = Mathf.Lerp(rect.yMax, rect.y, cpuArray[i] / maxVal); | |
| 134 | float gpuY = Mathf.Lerp(rect.yMax, rect.y, gpuArray[i] / maxVal); | |
| 135 | ||
| 136 | Vector3 cpuPoint = new Vector3(x, cpuY, 0); | |
| 137 | Vector3 gpuPoint = new Vector3(x, gpuY, 0); | |
| 138 | ||
| 139 | if (i > 0) | |
| 140 | {
| |
| 141 | Handles.color = new Color(0.3f, 0.6f, 1f); | |
| 142 | Handles.DrawLine(prevCpu, cpuPoint); | |
| 143 | ||
| 144 | Handles.color = new Color(1f, 0.4f, 0.4f); | |
| 145 | Handles.DrawLine(prevGpu, gpuPoint); | |
| 146 | } | |
| 147 | ||
| 148 | prevCpu = cpuPoint; | |
| 149 | prevGpu = gpuPoint; | |
| 150 | } | |
| 151 | } | |
| 152 | ||
| 153 | Handles.EndGUI(); | |
| 154 | } | |
| 155 | } | |
| 156 |