803 lines
19 KiB
Markdown
803 lines
19 KiB
Markdown
|
|
# 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"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 步骤 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
|
|||
|
|
{
|
|||
|
|
/// <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)
|
|||
|
|
|