using System.Collections.Generic; using System.IO; using UnityEngine; using UnityEngine.Profiling; using UnityEngine.Rendering; using Unity.Profiling; using System.Diagnostics; using Debug = UnityEngine.Debug; #if UNITY_EDITOR using UnityEditor; #endif namespace AibisDream.Utility { /// /// 性能分析工具,用于监控游戏性能 /// public class PerformanceProfiler : MonoBehaviour { [Header("性能监控设置")] public bool enableProfiling = true; public bool showFPS = true; public bool showMemory = true; public bool showDrawCalls = true; public bool showBatches = true; public bool showTris = true; public bool showVerts = true; [Header("数据记录设置")] public bool enableCsvLogging = false; public float logInterval = 0.5f; // 秒 public string csvFileName = "performance_log"; [Header("性能警告阈值")] public float fpsWarningThreshold = 30f; public float memoryWarningThreshold = 100f; // MB public int drawCallWarningThreshold = 100; public int batchWarningThreshold = 50; [Header("显示设置")] public Color normalColor = Color.white; public Color warningColor = Color.yellow; public Color errorColor = Color.red; public int fontSize = 14; [Header("CPU/GPU 时间分析")] public bool showCPUTime = true; public bool showGPUTime = true; public float cpuTimeWarningThreshold = 16.67f; // 60fps对应的帧时间 public float gpuTimeWarningThreshold = 16.67f; [Header("GC监控")] public bool showGCStats = true; public int gcWarningThreshold = 5; // 每秒GC次数警告阈值 [Header("资源监控")] public bool showResourceStats = true; public long textureMemoryWarningThreshold = 1024; // MB public long meshMemoryWarningThreshold = 512; // MB [Header("系统性能分析")] public bool enableSystemProfiling = true; public float systemTimeWarningThreshold = 8f; // ms private float deltaTime = 0.0f; private float cpuFrameTime; private float gpuFrameTime; private long lastGCCount; private float gcTimeAccumulator; private int gcCountInLastSecond; private int loadedTextureCount; private int loadedMeshCount; private long totalTextureMemory; private long totalMeshMemory; private Dictionary systemProfilers = new Dictionary(); public class CustomProfiler { public string name; public float totalTime; public float maxTime; public int sampleCount; public Stopwatch stopwatch; public float AverageTime => sampleCount > 0 ? totalTime / sampleCount : 0; public void Reset() { totalTime = 0; maxTime = 0; sampleCount = 0; stopwatch = new Stopwatch(); } } private float fps = 0.0f; private float memoryUsage = 0.0f; private int drawCalls = 0; private int batches = 0; private int triangles = 0; private int vertices = 0; private GUIStyle style; private List performanceWarnings = new List(); // CSV 日志 private StreamWriter csvWriter; private float nextLogTime = 0f; private string logFilePath; private void Start() { if (!enableProfiling) return; // 创建GUI样式 style = new GUIStyle(); style.fontSize = fontSize; style.normal.textColor = normalColor; // 启动性能监控协程 StartCoroutine(PerformanceMonitor()); // 初始化CSV日志 if (enableCsvLogging) { InitCsvLogging(); } } private void Update() { if (!enableProfiling) return; // 计算FPS deltaTime += (Time.unscaledDeltaTime - deltaTime) * 0.1f; fps = 1.0f / deltaTime; // 获取渲染统计信息 #if UNITY_EDITOR drawCalls = UnityEditor.UnityStats.drawCalls; batches = UnityEditor.UnityStats.batches; triangles = UnityEditor.UnityStats.triangles; vertices = UnityEditor.UnityStats.vertices; #else drawCalls = 0; batches = 0; triangles = 0; vertices = 0; #endif // 获取内存使用情况 memoryUsage = Profiler.GetTotalAllocatedMemoryLong() / (1024f * 1024f); // 转换为MB // CPU和GPU时间 cpuFrameTime = Time.unscaledDeltaTime * 1000f; // 转换为毫秒 if (FrameTimingManager.IsFeatureEnabled()) { var frameData = new FrameTiming[1]; FrameTimingManager.GetLatestTimings(1, frameData); if (frameData.Length > 0) { gpuFrameTime = (float)frameData[0].gpuFrameTime; } } // GC监控 if (System.GC.CollectionCount(0) > lastGCCount) { gcCountInLastSecond++; lastGCCount = System.GC.CollectionCount(0); } // 资源监控 if (showResourceStats) { var textures = Resources.FindObjectsOfTypeAll(); var meshes = Resources.FindObjectsOfTypeAll(); loadedTextureCount = textures.Length; loadedMeshCount = meshes.Length; totalTextureMemory = 0; foreach (var tex in textures) { if (tex != null) totalTextureMemory += Profiler.GetRuntimeMemorySizeLong(tex); } totalMeshMemory = 0; foreach (var mesh in meshes) { if (mesh != null) totalMeshMemory += Profiler.GetRuntimeMemorySizeLong(mesh); } } // 按间隔写入CSV if (enableCsvLogging && csvWriter != null && Time.unscaledTime >= nextLogTime) { WriteCsvRow(); nextLogTime = Time.unscaledTime + Mathf.Max(0.05f, logInterval); gcCountInLastSecond = 0; // 重置GC计数器 } } private System.Collections.IEnumerator PerformanceMonitor() { while (enableProfiling) { performanceWarnings.Clear(); // 检查FPS if (fps < fpsWarningThreshold) { performanceWarnings.Add($"FPS过低: {fps:F1}"); } // 检查内存使用 if (memoryUsage > memoryWarningThreshold) { performanceWarnings.Add($"内存使用过高: {memoryUsage:F1}MB"); } // 检查绘制调用 if (drawCalls > drawCallWarningThreshold) { performanceWarnings.Add($"绘制调用过多: {drawCalls}"); } // 检查批处理 if (batches > batchWarningThreshold) { performanceWarnings.Add($"批处理过多: {batches}"); } yield return new WaitForSeconds(1f); } } private void OnGUI() { if (!enableProfiling) return; float yPos = 10f; float lineHeight = fontSize + 5f; // 显示FPS if (showFPS) { Color fpsColor = fps < fpsWarningThreshold ? errorColor : normalColor; style.normal.textColor = fpsColor; GUI.Label(new Rect(10, yPos, 200, lineHeight), $"FPS: {fps:F1}", style); yPos += lineHeight; } // 显示内存使用 if (showMemory) { Color memColor = memoryUsage > memoryWarningThreshold ? warningColor : normalColor; style.normal.textColor = memColor; GUI.Label(new Rect(10, yPos, 200, lineHeight), $"内存: {memoryUsage:F1}MB", style); yPos += lineHeight; } // 显示绘制调用 if (showDrawCalls) { Color drawColor = drawCalls > drawCallWarningThreshold ? warningColor : normalColor; style.normal.textColor = drawColor; GUI.Label(new Rect(10, yPos, 200, lineHeight), $"绘制调用: {drawCalls}", style); yPos += lineHeight; } // 显示批处理 if (showBatches) { Color batchColor = batches > batchWarningThreshold ? warningColor : normalColor; style.normal.textColor = batchColor; GUI.Label(new Rect(10, yPos, 200, lineHeight), $"批处理: {batches}", style); yPos += lineHeight; } // 显示三角形数量 if (showTris) { style.normal.textColor = normalColor; GUI.Label(new Rect(10, yPos, 200, lineHeight), $"三角形: {triangles:N0}", style); yPos += lineHeight; } // 显示顶点数量 if (showVerts) { style.normal.textColor = normalColor; GUI.Label(new Rect(10, yPos, 200, lineHeight), $"顶点: {vertices:N0}", style); yPos += lineHeight; } // 显示CPU时间 if (showCPUTime) { Color timeColor = cpuFrameTime > cpuTimeWarningThreshold ? warningColor : normalColor; style.normal.textColor = timeColor; GUI.Label(new Rect(10, yPos, 200, lineHeight), $"CPU时间: {cpuFrameTime:F2}ms", style); yPos += lineHeight; } // 显示GPU时间 if (showGPUTime && FrameTimingManager.IsFeatureEnabled()) { Color timeColor = gpuFrameTime > gpuTimeWarningThreshold ? warningColor : normalColor; style.normal.textColor = timeColor; GUI.Label(new Rect(10, yPos, 200, lineHeight), $"GPU时间: {gpuFrameTime:F2}ms", style); yPos += lineHeight; } // 显示GC信息 if (showGCStats) { Color gcColor = gcCountInLastSecond > gcWarningThreshold ? warningColor : normalColor; style.normal.textColor = gcColor; GUI.Label(new Rect(10, yPos, 200, lineHeight), $"GC次数/秒: {gcCountInLastSecond}", style); yPos += lineHeight; } // 显示资源信息 if (showResourceStats) { yPos += 10f; style.normal.textColor = normalColor; GUI.Label(new Rect(10, yPos, 200, lineHeight), "资源统计:", style); yPos += lineHeight; Color texMemColor = (totalTextureMemory / (1024f * 1024f)) > textureMemoryWarningThreshold ? warningColor : normalColor; style.normal.textColor = texMemColor; GUI.Label(new Rect(20, yPos, 300, lineHeight), $"贴图: {loadedTextureCount}个 ({totalTextureMemory / (1024f * 1024f):F1}MB)", style); yPos += lineHeight; Color meshMemColor = (totalMeshMemory / (1024f * 1024f)) > meshMemoryWarningThreshold ? warningColor : normalColor; style.normal.textColor = meshMemColor; GUI.Label(new Rect(20, yPos, 300, lineHeight), $"网格: {loadedMeshCount}个 ({totalMeshMemory / (1024f * 1024f):F1}MB)", style); yPos += lineHeight; } // 显示系统性能分析 if (enableSystemProfiling && systemProfilers.Count > 0) { yPos += 10f; style.normal.textColor = normalColor; GUI.Label(new Rect(10, yPos, 200, lineHeight), "系统性能:", style); yPos += lineHeight; foreach (var profiler in systemProfilers.Values) { if (profiler.sampleCount > 0) { Color timeColor = profiler.AverageTime > systemTimeWarningThreshold ? warningColor : normalColor; style.normal.textColor = timeColor; GUI.Label(new Rect(20, yPos, 400, lineHeight), $"{profiler.name}: {profiler.AverageTime:F2}ms (最大: {profiler.maxTime:F2}ms)", style); yPos += lineHeight; } } } // 显示性能警告 if (performanceWarnings.Count > 0) { yPos += 10f; style.normal.textColor = errorColor; GUI.Label(new Rect(10, yPos, 400, lineHeight), "性能警告:", style); yPos += lineHeight; foreach (string warning in performanceWarnings) { GUI.Label(new Rect(20, yPos, 400, lineHeight), $"• {warning}", style); yPos += lineHeight; } } } /// /// 获取当前FPS /// public float GetFPS() { return fps; } /// /// 获取内存使用量(MB) /// public float GetMemoryUsage() { return memoryUsage; } /// /// 获取绘制调用数量 /// public int GetDrawCalls() { return drawCalls; } /// /// 获取批处理数量 /// public int GetBatches() { return batches; } /// /// 记录性能数据到日志 /// public void LogPerformanceData() { Debug.Log($"[性能数据] FPS: {fps:F1}, 内存: {memoryUsage:F1}MB, 绘制调用: {drawCalls}, 批处理: {batches}"); } private void OnDisable() { CloseCsv(); } private void OnDestroy() { CloseCsv(); } private void InitCsvLogging() { try { var directory = Application.persistentDataPath; logFilePath = Path.Combine(directory, $"perf_{System.DateTime.Now:yyyyMMdd_HHmmss}.csv"); csvWriter = new StreamWriter(new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)); csvWriter.WriteLine("time,fps,memoryMB,drawCalls,batches,triangles,vertices,cpuTime,gpuTime,gcCount," + "textureCount,meshCount,textureMemoryMB,meshMemoryMB"); csvWriter.Flush(); nextLogTime = Time.unscaledTime + Mathf.Max(0.05f, logInterval); Debug.Log($"性能CSV日志: {logFilePath}"); } catch (System.Exception e) { Debug.LogWarning($"初始化CSV日志失败: {e.Message}"); enableCsvLogging = false; } } private void WriteCsvRow() { try { if (csvWriter == null) return; csvWriter.WriteLine($"{Time.unscaledTime:F3},{fps:F2},{memoryUsage:F1},{drawCalls},{batches},{triangles},{vertices}," + $"{cpuFrameTime:F2},{gpuFrameTime:F2},{gcCountInLastSecond}," + $"{loadedTextureCount},{loadedMeshCount},{totalTextureMemory/1024/1024:F1},{totalMeshMemory/1024/1024:F1}"); csvWriter.Flush(); } catch (System.Exception e) { Debug.LogWarning($"写入CSV失败: {e.Message}"); } } private void CloseCsv() { try { if (csvWriter != null) { csvWriter.Flush(); csvWriter.Close(); csvWriter.Dispose(); csvWriter = null; } } catch {} } public string GetLogFilePath() { return logFilePath; } /// /// 开始系统性能采样 /// public void BeginSample(string systemName) { if (!enableSystemProfiling) return; if (!systemProfilers.ContainsKey(systemName)) { systemProfilers[systemName] = new CustomProfiler { name = systemName, stopwatch = new Stopwatch() }; } systemProfilers[systemName].stopwatch.Start(); } /// /// 结束系统性能采样 /// public void EndSample(string systemName) { if (!enableSystemProfiling) return; if (systemProfilers.TryGetValue(systemName, out var profiler)) { profiler.stopwatch.Stop(); float time = profiler.stopwatch.ElapsedTicks / (float)System.TimeSpan.TicksPerMillisecond; profiler.totalTime += time; profiler.maxTime = Mathf.Max(profiler.maxTime, time); profiler.sampleCount++; profiler.stopwatch.Reset(); } } /// /// 重置所有系统性能分析器 /// public void ResetAllProfilers() { foreach (var profiler in systemProfilers.Values) { profiler.Reset(); } } /// /// 获取系统性能数据 /// public Dictionary GetSystemProfilingData() { var result = new Dictionary(); foreach (var kvp in systemProfilers) { var profiler = kvp.Value; result[kvp.Key] = (profiler.AverageTime, profiler.maxTime, profiler.sampleCount); } return result; } } }