feat: RenderGraph GPU timestamp profiling

- RG::SetQueryPool 可选注入;Execute 期每 pass 前后各写一枚
  writeTimestamp2(BOTTOM_OF_PIPE),索引按帧槽位分区 + 执行序排布
- 每帧录制开头 reset 本槽位查询区间(上一轮结果已在 fence 等待后读取)
- Renderer 在 WaitFence 后读 64bit 结果换算毫秒(timestampPeriod 缓存),
  经 GetPassNames/GetPassGpuMs 透出给 ImGui 面板显示
- 每槽位首帧跳过读取(查询尚未 primed,避免 VUID-09401)
This commit is contained in:
2026-09-22 19:25:42 +08:00
parent 908e688178
commit 41e4cc85d5
5 changed files with 120 additions and 0 deletions
+48
View File
@@ -258,6 +258,19 @@ namespace Engine
UpdateShadowDescriptors();
UpdateFullscreenDescriptors();
// 12. GPU timestamp:每 pass 两枚(前后),按帧槽位分区
m_QueriesPerFrame = static_cast<u32>(m_RenderGraph->GetPassCount()) * 2;
m_PassNames = m_RenderGraph->GetPassNames();
m_PassGpuMs.assign(m_RenderGraph->GetPassCount(), 0.0f);
vk::QueryPoolCreateInfo qpInfo;
qpInfo.setQueryType(vk::QueryType::eTimestamp)
.setQueryCount(m_QueriesPerFrame * kMaxFramesInFlight);
m_QueryPool = m_Device->GetDevice().createQueryPool(qpInfo);
m_RenderGraph->SetQueryPool(m_QueryPool, m_QueriesPerFrame);
m_TimestampPeriod = static_cast<f32>(
m_Device->GetPhysicalDevice().getProperties().limits.timestampPeriod);
ENGINE_INFO("Renderer initialized");
}
@@ -294,6 +307,9 @@ namespace Engine
vkDevice.destroySampler(m_ShadowSampler);
vkDevice.destroySampler(m_TonemapSampler);
if (m_QueryPool) {
vkDevice.destroyQueryPool(m_QueryPool);
}
vkDevice.destroyPipelineLayout(m_PipelineLayout);
vkDevice.destroyPipelineLayout(m_ShadowPipelineLayout);
vkDevice.destroyPipelineLayout(m_TonemapPipelineLayout);
@@ -804,6 +820,37 @@ namespace Engine
}
}
void Renderer::ReadGpuTimes(u32 frameSlot)
{
if (!m_QueryPool || m_QueriesPerFrame == 0) return;
// 每槽位首帧:该区间的查询尚未被 reset/写入,跳过一次读取
if (!m_TimesPrimed[frameSlot]) {
m_TimesPrimed[frameSlot] = true;
return;
}
const u32 passCount = m_QueriesPerFrame / 2;
std::vector<u64> ticks(m_QueriesPerFrame, 0);
// fence 已等待本槽位上一轮完成,结果必然可用(阻塞式 + 64bit)
const vk::Result r = m_Device->GetDevice().getQueryPoolResults(
m_QueryPool,
frameSlot * m_QueriesPerFrame, m_QueriesPerFrame,
sizeof(u64) * m_QueriesPerFrame,
ticks.data(), sizeof(u64),
vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWait);
if (r != vk::Result::eSuccess) return;
for (u32 i = 0; i < passCount; ++i) {
const u64 start = ticks[i * 2];
const u64 end = ticks[i * 2 + 1];
m_PassGpuMs[i] = (end >= start)
? static_cast<f32>(end - start) * m_TimestampPeriod / 1.0e6f
: 0.0f;
}
}
void Renderer::Render(ECS::Scene& scene, Assets::AssetManager& assets)
{
m_BoundScene = &scene;
@@ -880,6 +927,7 @@ namespace Engine
// 此时本槽位的 fence 尚未 reset——先等它(保证 GPU 已完成本槽位上一轮),
// 再写 UBO,写-读竞争关闭。
m_SyncManager->WaitFence(frameSlot);
ReadGpuTimes(frameSlot); // fence 已等:上一轮该槽位的查询已就绪
m_UniformBuffer->Update(frameSlot, &ubo, sizeof(ubo));
m_SyncManager->ResetFence(frameSlot);