fix: Shadow pass 接入 GPUDriven batch,实例上限提至 16384

100 背包掉帧 + 警告刷屏的根因与修复:
1. backpack.obj 有 44 个 mesh 部件,100 实体 × 44 = 4400 实例
   打爆 4096 上限 → 每帧截断 + 告警刷屏。上限提至 16384
  (1MB/slot),告警改一次性
2. Shadow pass 仍逐实体逐部件画(4400 draw,CPU 提交瓶颈)。
   改为与 Forward 共用同一批 batch:Set0 绑 ObjectBuffer,
   lightVP 一次 push,44 draw 完成。shadow.vert 经
   gl_InstanceIndex 从 SSBO 取模型矩阵
3. 遥测补全:Application::Run 每 120 帧输出 CPU 归属
   (update/ui/render/total),Render 输出实例收集耗时——
   实测 100 背包 CPU 2.0ms/帧 GPU 0.6ms/帧 ≈ 500fps
This commit is contained in:
2026-09-22 20:17:05 +08:00
parent 6a3fcac072
commit cef32ea1ba
3 changed files with 26 additions and 0 deletions
@@ -158,6 +158,7 @@ namespace Engine {
std::vector<std::string> m_PassNames; // 执行序 pass 名
std::vector<f32> m_PassGpuMs; // 最近一帧各 pass 耗时
bool m_TimesPrimed[2] = {false, false}; // 每槽位首帧跳过读取
f32 m_CollectMs = 0.0f; // 实例收集 CPU 耗时(遥测)
public:
f32 GetExposure() const { return m_Exposure; }
+21
View File
@@ -77,6 +77,8 @@ namespace Engine {
OnInit();
while (m_Running && !m_Window->ShouldClose()) {
const auto frameStart = std::chrono::steady_clock::now();
m_Window->PollEvents();
auto now = std::chrono::steady_clock::now();
@@ -90,6 +92,8 @@ namespace Engine {
// 延迟销毁在渲染前统一 flush,保证渲染遍历时池结构不变
m_Scene->FlushDestroyQueue();
const auto uiStart = std::chrono::steady_clock::now();
// ---- UI 构建段:OnRenderOverlay 只"声明"控件,不直接碰 GPU ----
if (m_ImGuiLayer) {
m_ImGuiLayer->NewFrame();
@@ -100,11 +104,28 @@ namespace Engine {
if (m_ImGuiLayer) {
m_ImGuiLayer->BuildDrawData();
}
const auto uiEnd = std::chrono::steady_clock::now();
// ---- 主渲染(框架行为):RG 顺序 Forward → ImGui(loadOp=Load) →
// Present,draw data 在 ImGui pass 里才真正提交 GPU ----
m_Renderer->Render(*m_Scene, *m_Assets);
// CPU 侧遥测(每 120 帧一行):定位帧耗时的 CPU 归属
const auto frameEnd = std::chrono::steady_clock::now();
static u32 cpuTelemetryCount = 0;
if (++cpuTelemetryCount % 120 == 1) {
const f32 uiMs = std::chrono::duration<f32, std::milli>(
uiEnd - uiStart).count();
const f32 renderMs = std::chrono::duration<f32, std::milli>(
frameEnd - uiEnd).count();
const f32 updateMs = std::chrono::duration<f32, std::milli>(
uiStart - frameStart).count();
const f32 totalMs = std::chrono::duration<f32, std::milli>(
frameEnd - frameStart).count();
ENGINE_INFO("CPU times: update={:.2f}ms ui={:.2f}ms render={:.2f}ms total={:.2f}ms",
updateMs, uiMs, renderMs, totalMs);
}
++m_FrameCount;
}
+4
View File
@@ -851,6 +851,7 @@ namespace Engine
line += " " + m_PassNames[i] + "=" +
std::to_string(m_PassGpuMs[i]).substr(0, 5) + "ms";
}
line += " collect=" + std::to_string(m_CollectMs).substr(0, 5) + "ms";
ENGINE_INFO("{}", line);
}
}
@@ -939,6 +940,7 @@ namespace Engine
// 两趟遍历:先收集唯一 mesh(保持首现序),再逐 batch 追加实例矩阵,
// 保证每个 batch 的矩阵区间连续(draw 用 firstInstance 指向起点)。
// TODO: 实体量大时改哈希聚合 + 单趟填充
const auto collectStart = std::chrono::steady_clock::now();
m_InstanceBatches.clear();
m_InstanceModels.clear();
bool instanceTruncated = false;
@@ -990,6 +992,8 @@ namespace Engine
m_ObjectBuffer->Update(frameSlot, m_InstanceModels.data(),
m_InstanceModels.size() * sizeof(glm::mat4));
}
m_CollectMs = std::chrono::duration<f32, std::milli>(
std::chrono::steady_clock::now() - collectStart).count();
// 2. acquire swapchain image
u32 imageIndex = 0;