From 3bfe28b027507d85a841a71baec3c8f83bd0cdb8 Mon Sep 17 00:00:00 2001 From: atdunbg Date: Thu, 24 Sep 2026 19:45:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Tracy=20GPU=20=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E7=BA=BF=E2=80=94=E2=80=94RG=20pass=20=E7=BA=A7=20GPU=20zone?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 调优的测量前置。 --- .../include/Engine/RenderGraph/RenderGraph.h | 19 ++++ Engine/include/Engine/Renderer/Renderer.h | 4 + .../Engine/Renderer/TracyGpuProfiler.h | 41 ++++++++ Engine/src/RenderGraph/RenderGraph.cpp | 14 +++ Engine/src/Renderer/Renderer.cpp | 5 + Engine/src/Renderer/TracyGpuProfiler.cpp | 99 +++++++++++++++++++ 6 files changed, 182 insertions(+) create mode 100644 Engine/include/Engine/Renderer/TracyGpuProfiler.h create mode 100644 Engine/src/Renderer/TracyGpuProfiler.cpp diff --git a/Engine/include/Engine/RenderGraph/RenderGraph.h b/Engine/include/Engine/RenderGraph/RenderGraph.h index 30ffa72..03ff4c3 100644 --- a/Engine/include/Engine/RenderGraph/RenderGraph.h +++ b/Engine/include/Engine/RenderGraph/RenderGraph.h @@ -260,6 +260,24 @@ namespace Engine::RG { // pool 由调用方创建(容量 ≥ framesInFlight × queriesPerFrame), // 读取由调用方在本槽位 fence 等待之后进行。 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 BeginPass( + const char* name, vk::CommandBuffer cmd) = 0; + virtual void EndFrame(vk::CommandBuffer cmd) = 0; + }; + void SetGpuProfiler(IGpuProfiler* profiler) { m_GpuProfiler = profiler; } + std::vector GetPassNames() const; // ---- 统计 ---- @@ -314,6 +332,7 @@ namespace Engine::RG { // GPU timestamp(可选注入) vk::QueryPool m_QueryPool{}; u32 m_QueriesPerFrame = 0; + IGpuProfiler* m_GpuProfiler = nullptr; // 非拥有(Renderer 持有) // 物理资源池:key = (format, extent, usage),value = 空闲镜像列表 struct PoolKey { diff --git a/Engine/include/Engine/Renderer/Renderer.h b/Engine/include/Engine/Renderer/Renderer.h index 0cf567b..c51f18d 100644 --- a/Engine/include/Engine/Renderer/Renderer.h +++ b/Engine/include/Engine/Renderer/Renderer.h @@ -44,6 +44,7 @@ namespace Engine { class Cubemap; class IblGenerator; class ImGuiLayer; + class TracyGpuProfiler; namespace ECS { class Scene; } namespace Assets { class AssetManager; class IAssetUploader; } @@ -172,6 +173,9 @@ namespace Engine { std::vector m_PassGpuMs; // 最近一帧各 pass 耗时 bool m_TimesPrimed[2] = {false, false}; // 每槽位首帧跳过读取 + // Tracy GPU 时间线(pass zone;TRACY_ENABLE 关闭时空实现) + Scope m_TracyProfiler; + vk::Extent2D m_Extent{}; u32 m_CurrentFrame = 0; }; diff --git a/Engine/include/Engine/Renderer/TracyGpuProfiler.h b/Engine/include/Engine/Renderer/TracyGpuProfiler.h new file mode 100644 index 0000000..e0dd1db --- /dev/null +++ b/Engine/include/Engine/Renderer/TracyGpuProfiler.h @@ -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 + +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 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 diff --git a/Engine/src/RenderGraph/RenderGraph.cpp b/Engine/src/RenderGraph/RenderGraph.cpp index 2cbe9c7..1a8ce31 100644 --- a/Engine/src/RenderGraph/RenderGraph.cpp +++ b/Engine/src/RenderGraph/RenderGraph.cpp @@ -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 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 ---------- diff --git a/Engine/src/Renderer/Renderer.cpp b/Engine/src/Renderer/Renderer.cpp index 78a3409..aa9c67c 100644 --- a/Engine/src/Renderer/Renderer.cpp +++ b/Engine/src/Renderer/Renderer.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -126,6 +127,10 @@ namespace Engine m_TimestampPeriod = static_cast( m_Device->GetPhysicalDevice().getProperties().limits.timestampPeriod); + // 10. Tracy GPU 时间线(可选注入;TRACY_ENABLE 关闭时空实现) + m_TracyProfiler = CreateScope(*m_Device); + m_RenderGraph->SetGpuProfiler(m_TracyProfiler.get()); + ENGINE_INFO("Renderer initialized"); } diff --git a/Engine/src/Renderer/TracyGpuProfiler.cpp b/Engine/src/Renderer/TracyGpuProfiler.cpp new file mode 100644 index 0000000..6fc6782 --- /dev/null +++ b/Engine/src/Renderer/TracyGpuProfiler.cpp @@ -0,0 +1,99 @@ +// +// Created by atdunbg on 2026/9/24. +// + +#include +#include +#include + +#include +#include + +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(__LINE__), __FILE__, + sizeof(__FILE__) - 1, __FUNCTION__, sizeof(__FUNCTION__) - 1, + name, std::strlen(name), + static_cast(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(device.GetPhysicalDevice()), + static_cast(device.GetDevice()), + static_cast(device.GetGraphicsQueue()), + static_cast(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(m_Ctx)); + } +#endif + } + + Scope TracyGpuProfiler::BeginPass( + const char* name, vk::CommandBuffer cmd) + { +#ifdef TRACY_ENABLE + if (!m_Ctx) return nullptr; + return Scope(new ScopeImpl( + static_cast(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(m_Ctx), + static_cast(cmd)); + } +#else + (void)cmd; +#endif + } + +} // namespace Engine