feat: Tracy GPU 时间线——RG pass 级 GPU zone

RenderGraph 新增 IGpuProfiler 可选注入(pass 边界 Begin/End 钩子 +
帧尾 Collect,zone 覆盖 barrier+lambda+postBarriers),RG 本体不感知
任何 profiler 实现。Renderer 侧 TracyGpuProfiler 实现:TracyVkContext
(校准用临时 cb,池必须带 RESET_COMMAND_BUFFER_BIT——tracy 校准流程
对同一 cb 连续 begin 三次)+ TracyVkZoneTransient(pass 名是运行时
字符串)。TRACY_ENABLE 关闭全链路退化为空。

用法:Tracy GUI(0.14.1 配对)连接后按 pass 名看 GPU 段时间线,
与 ImGui 数表互补;LOD 调优的测量前置。
This commit is contained in:
2026-09-24 19:45:17 +08:00
parent 99e6712cdf
commit 3bfe28b027
6 changed files with 182 additions and 0 deletions
+14
View File
@@ -728,6 +728,14 @@ namespace Engine::RG {
auto& pass = m_Passes[passIdx];
if (pass.culled) continue;
// profiler zone:覆盖本 pass 的 barrier + lambda + postBarriers
// (GPU 时间线里 pass 是完整段,含同步开销)
Scope<IGpuProfiler::IScope> zoneScope;
if (m_GpuProfiler) {
zoneScope = m_GpuProfiler->BeginPass(pass.name.c_str(),
frame.cmd);
}
if (m_QueryPool) {
frame.cmd.writeTimestamp2(
vk::PipelineStageFlagBits2::eBottomOfPipe,
@@ -798,8 +806,14 @@ namespace Engine::RG {
vk::PipelineStageFlagBits2::eBottomOfPipe,
m_QueryPool, frame.frameSlot * m_QueriesPerFrame + tsPos * 2 + 1);
}
zoneScope.reset(); // END 时间戳
++tsPos;
}
// 帧尾收集:profiler 在当帧 cmd 内解析查询并上报(提交前录制)
if (m_GpuProfiler) {
m_GpuProfiler->EndFrame(frame.cmd);
}
}
// ---------- RenderGraphContext ----------
+5
View File
@@ -17,6 +17,7 @@
#include <Engine/Renderer/Passes/ForwardPass.h>
#include <Engine/Renderer/Cubemap.h>
#include <Engine/Renderer/IblGenerator.h>
#include <Engine/Renderer/TracyGpuProfiler.h>
#include <Engine/Renderer/Passes/TAAPass.h>
#include <Engine/Renderer/Passes/BloomPass.h>
#include <Engine/Renderer/Passes/TonemapPass.h>
@@ -126,6 +127,10 @@ namespace Engine
m_TimestampPeriod = static_cast<f32>(
m_Device->GetPhysicalDevice().getProperties().limits.timestampPeriod);
// 10. Tracy GPU 时间线(可选注入;TRACY_ENABLE 关闭时空实现)
m_TracyProfiler = CreateScope<TracyGpuProfiler>(*m_Device);
m_RenderGraph->SetGpuProfiler(m_TracyProfiler.get());
ENGINE_INFO("Renderer initialized");
}
+99
View File
@@ -0,0 +1,99 @@
//
// Created by atdunbg on 2026/9/24.
//
#include <Engine/Renderer/TracyGpuProfiler.h>
#include <Engine/Vulkan/Device.h>
#include <Engine/Core/Log.h>
#include <tracy/TracyVulkan.hpp>
#include <cstring>
namespace Engine {
// pass scope:构造 = BEGIN 时间戳,析构 = END(覆盖 pass lambda 录制期)
class TracyGpuProfiler::ScopeImpl final : public RG::RenderGraph::IGpuProfiler::IScope {
public:
#ifdef TRACY_ENABLE
ScopeImpl(TracyVkCtx ctx, const char* name, vk::CommandBuffer cmd)
: m_Zone(ctx, static_cast<uint32_t>(__LINE__), __FILE__,
sizeof(__FILE__) - 1, __FUNCTION__, sizeof(__FUNCTION__) - 1,
name, std::strlen(name),
static_cast<VkCommandBuffer>(cmd), true) {}
tracy::VkCtxScope m_Zone;
#else
ScopeImpl(TracyVkCtx, const char*, vk::CommandBuffer) {}
#endif
};
TracyGpuProfiler::TracyGpuProfiler(Device& device)
{
#ifdef TRACY_ENABLE
// 校准用临时 command buffer:tracy 校准流程对同一 cb 连续
// begin 三次(无中间 reset)——池必须带 RESET_COMMAND_BUFFER_BIT,
// 否则 VUID-vkBeginCommandBuffer-commandBuffer-00050
vk::CommandPoolCreateInfo poolInfo;
poolInfo.setQueueFamilyIndex(device.GetGraphicsFamily())
.setFlags(vk::CommandPoolCreateFlagBits::eTransient |
vk::CommandPoolCreateFlagBits::eResetCommandBuffer);
vk::CommandPool pool =
device.GetDevice().createCommandPool(poolInfo);
vk::CommandBuffer cb = device.GetDevice().allocateCommandBuffers(
vk::CommandBufferAllocateInfo{}
.setCommandPool(pool)
.setLevel(vk::CommandBufferLevel::ePrimary)
.setCommandBufferCount(1))[0];
m_Ctx = TracyVkContext(
static_cast<VkPhysicalDevice>(device.GetPhysicalDevice()),
static_cast<VkDevice>(device.GetDevice()),
static_cast<VkQueue>(device.GetGraphicsQueue()),
static_cast<VkCommandBuffer>(cb));
device.GetDevice().freeCommandBuffers(pool, cb);
device.GetDevice().destroyCommandPool(pool);
if (m_Ctx) {
ENGINE_INFO("Tracy GPU profiler attached (pass zones active)");
} else {
ENGINE_WARN("Tracy GPU profiler: context creation failed, disabled");
}
#endif
}
TracyGpuProfiler::~TracyGpuProfiler()
{
#ifdef TRACY_ENABLE
if (m_Ctx) {
TracyVkDestroy(static_cast<TracyVkCtx>(m_Ctx));
}
#endif
}
Scope<TracyGpuProfiler::IScope> TracyGpuProfiler::BeginPass(
const char* name, vk::CommandBuffer cmd)
{
#ifdef TRACY_ENABLE
if (!m_Ctx) return nullptr;
return Scope<IScope>(new ScopeImpl(
static_cast<TracyVkCtx>(m_Ctx), name, cmd));
#else
(void)name;
(void)cmd;
return nullptr;
#endif
}
void TracyGpuProfiler::EndFrame(vk::CommandBuffer cmd)
{
#ifdef TRACY_ENABLE
if (m_Ctx) {
TracyVkCollect(static_cast<TracyVkCtx>(m_Ctx),
static_cast<VkCommandBuffer>(cmd));
}
#else
(void)cmd;
#endif
}
} // namespace Engine