docs: rebuild Debug API content
This commit is contained in:
107
docs/api/_guides/Debug/Logging-Architecture.md
Normal file
107
docs/api/_guides/Debug/Logging-Architecture.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Logging Architecture
|
||||
|
||||
## 这套日志系统解决什么问题
|
||||
|
||||
在游戏引擎里,日志不是单纯的输出文本,而是跨模块诊断链路。渲染后端、资源系统、编辑器工具和自动化测试都需要记录问题,但它们不应该各自决定“写到哪里、以什么格式写、是否需要同时写文件和控制台”。
|
||||
|
||||
`XCEngine::Debug::Logger` 采用中心分发 + sink 的结构,就是为了把这两类职责拆开:
|
||||
|
||||
- 业务层负责说明“发生了什么”。
|
||||
- `Logger` 负责说明“这条记录如何统一过滤和分发”。
|
||||
- sink 负责说明“最终落到哪里”。
|
||||
|
||||
## 为什么不是 everywhere `printf`
|
||||
|
||||
直接 `printf` 的问题是:
|
||||
|
||||
- 输出目的地写死在调用点里。
|
||||
- 无法统一按级别和分类过滤。
|
||||
- 很难同时输出到控制台、文件和编辑器 UI。
|
||||
- 工具层很难在不修改业务代码的前提下插入额外日志通道。
|
||||
|
||||
sink 模式的好处是,业务代码只需要依赖:
|
||||
|
||||
```cpp
|
||||
Logger::Get().Info(LogCategory::General, "...");
|
||||
```
|
||||
|
||||
至于这条日志最终是进入控制台、文件还是编辑器面板,可以由启动阶段自由拼装。
|
||||
|
||||
## 当前架构中的角色分工
|
||||
|
||||
### `Logger`
|
||||
|
||||
- 维护全局最小级别。
|
||||
- 维护 category 开关。
|
||||
- 生成 `LogEntry`。
|
||||
- 把日志广播给已注册 sink。
|
||||
|
||||
### `ILogSink`
|
||||
|
||||
- 定义输出目标的最小契约。
|
||||
- 允许扩展出控制台、文件、内存缓存或远程诊断通道。
|
||||
|
||||
### `ConsoleLogSink`
|
||||
|
||||
- 面向“立即可见”的输出。
|
||||
- 最适合测试程序、命令行工具和开发期启动日志。
|
||||
|
||||
### `FileLogSink`
|
||||
|
||||
- 面向“事后复盘”的输出。
|
||||
- 最适合编辑器、长时间运行工具和自动化测试。
|
||||
|
||||
## 推荐接线方式
|
||||
|
||||
启动阶段推荐做三件事:
|
||||
|
||||
1. 显式 `Logger::Get().Initialize()`。
|
||||
2. 注册至少一个可见 sink,例如 `ConsoleLogSink`。
|
||||
3. 如果程序有会话级价值,再追加 `FileLogSink`。
|
||||
|
||||
示例:
|
||||
|
||||
```cpp
|
||||
using namespace XCEngine::Debug;
|
||||
|
||||
Logger::Get().Initialize();
|
||||
Logger::Get().AddSink(std::make_unique<ConsoleLogSink>());
|
||||
Logger::Get().AddSink(std::make_unique<FileLogSink>("editor.log"));
|
||||
Logger::Get().SetMinimumLevel(LogLevel::Debug);
|
||||
```
|
||||
|
||||
这也是当前编辑器和多个集成测试采用的基本模式。
|
||||
|
||||
## 和 Unity 的对照
|
||||
|
||||
可以把这套结构理解为:
|
||||
|
||||
- 比 Unity Console 更底层,因为它先解决日志分发,再决定是否显示在 UI。
|
||||
- 比单纯的 `Debug.Log` 更偏引擎层,因为它允许接多个输出目标。
|
||||
|
||||
但它又明显没有 Unity 那么完整:
|
||||
|
||||
- 还没有统一的上下文对象、堆栈追踪或富文本格式化。
|
||||
- `XE_ASSERT` 当前也只会记一条 `Fatal` 日志,并不会中断执行。
|
||||
|
||||
## 当前版本最重要的限制
|
||||
|
||||
- `Logger::SetMinimumLevel` 和 `SetCategoryEnabled` 当前没有加锁,最好在初始化阶段配置。
|
||||
- `Shutdown` 不应和其它线程的 `Log` 并发执行。
|
||||
- 便捷方法 `Info`、`Warning` 等默认不带源码位置信息;需要文件和行号时,应优先使用 `XE_LOG`。
|
||||
- `FileLogSink` 会每次写入后立刻刷新文件,诊断友好,但吞吐量不是最优。
|
||||
|
||||
## 推荐实践
|
||||
|
||||
- 引擎启动、设备创建、资源加载失败这类关键路径,用 `Info` / `Warning` / `Error` 做结构化记录。
|
||||
- 高频噪声日志放到 `Verbose` 或 `Debug`,并通过最小级别统一控制。
|
||||
- category 要按子系统区分,不要把所有信息都塞到 `General`。
|
||||
- 如果未来需要编辑器控制台过滤、远程日志或测试断言统计,优先加 sink,不要让业务代码直接分叉输出逻辑。
|
||||
|
||||
## 相关 API
|
||||
|
||||
- [Debug](../../XCEngine/Debug/Debug.md)
|
||||
- [Logger](../../XCEngine/Debug/Logger/Logger.md)
|
||||
- [ILogSink](../../XCEngine/Debug/ILogSink/ILogSink.md)
|
||||
- [ConsoleLogSink](../../XCEngine/Debug/ConsoleLogSink/ConsoleLogSink.md)
|
||||
- [FileLogSink](../../XCEngine/Debug/FileLogSink/FileLogSink.md)
|
||||
109
docs/api/_guides/Debug/Profiler-Workflow.md
Normal file
109
docs/api/_guides/Debug/Profiler-Workflow.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Profiler Workflow
|
||||
|
||||
## 先说结论
|
||||
|
||||
`XCEngine::Debug::Profiler` 当前不是成熟的分析器,而是一个“轻量级 CPU 埋点接口壳”。它已经能表示 profile 区段,但还没有形成完整的数据消费闭环。
|
||||
|
||||
这不是坏事,但文档和使用方式必须诚实:
|
||||
|
||||
- 它适合做早期埋点。
|
||||
- 它不适合被描述成 Unity Profiler 那种完整时间线工具。
|
||||
|
||||
## 现在真正能做什么
|
||||
|
||||
当前实现中,`BeginProfile` / `EndProfile` 会记录:
|
||||
|
||||
- 区段名
|
||||
- 开始时间
|
||||
- 结束时间
|
||||
- 线程哈希
|
||||
|
||||
这证明设计方向是对的:接口已经在向“作用域埋点”靠拢。
|
||||
|
||||
但是同一时间,以下能力还没打通:
|
||||
|
||||
- 没有公开的样本读取 API。
|
||||
- `MarkEvent`、`SetMarker`、`ExportChromeTracing` 仍是空实现。
|
||||
- `EndFrame` 会直接清空样本。
|
||||
|
||||
所以当前更合理的定位是:
|
||||
|
||||
- 先把埋点位置和调用习惯稳定下来;
|
||||
- 再逐步补数据导出、可视化和多线程时间线。
|
||||
|
||||
## 为什么先做成单例
|
||||
|
||||
对调试工具而言,单例有一个现实优势:调用点成本极低。
|
||||
|
||||
```cpp
|
||||
XE_PROFILE_FUNCTION();
|
||||
```
|
||||
|
||||
这种接法非常适合逐步给引擎关键路径加埋点,而不需要把 profiler 对象层层传递。
|
||||
|
||||
代价也很明显:
|
||||
|
||||
- 当前实现只有一个全局栈。
|
||||
- 如果多个线程同时写入,栈结构和样本语义都会混乱。
|
||||
|
||||
这就是为什么文档必须明确写“当前只适合受控单线程埋点”。
|
||||
|
||||
## 推荐工作流
|
||||
|
||||
当前阶段推荐把 `Profiler` 用在下面这些场景:
|
||||
|
||||
1. 主线程 CPU 热路径粗测。
|
||||
2. 某个函数或阶段的进入/退出埋点。
|
||||
3. 为未来时间线导出预留稳定的区段命名。
|
||||
|
||||
推荐写法:
|
||||
|
||||
```cpp
|
||||
using namespace XCEngine::Debug;
|
||||
|
||||
Profiler::Get().Initialize();
|
||||
Profiler::Get().BeginFrame();
|
||||
|
||||
XE_PROFILE_BEGIN("SceneUpdate");
|
||||
// ...
|
||||
XE_PROFILE_END();
|
||||
|
||||
Profiler::Get().EndFrame();
|
||||
```
|
||||
|
||||
## 和 Unity Profiler 的差异
|
||||
|
||||
可以把它理解为“还在早期形态的引擎内 CPU 埋点接口”,而不是 Unity Profiler 的直接对标物。
|
||||
|
||||
Unity Profiler 已经提供:
|
||||
|
||||
- 完整的帧时间线
|
||||
- 多线程可视化
|
||||
- 统计聚合
|
||||
- UI 分析工具
|
||||
|
||||
XCEngine 当前的 `Profiler` 还没有这些能力。它更像是先把“埋点 API”立住,再逐步扩展后端。
|
||||
|
||||
## 当前使用时最该注意的事
|
||||
|
||||
- `BeginProfile` / `EndProfile` 必须按栈顺序配对。
|
||||
- 不要跨线程共享同一条 profile 栈。
|
||||
- 不要指望 `ExportChromeTracing` 现在就会产出文件。
|
||||
- 如果你在 `EndFrame` 之后再找样本,当前实现里它们已经被清掉了。
|
||||
|
||||
## 下一阶段最值得补的能力
|
||||
|
||||
如果继续把这个模块做实,最有价值的方向通常是:
|
||||
|
||||
1. 暴露只读样本访问接口。
|
||||
2. 把样本按线程分桶。
|
||||
3. 实现 Chrome Trace / Perfetto 导出。
|
||||
4. 在编辑器里增加最小时间线浏览能力。
|
||||
|
||||
## 相关 API
|
||||
|
||||
- [Debug](../../XCEngine/Debug/Debug.md)
|
||||
- [Profiler](../../XCEngine/Debug/Profiler/Profiler.md)
|
||||
- [BeginProfile](../../XCEngine/Debug/Profiler/BeginProfile.md)
|
||||
- [EndProfile](../../XCEngine/Debug/Profiler/EndProfile.md)
|
||||
- [ExportChromeTracing](../../XCEngine/Debug/Profiler/ExportChromeTracing.md)
|
||||
88
docs/api/_guides/Debug/RenderDoc-Capture-Workflow.md
Normal file
88
docs/api/_guides/Debug/RenderDoc-Capture-Workflow.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# RenderDoc Capture Workflow
|
||||
|
||||
## 为什么要把 RenderDoc 接进引擎
|
||||
|
||||
对图形引擎来说,GPU 调试不是“出了问题再临时打开工具”的事,而应该是测试与验证流程的一部分。
|
||||
|
||||
`RenderDocCapture` 的意义就在这里:
|
||||
|
||||
- D3D12 和 OpenGL 集成测试可以在目标帧自动开始和结束抓帧。
|
||||
- 调试流程可以和测试用例、窗口创建、设备初始化保持同一套代码路径。
|
||||
- 不需要依赖人工热键或临时手动附加。
|
||||
|
||||
## 推荐接入顺序
|
||||
|
||||
当前代码库里的典型流程是:
|
||||
|
||||
1. 创建窗口。
|
||||
2. 调用 `RenderDocCapture::Get().Initialize(nullptr, hwnd)`。
|
||||
3. 初始化图形设备。
|
||||
4. 调用 `SetDevice(...)` 补齐真实设备指针。
|
||||
5. 在目标帧前调用 `BeginCapture(...)`。
|
||||
6. 在目标帧后调用 `EndCapture()`。
|
||||
7. 程序结束前调用 `Shutdown()`。
|
||||
|
||||
示例:
|
||||
|
||||
```cpp
|
||||
using namespace XCEngine::Debug;
|
||||
|
||||
RenderDocCapture::Get().Initialize(nullptr, hwnd);
|
||||
RenderDocCapture::Get().SetCaptureFilePath(".\\triangle_frame30");
|
||||
RenderDocCapture::Get().SetDevice(devicePtr);
|
||||
|
||||
if (frameCount == 29) {
|
||||
RenderDocCapture::Get().BeginCapture("Triangle_Test");
|
||||
}
|
||||
|
||||
if (frameCount == 30) {
|
||||
RenderDocCapture::Get().EndCapture();
|
||||
}
|
||||
```
|
||||
|
||||
## 为什么允许先不传 device
|
||||
|
||||
窗口通常比图形设备更早创建。当前 `Initialize` 支持先传 `window`、后传 `device`,就是为了对齐真实的引擎启动顺序。
|
||||
|
||||
这点在图形测试里尤其重要,因为:
|
||||
|
||||
- 有时必须先拿到 `HWND` 才能初始化后端。
|
||||
- 但 RenderDoc 的目标窗口又应该尽早绑定好。
|
||||
|
||||
## 这套设计的好处
|
||||
|
||||
- 把“抓帧时机”从人工操作变成代码可控流程。
|
||||
- D3D12 / OpenGL 可以复用同一套抓帧入口。
|
||||
- 自动化测试更容易固定在某一帧抓取稳定结果。
|
||||
|
||||
## 当前限制
|
||||
|
||||
- 这是 Windows 专用封装。
|
||||
- `renderdoc.dll` 必须位于可执行文件目录。
|
||||
- `BeginCapture` 会尝试把窗口切到前台,这对自动化环境可能是有副作用的。
|
||||
- `LaunchReplayUI` 只能确认“请求已发出”,不能确认 UI 一定成功启动。
|
||||
- 头文件暴露的是原始 `void*` 设备和窗口指针,没有类型安全包装。
|
||||
|
||||
## 和 Unity 的对照
|
||||
|
||||
它更接近“把外部 RenderDoc 工作流内嵌到引擎测试流程”,而不是 Unity Frame Debugger 那种引擎内逐 draw-call 可视化工具。
|
||||
|
||||
换句话说:
|
||||
|
||||
- Unity Frame Debugger 侧重引擎自己解释渲染步骤。
|
||||
- `RenderDocCapture` 侧重让 RenderDoc 在正确的时机抓到正确的帧。
|
||||
|
||||
## 实战建议
|
||||
|
||||
- 把抓帧逻辑放在测试场景最稳定的关键帧,而不是随机运行时。
|
||||
- 在调用 `BeginCapture` 前确保设备和窗口都已设置完成。
|
||||
- 用 `SetCaptureFilePath` 给不同测试用例设置可区分的输出前缀。
|
||||
- 如果要写自动化诊断流程,优先把 capture 的开始/结束时机做成和帧数、状态机绑定,而不是临时热键。
|
||||
|
||||
## 相关 API
|
||||
|
||||
- [Debug](../../XCEngine/Debug/Debug.md)
|
||||
- [RenderDocCapture](../../XCEngine/Debug/RenderDocCapture/RenderDocCapture.md)
|
||||
- [Initialize](../../XCEngine/Debug/RenderDocCapture/Initialize.md)
|
||||
- [BeginCapture](../../XCEngine/Debug/RenderDocCapture/BeginCapture.md)
|
||||
- [EndCapture](../../XCEngine/Debug/RenderDocCapture/EndCapture.md)
|
||||
Reference in New Issue
Block a user