19 KiB
19 KiB
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:
#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:
#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"
步骤 3:CMakeLists.txt 配置
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
# 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:
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:
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:
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:
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:
// 放置在: 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 中:
- 切换到 IL2CPP 脚本后端
- 添加额外的编译参数:
-DTRACY_ENABLE -DTRACY_ON_DEMAND - 链接 Tracy 库
方法三:Unity Profiler 集成(可选)
可以创建一个桥接层,将 Unity Profiler 的数据转发到 Tracy。
UnityToTracyBridge.cs:
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
# 从 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. 自定义内存分配追踪
// 在 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);
}
// 在 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. 锁竞争分析
// 在 Plugin 中
#define LockableName(type, varname) TracyLockableN(type, varname, #varname)
总结
将 Tracy 集成到 Unity 中可以提供比 Unity Profiler 更详细的性能分析数据,特别是对于:
- Native Code 性能 - 如果你使用了大量 Native Plugin
- 多线程分析 - Tracy 的线程视图非常直观
- 跨平台分析 - 统一的工具链
- 帧级别分析 - 精确到微秒的时间线
根据你的项目需求,选择合适的集成方法。对于大多数项目,方法一(Native Plugin) 是最推荐的方式。