完成可编译

This commit is contained in:
2025-11-27 10:15:03 +08:00
parent 43de86ee31
commit 7ee76113c9
11 changed files with 192 additions and 1276 deletions

802
README.md Normal file
View File

@@ -0,0 +1,802 @@
# 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)