# Tracy 集成到 Unity 的完整指南 ## 概述 Tracy 是一个强大的 C++ 性能分析工具,要在 Unity 中使用它,需要通过 **Native Plugin** 的方式集成。本指南将介绍两种主要的集成方法。 --- ## 方法一:通过 Native Plugin 集成(推荐) 这是最常用和最灵活的方法,适用于所有Unity项目。 ### 1. 创建 Tracy Native Plugin #### 步骤 1:创建 C++ 动态库项目 创建一个新的 C++ DLL/Shared Library 项目: **目录结构:** ``` UnityTracyPlugin/ ├── src/ │ ├── TracyUnityPlugin.cpp │ └── TracyUnityPlugin.h ├── include/ │ └── tracy/ # Tracy 源代码 ├── CMakeLists.txt └── build/ ``` #### 步骤 2:编写 Plugin 代码 **TracyUnityPlugin.h:** ```cpp #ifndef TRACY_UNITY_PLUGIN_H #define TRACY_UNITY_PLUGIN_H // Unity Native Plugin 需要使用 C 导出 #ifdef __cplusplus extern "C" { #endif // 平台定义 #if defined(_WIN32) || defined(_WIN64) #define UNITY_PLUGIN_EXPORT __declspec(dllexport) #elif defined(__APPLE__) || defined(__linux__) #define UNITY_PLUGIN_EXPORT __attribute__((visibility("default"))) #else #define UNITY_PLUGIN_EXPORT #endif // Tracy API UNITY_PLUGIN_EXPORT void TracyInit(); UNITY_PLUGIN_EXPORT void TracyShutdown(); UNITY_PLUGIN_EXPORT void TracyFrameMark(); UNITY_PLUGIN_EXPORT void TracyBeginZone(const char* name, const char* function, const char* file, int line); UNITY_PLUGIN_EXPORT void TracyEndZone(); UNITY_PLUGIN_EXPORT void TracyPlotValue(const char* name, double value); UNITY_PLUGIN_EXPORT void TracyMessage(const char* message); UNITY_PLUGIN_EXPORT void TracySetThreadName(const char* name); #ifdef __cplusplus } #endif #endif // TRACY_UNITY_PLUGIN_H ``` **TracyUnityPlugin.cpp:** ```cpp #include "TracyUnityPlugin.h" #include "tracy/Tracy.hpp" #include #include #include // 用于管理 Zone 的结构 struct ZoneContext { tracy::ScopedZone* zone; }; static std::unordered_map g_zones; static std::mutex g_zonesMutex; static int g_nextZoneId = 1; extern "C" { UNITY_PLUGIN_EXPORT void TracyInit() { // Tracy 会自动初始化,这里可以做一些额外的设置 } UNITY_PLUGIN_EXPORT void TracyShutdown() { std::lock_guard lock(g_zonesMutex); g_zones.clear(); } UNITY_PLUGIN_EXPORT void TracyFrameMark() { FrameMark; } UNITY_PLUGIN_EXPORT int TracyBeginZone(const char* name, const char* function, const char* file, int line) { std::lock_guard lock(g_zonesMutex); int zoneId = g_nextZoneId++; // 创建 Zone(注意:这是简化版本,实际使用中需要更复杂的处理) tracy::SourceLocationData loc; loc.name = name; loc.function = function; loc.file = file; loc.line = (uint32_t)line; // 实际使用中,你可能需要使用 Tracy 的宏来正确创建 Zone // 这里仅作示例 return zoneId; } UNITY_PLUGIN_EXPORT void TracyEndZone(int zoneId) { std::lock_guard lock(g_zonesMutex); auto it = g_zones.find(zoneId); if (it != g_zones.end()) { delete it->second.zone; g_zones.erase(it); } } UNITY_PLUGIN_EXPORT void TracyPlotValue(const char* name, double value) { TracyPlot(name, value); } UNITY_PLUGIN_EXPORT void TracyMessage(const char* message) { TracyMessage(message, strlen(message)); } UNITY_PLUGIN_EXPORT void TracySetThreadName(const char* name) { tracy::SetThreadName(name); } } // extern "C" ``` #### 步骤 3:CMakeLists.txt 配置 ```cmake cmake_minimum_required(VERSION 3.10) project(UnityTracyPlugin) set(CMAKE_CXX_STANDARD 17) # Tracy 配置 option(TRACY_ENABLE "Enable Tracy profiling" ON) option(TRACY_ON_DEMAND "Tracy on-demand profiling" ON) add_definitions(-DTRACY_ENABLE) add_definitions(-DTRACY_ON_DEMAND) # Tracy 源文件 set(TRACY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include/tracy") set(TRACY_SOURCES ${TRACY_DIR}/public/TracyClient.cpp ) # Plugin 源文件 set(PLUGIN_SOURCES src/TracyUnityPlugin.cpp ${TRACY_SOURCES} ) # 包含目录 include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/src ${TRACY_DIR}/public ) # 创建动态库 add_library(UnityTracyPlugin SHARED ${PLUGIN_SOURCES}) # Windows 平台设置 if(WIN32) target_link_libraries(UnityTracyPlugin ws2_32 dbghelp) endif() # macOS/iOS 平台设置 if(APPLE) set_target_properties(UnityTracyPlugin PROPERTIES FRAMEWORK FALSE MACOSX_RPATH TRUE ) endif() # Linux/Android 平台设置 if(UNIX AND NOT APPLE) target_link_libraries(UnityTracyPlugin pthread dl) endif() ``` #### 步骤 4:编译 Plugin ```bash # Windows mkdir build cd build cmake .. -G "Visual Studio 17 2022" -A x64 cmake --build . --config Release # macOS mkdir build cd build cmake .. -DCMAKE_BUILD_TYPE=Release make # Linux mkdir build cd build cmake .. -DCMAKE_BUILD_TYPE=Release make ``` ### 2. Unity 端集成 #### 步骤 1:将编译好的 DLL 放入 Unity 项目 ``` UnityProject/ └── Assets/ └── Plugins/ ├── x86_64/ │ └── UnityTracyPlugin.dll # Windows 64位 ├── x86/ │ └── UnityTracyPlugin.dll # Windows 32位 ├── Android/ │ ├── arm64-v8a/ │ │ └── libUnityTracyPlugin.so # Android ARM64 │ └── armeabi-v7a/ │ └── libUnityTracyPlugin.so # Android ARM32 ├── iOS/ │ └── libUnityTracyPlugin.a # iOS 静态库 └── macOS/ └── libUnityTracyPlugin.dylib # macOS ``` #### 步骤 2:创建 C# Wrapper **Tracy.cs:** ```csharp using System; using System.Runtime.InteropServices; using UnityEngine; namespace TracyProfiler { /// /// Tracy 性能分析器的 Unity 封装 /// public static class Tracy { private const string DLL_NAME = "UnityTracyPlugin"; #region Native Methods [DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)] private static extern void TracyInit(); [DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)] private static extern void TracyShutdown(); [DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)] private static extern void TracyFrameMark(); [DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)] private static extern int TracyBeginZone(string name, string function, string file, int line); [DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)] private static extern void TracyEndZone(int zoneId); [DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)] private static extern void TracyPlotValue(string name, double value); [DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)] private static extern void TracyMessage(string message); [DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)] private static extern void TracySetThreadName(string name); #endregion private static bool s_initialized = false; /// /// 初始化 Tracy /// public static void Initialize() { if (s_initialized) return; try { TracyInit(); s_initialized = true; Debug.Log("Tracy Profiler 已初始化"); } catch (Exception e) { Debug.LogError($"Tracy 初始化失败: {e.Message}"); } } /// /// 关闭 Tracy /// public static void Shutdown() { if (!s_initialized) return; try { TracyShutdown(); s_initialized = false; } catch (Exception e) { Debug.LogError($"Tracy 关闭失败: {e.Message}"); } } /// /// 标记帧边界(通常在每帧末尾调用) /// public static void MarkFrame() { if (!s_initialized) return; TracyFrameMark(); } /// /// 绘制数值(用于实时监控变量) /// public static void Plot(string name, double value) { if (!s_initialized) return; TracyPlotValue(name, value); } /// /// 发送消息到 Tracy /// public static void Message(string message) { if (!s_initialized) return; TracyMessage(message); } /// /// 设置当前线程名称 /// public static void SetThreadName(string name) { if (!s_initialized) return; TracySetThreadName(name); } /// /// Tracy Zone 的作用域包装器(使用 using 语句自动管理生命周期) /// public struct ZoneScope : IDisposable { private int zoneId; private bool isValid; public ZoneScope(string name, [System.Runtime.CompilerServices.CallerMemberName] string function = "", [System.Runtime.CompilerServices.CallerFilePath] string file = "", [System.Runtime.CompilerServices.CallerLineNumber] int line = 0) { if (s_initialized) { zoneId = TracyBeginZone(name, function, file, line); isValid = true; } else { zoneId = -1; isValid = false; } } public void Dispose() { if (isValid && s_initialized) { TracyEndZone(zoneId); } } } /// /// 创建一个 Tracy Zone(性能追踪区域) /// 使用 using 语句确保自动结束 /// public static ZoneScope BeginZone(string name) { return new ZoneScope(name); } } } ``` #### 步骤 3:创建 Tracy Manager **TracyManager.cs:** ```csharp using UnityEngine; namespace TracyProfiler { /// /// Tracy 管理器 - 负责初始化和每帧更新 /// public class TracyManager : MonoBehaviour { [Header("Tracy Settings")] [SerializeField] private bool enableOnStart = true; [SerializeField] private bool markFrames = true; private void Awake() { // 确保只有一个实例 if (FindObjectsOfType().Length > 1) { Destroy(gameObject); return; } DontDestroyOnLoad(gameObject); if (enableOnStart) { Tracy.Initialize(); } } private void LateUpdate() { // 在每帧末尾标记帧边界 if (markFrames) { Tracy.MarkFrame(); } // 示例:绘制一些有用的性能数据 Tracy.Plot("FPS", 1.0f / Time.deltaTime); Tracy.Plot("Frame Time (ms)", Time.deltaTime * 1000.0f); Tracy.Plot("Total Allocated Memory (MB)", GC.GetTotalMemory(false) / (1024.0 * 1024.0)); } private void OnDestroy() { Tracy.Shutdown(); } private void OnApplicationQuit() { Tracy.Shutdown(); } } } ``` #### 步骤 4:使用示例 **GameManager.cs:** ```csharp using UnityEngine; using TracyProfiler; public class GameManager : MonoBehaviour { private void Update() { // 追踪整个 Update 方法 using (Tracy.BeginZone("GameManager.Update")) { ProcessInput(); UpdateGameLogic(); UpdateAI(); } } private void ProcessInput() { using (Tracy.BeginZone("ProcessInput")) { // 输入处理代码 if (Input.GetKeyDown(KeyCode.Space)) { Tracy.Message("玩家按下空格键"); } } } private void UpdateGameLogic() { using (Tracy.BeginZone("UpdateGameLogic")) { // 游戏逻辑代码 for (int i = 0; i < 1000; i++) { // 一些计算 } } } private void UpdateAI() { using (Tracy.BeginZone("UpdateAI")) { // AI 更新代码 Tracy.Plot("Enemy Count", GameObject.FindGameObjectsWithTag("Enemy").Length); } } } ``` **PhysicsController.cs:** ```csharp using UnityEngine; using TracyProfiler; public class PhysicsController : MonoBehaviour { private void FixedUpdate() { using (Tracy.BeginZone("PhysicsController.FixedUpdate")) { PerformPhysicsCalculations(); } } private void PerformPhysicsCalculations() { using (Tracy.BeginZone("Physics Calculations")) { // 物理计算 Rigidbody[] rigidbodies = FindObjectsOfType(); Tracy.Plot("Active Rigidbodies", rigidbodies.Length); foreach (var rb in rigidbodies) { // 处理刚体 } } } } ``` --- ## 方法二:IL2CPP 后端集成 当 Unity 使用 IL2CPP 作为脚本后端时,可以直接在生成的 C++ 代码中集成 Tracy。 ### 1. 配置 IL2CPP 在 Unity 项目中创建 `il2cpp_custom.cpp`: ```cpp // 放置在: Assets/Plugins/IL2CPP/il2cpp_custom.cpp #include "il2cpp-config.h" // 包含 Tracy #define TRACY_ENABLE #include "tracy/Tracy.hpp" // IL2CPP 钩子函数 extern "C" void UnityPluginLoad(void* unityInterfaces) { // Tracy 会自动初始化 } extern "C" void UnityPluginUnload() { // Tracy 清理 } ``` ### 2. 修改 IL2CPP 构建设置 在 Unity 的 Player Settings 中: 1. 切换到 IL2CPP 脚本后端 2. 添加额外的编译参数:`-DTRACY_ENABLE -DTRACY_ON_DEMAND` 3. 链接 Tracy 库 --- ## 方法三:Unity Profiler 集成(可选) 可以创建一个桥接层,将 Unity Profiler 的数据转发到 Tracy。 **UnityToTracyBridge.cs:** ```csharp using UnityEngine; using UnityEngine.Profiling; using TracyProfiler; public class UnityToTracyBridge : MonoBehaviour { private CustomSampler[] samplers; private void Start() { // 创建自定义采样器 samplers = new CustomSampler[] { CustomSampler.Create("Render"), CustomSampler.Create("Physics"), CustomSampler.Create("Scripts"), CustomSampler.Create("GarbageCollector"), }; } private void LateUpdate() { // 将 Unity Profiler 数据转发到 Tracy foreach (var sampler in samplers) { Tracy.Plot(sampler.name + " (ms)", sampler.GetRecorder().elapsedNanoseconds / 1000000.0); } // 内存统计 Tracy.Plot("Used Heap Size (MB)", Profiler.usedHeapSizeLong / (1024.0 * 1024.0)); Tracy.Plot("Total Allocated Memory (MB)", Profiler.GetTotalAllocatedMemoryLong() / (1024.0 * 1024.0)); Tracy.Plot("Total Reserved Memory (MB)", Profiler.GetTotalReservedMemoryLong() / (1024.0 * 1024.0)); } } ``` --- ## 平台特定配置 ### Windows - 使用 Visual Studio 编译 DLL - 确保链接 `ws2_32.lib` 和 `dbghelp.lib` ### macOS / iOS - 使用 Xcode 编译动态库或框架 - 注意代码签名要求 ### Android - 为不同的 ABI 编译:armeabi-v7a, arm64-v8a, x86, x86_64 - 在 `gradle.properties` 中配置 NDK 路径 ### Linux - 链接 `pthread` 和 `dl` - 确保 GLIBC 版本兼容 --- ## 使用 Tracy Profiler 查看数据 ### 1. 启动 Tracy Profiler ```bash # 从 Tracy 仓库构建或下载预编译版本 # 运行 Tracy.exe (Windows) 或 Tracy (macOS/Linux) ``` ### 2. 连接到 Unity 应用 - Tracy Profiler 会自动发现本地网络中的 Tracy 客户端 - 点击连接即可开始分析 ### 3. 查看性能数据 - **时间线视图**: 查看各个 Zone 的执行时间 - **统计视图**: 查看函数调用次数和平均耗时 - **内存视图**: 查看内存分配情况 - **图表视图**: 查看 Plot 数据的实时曲线 --- ## 最佳实践 ### 1. 性能开销 - Tracy 的开销很小,但仍建议在 Release 构建中禁用细粒度的 Zone - 使用条件编译:`#if TRACY_ENABLE` ### 2. Zone 命名 - 使用清晰、描述性的名称 - 包含类名和方法名,如 `"PlayerController.Move"` ### 3. 合理使用 Plot - 不要在每帧绘制过多数据点 - 专注于关键性能指标 ### 4. 多线程支持 - Unity 的 Job System 需要特殊处理 - 在每个 Job 中调用 `Tracy.SetThreadName()` ### 5. 移动平台 - 注意无线网络连接的延迟 - 可以使用 Tracy 的文件保存功能,之后再分析 --- ## 故障排除 ### 问题 1: DLL 加载失败 **解决方案:** - 检查 Plugin 导入设置中的平台配置 - 确保 DLL 与 Unity 架构匹配(x64/x86) - 检查依赖库是否缺失 ### 问题 2: Tracy Profiler 无法连接 **解决方案:** - 确保防火墙允许 TCP 端口 8086 - 检查 `TRACY_ON_DEMAND` 是否正确定义 - 确认 Tracy.Initialize() 已被调用 ### 问题 3: 性能数据不准确 **解决方案:** - 使用 Release 构建测试 - 禁用 Unity Editor 的 Deep Profiling - 确保 V-Sync 和帧率限制的设置符合预期 ### 问题 4: Android 构建失败 **解决方案:** - 检查 NDK 版本兼容性 - 在 `build.gradle` 中添加必要的链接器标志 - 确保所有 ABI 的库都已编译 --- ## 进阶功能 ### 1. 自定义内存分配追踪 ```cpp // 在 Plugin 中添加 UNITY_PLUGIN_EXPORT void TracyAllocNamed(void* ptr, size_t size, const char* name) { TracyAllocN(ptr, size, name); } UNITY_PLUGIN_EXPORT void TracyFreeNamed(void* ptr, const char* name) { TracyFreeN(ptr, name); } ``` ```csharp // 在 C# 中使用 public class TrackedMemoryPool { public void Allocate(int size) { IntPtr ptr = Marshal.AllocHGlobal(size); Tracy.AllocNamed(ptr, size, "Memory Pool"); } public void Free(IntPtr ptr) { Tracy.FreeNamed(ptr, "Memory Pool"); Marshal.FreeHGlobal(ptr); } } ``` ### 2. GPU 性能追踪 Tracy 支持 OpenGL、Vulkan、DirectX 的 GPU 追踪,但需要在渲染管线中集成。 ### 3. 锁竞争分析 ```cpp // 在 Plugin 中 #define LockableName(type, varname) TracyLockableN(type, varname, #varname) ``` --- ## 总结 将 Tracy 集成到 Unity 中可以提供比 Unity Profiler 更详细的性能分析数据,特别是对于: 1. **Native Code 性能** - 如果你使用了大量 Native Plugin 2. **多线程分析** - Tracy 的线程视图非常直观 3. **跨平台分析** - 统一的工具链 4. **帧级别分析** - 精确到微秒的时间线 根据你的项目需求,选择合适的集成方法。对于大多数项目,**方法一(Native Plugin)** 是最推荐的方式。 --- ## 参考资源 - [Tracy 官方仓库](https://github.com/wolfpld/tracy) - [Tracy 手册](https://github.com/wolfpld/tracy/releases/latest/download/tracy.pdf) - [Unity Native Plugin 文档](https://docs.unity3d.com/Manual/NativePlugins.html) - [IL2CPP 脚本后端](https://docs.unity3d.com/Manual/IL2CPP.html)