550 lines
19 KiB
C#
550 lines
19 KiB
C#
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
|
||
{
|
||
/// <summary>
|
||
/// 性能分析工具,用于监控游戏性能
|
||
/// </summary>
|
||
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<string, CustomProfiler> systemProfilers = new Dictionary<string, CustomProfiler>();
|
||
|
||
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<string> performanceWarnings = new List<string>();
|
||
|
||
// 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<Texture>();
|
||
var meshes = Resources.FindObjectsOfTypeAll<Mesh>();
|
||
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前FPS
|
||
/// </summary>
|
||
public float GetFPS()
|
||
{
|
||
return fps;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取内存使用量(MB)
|
||
/// </summary>
|
||
public float GetMemoryUsage()
|
||
{
|
||
return memoryUsage;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取绘制调用数量
|
||
/// </summary>
|
||
public int GetDrawCalls()
|
||
{
|
||
return drawCalls;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取批处理数量
|
||
/// </summary>
|
||
public int GetBatches()
|
||
{
|
||
return batches;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 记录性能数据到日志
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 开始系统性能采样
|
||
/// </summary>
|
||
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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 结束系统性能采样
|
||
/// </summary>
|
||
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();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重置所有系统性能分析器
|
||
/// </summary>
|
||
public void ResetAllProfilers()
|
||
{
|
||
foreach (var profiler in systemProfilers.Values)
|
||
{
|
||
profiler.Reset();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取系统性能数据
|
||
/// </summary>
|
||
public Dictionary<string, (float averageTime, float maxTime, int sampleCount)> GetSystemProfilingData()
|
||
{
|
||
var result = new Dictionary<string, (float, float, int)>();
|
||
foreach (var kvp in systemProfilers)
|
||
{
|
||
var profiler = kvp.Value;
|
||
result[kvp.Key] = (profiler.AverageTime, profiler.maxTime, profiler.sampleCount);
|
||
}
|
||
return result;
|
||
}
|
||
}
|
||
}
|