Files
tracy-for-unity/README.md
2025-11-27 10:15:03 +08:00

803 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 <string>
#include <unordered_map>
#include <mutex>
// 用于管理 Zone 的结构
struct ZoneContext {
tracy::ScopedZone* zone;
};
static std::unordered_map<int, ZoneContext> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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"
```
#### 步骤 3CMakeLists.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
{
/// <summary>
/// Tracy 性能分析器的 Unity 封装
/// </summary>
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;
/// <summary>
/// 初始化 Tracy
/// </summary>
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}");
}
}
/// <summary>
/// 关闭 Tracy
/// </summary>
public static void Shutdown()
{
if (!s_initialized) return;
try
{
TracyShutdown();
s_initialized = false;
}
catch (Exception e)
{
Debug.LogError($"Tracy 关闭失败: {e.Message}");
}
}
/// <summary>
/// 标记帧边界(通常在每帧末尾调用)
/// </summary>
public static void MarkFrame()
{
if (!s_initialized) return;
TracyFrameMark();
}
/// <summary>
/// 绘制数值(用于实时监控变量)
/// </summary>
public static void Plot(string name, double value)
{
if (!s_initialized) return;
TracyPlotValue(name, value);
}
/// <summary>
/// 发送消息到 Tracy
/// </summary>
public static void Message(string message)
{
if (!s_initialized) return;
TracyMessage(message);
}
/// <summary>
/// 设置当前线程名称
/// </summary>
public static void SetThreadName(string name)
{
if (!s_initialized) return;
TracySetThreadName(name);
}
/// <summary>
/// Tracy Zone 的作用域包装器(使用 using 语句自动管理生命周期)
/// </summary>
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);
}
}
}
/// <summary>
/// 创建一个 Tracy Zone性能追踪区域
/// 使用 using 语句确保自动结束
/// </summary>
public static ZoneScope BeginZone(string name)
{
return new ZoneScope(name);
}
}
}
```
#### 步骤 3创建 Tracy Manager
**TracyManager.cs:**
```csharp
using UnityEngine;
namespace TracyProfiler
{
/// <summary>
/// Tracy 管理器 - 负责初始化和每帧更新
/// </summary>
public class TracyManager : MonoBehaviour
{
[Header("Tracy Settings")]
[SerializeField] private bool enableOnStart = true;
[SerializeField] private bool markFrames = true;
private void Awake()
{
// 确保只有一个实例
if (FindObjectsOfType<TracyManager>().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<Rigidbody>();
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)