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:
@@ -260,6 +260,24 @@ namespace Engine::RG {
|
|||||||
// pool 由调用方创建(容量 ≥ framesInFlight × queriesPerFrame),
|
// pool 由调用方创建(容量 ≥ framesInFlight × queriesPerFrame),
|
||||||
// 读取由调用方在本槽位 fence 等待之后进行。
|
// 读取由调用方在本槽位 fence 等待之后进行。
|
||||||
void SetQueryPool(vk::QueryPool pool, u32 queriesPerFrame);
|
void SetQueryPool(vk::QueryPool pool, u32 queriesPerFrame);
|
||||||
|
|
||||||
|
// GPU profiler(可选):pass 边界的 GPU zone 钩子(Tracy 等工具的
|
||||||
|
// 接缝,RG 本体不感知任何 profiler 实现)。BeginPass 返回的 scope
|
||||||
|
// 析构 = 结束时间戳;注入者须保证 scope 生命周期覆盖 pass lambda。
|
||||||
|
// EndFrame 在全部 pass 录制完后调用(仍在当帧 cmd 内,提交前)。
|
||||||
|
class IGpuProfiler {
|
||||||
|
public:
|
||||||
|
virtual ~IGpuProfiler() = default;
|
||||||
|
class IScope {
|
||||||
|
public:
|
||||||
|
virtual ~IScope() = default;
|
||||||
|
};
|
||||||
|
[[nodiscard]] virtual Scope<IScope> BeginPass(
|
||||||
|
const char* name, vk::CommandBuffer cmd) = 0;
|
||||||
|
virtual void EndFrame(vk::CommandBuffer cmd) = 0;
|
||||||
|
};
|
||||||
|
void SetGpuProfiler(IGpuProfiler* profiler) { m_GpuProfiler = profiler; }
|
||||||
|
|
||||||
std::vector<std::string> GetPassNames() const;
|
std::vector<std::string> GetPassNames() const;
|
||||||
|
|
||||||
// ---- 统计 ----
|
// ---- 统计 ----
|
||||||
@@ -314,6 +332,7 @@ namespace Engine::RG {
|
|||||||
// GPU timestamp(可选注入)
|
// GPU timestamp(可选注入)
|
||||||
vk::QueryPool m_QueryPool{};
|
vk::QueryPool m_QueryPool{};
|
||||||
u32 m_QueriesPerFrame = 0;
|
u32 m_QueriesPerFrame = 0;
|
||||||
|
IGpuProfiler* m_GpuProfiler = nullptr; // 非拥有(Renderer 持有)
|
||||||
|
|
||||||
// 物理资源池:key = (format, extent, usage),value = 空闲镜像列表
|
// 物理资源池:key = (format, extent, usage),value = 空闲镜像列表
|
||||||
struct PoolKey {
|
struct PoolKey {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ namespace Engine {
|
|||||||
class Cubemap;
|
class Cubemap;
|
||||||
class IblGenerator;
|
class IblGenerator;
|
||||||
class ImGuiLayer;
|
class ImGuiLayer;
|
||||||
|
class TracyGpuProfiler;
|
||||||
|
|
||||||
namespace ECS { class Scene; }
|
namespace ECS { class Scene; }
|
||||||
namespace Assets { class AssetManager; class IAssetUploader; }
|
namespace Assets { class AssetManager; class IAssetUploader; }
|
||||||
@@ -172,6 +173,9 @@ namespace Engine {
|
|||||||
std::vector<f32> m_PassGpuMs; // 最近一帧各 pass 耗时
|
std::vector<f32> m_PassGpuMs; // 最近一帧各 pass 耗时
|
||||||
bool m_TimesPrimed[2] = {false, false}; // 每槽位首帧跳过读取
|
bool m_TimesPrimed[2] = {false, false}; // 每槽位首帧跳过读取
|
||||||
|
|
||||||
|
// Tracy GPU 时间线(pass zone;TRACY_ENABLE 关闭时空实现)
|
||||||
|
Scope<TracyGpuProfiler> m_TracyProfiler;
|
||||||
|
|
||||||
vk::Extent2D m_Extent{};
|
vk::Extent2D m_Extent{};
|
||||||
u32 m_CurrentFrame = 0;
|
u32 m_CurrentFrame = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/24.
|
||||||
|
//
|
||||||
|
// TracyGpuProfiler:RG::IGpuProfiler 的 Tracy Vulkan 实现。
|
||||||
|
//
|
||||||
|
// 每个 RG pass 在 GPU 时间线里是一个 zone(TracyVkZoneTransient,运行时
|
||||||
|
// 名字),时间戳在命令录制期写入当帧 cmd,TracyVkCollect 在帧尾解析。
|
||||||
|
// TRACY_ENABLE 关闭时退化为空实现(零开销)。
|
||||||
|
//
|
||||||
|
// 时间戳未做 CPU/GPU 校准(TracyVkContext 非 calibrated 变体):
|
||||||
|
// Tracy 按收集批次对齐,长时间运行的漂移可后续升级 Calibrated 变体。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_TRACYGPUPROFILER_H
|
||||||
|
#define HELLOVULKAN_TRACYGPUPROFILER_H
|
||||||
|
|
||||||
|
#include <Engine/RenderGraph/RenderGraph.h>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
class Device;
|
||||||
|
|
||||||
|
class TracyGpuProfiler final : public RG::RenderGraph::IGpuProfiler {
|
||||||
|
public:
|
||||||
|
explicit TracyGpuProfiler(Device& device);
|
||||||
|
~TracyGpuProfiler() override;
|
||||||
|
|
||||||
|
TracyGpuProfiler(const TracyGpuProfiler&) = delete;
|
||||||
|
TracyGpuProfiler& operator=(const TracyGpuProfiler&) = delete;
|
||||||
|
|
||||||
|
[[nodiscard]] Scope<IScope> BeginPass(const char* name,
|
||||||
|
vk::CommandBuffer cmd) override;
|
||||||
|
void EndFrame(vk::CommandBuffer cmd) override;
|
||||||
|
private:
|
||||||
|
class ScopeImpl;
|
||||||
|
void* m_Ctx{}; // tracy::VkCtx*(TracyVulkan.hpp 只在实现内 include)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_TRACYGPUPROFILER_H
|
||||||
@@ -728,6 +728,14 @@ namespace Engine::RG {
|
|||||||
auto& pass = m_Passes[passIdx];
|
auto& pass = m_Passes[passIdx];
|
||||||
if (pass.culled) continue;
|
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) {
|
if (m_QueryPool) {
|
||||||
frame.cmd.writeTimestamp2(
|
frame.cmd.writeTimestamp2(
|
||||||
vk::PipelineStageFlagBits2::eBottomOfPipe,
|
vk::PipelineStageFlagBits2::eBottomOfPipe,
|
||||||
@@ -798,8 +806,14 @@ namespace Engine::RG {
|
|||||||
vk::PipelineStageFlagBits2::eBottomOfPipe,
|
vk::PipelineStageFlagBits2::eBottomOfPipe,
|
||||||
m_QueryPool, frame.frameSlot * m_QueriesPerFrame + tsPos * 2 + 1);
|
m_QueryPool, frame.frameSlot * m_QueriesPerFrame + tsPos * 2 + 1);
|
||||||
}
|
}
|
||||||
|
zoneScope.reset(); // END 时间戳
|
||||||
++tsPos;
|
++tsPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 帧尾收集:profiler 在当帧 cmd 内解析查询并上报(提交前录制)
|
||||||
|
if (m_GpuProfiler) {
|
||||||
|
m_GpuProfiler->EndFrame(frame.cmd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- RenderGraphContext ----------
|
// ---------- RenderGraphContext ----------
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include <Engine/Renderer/Passes/ForwardPass.h>
|
#include <Engine/Renderer/Passes/ForwardPass.h>
|
||||||
#include <Engine/Renderer/Cubemap.h>
|
#include <Engine/Renderer/Cubemap.h>
|
||||||
#include <Engine/Renderer/IblGenerator.h>
|
#include <Engine/Renderer/IblGenerator.h>
|
||||||
|
#include <Engine/Renderer/TracyGpuProfiler.h>
|
||||||
#include <Engine/Renderer/Passes/TAAPass.h>
|
#include <Engine/Renderer/Passes/TAAPass.h>
|
||||||
#include <Engine/Renderer/Passes/BloomPass.h>
|
#include <Engine/Renderer/Passes/BloomPass.h>
|
||||||
#include <Engine/Renderer/Passes/TonemapPass.h>
|
#include <Engine/Renderer/Passes/TonemapPass.h>
|
||||||
@@ -126,6 +127,10 @@ namespace Engine
|
|||||||
m_TimestampPeriod = static_cast<f32>(
|
m_TimestampPeriod = static_cast<f32>(
|
||||||
m_Device->GetPhysicalDevice().getProperties().limits.timestampPeriod);
|
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");
|
ENGINE_INFO("Renderer initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user