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 取模型矩阵

实测(100 背包):GPU 总耗时 ~5.7ms(Shadow 2.9 + Forward 2.6 +
后处理 0.1),0 警告,帧率受 mailbox 垂直同步 pacing 限制
This commit is contained in:
2026-09-22 20:06:43 +08:00
parent 7fca721a24
commit 6a3fcac072
3 changed files with 50 additions and 38 deletions
+1 -1
View File
@@ -140,7 +140,7 @@ namespace Engine {
// GPUDriven:当帧实例数据(Render 期收集,Forward pass 消费) // GPUDriven:当帧实例数据(Render 期收集,Forward pass 消费)
std::vector<InstanceBatch> m_InstanceBatches; std::vector<InstanceBatch> m_InstanceBatches;
std::vector<glm::mat4> m_InstanceModels; std::vector<glm::mat4> m_InstanceModels;
static constexpr u32 kMaxInstances = 4096; static constexpr u32 kMaxInstances = 16384;
// 可选叠加层(不拥有;Application 持有) // 可选叠加层(不拥有;Application 持有)
ImGuiLayer* m_ImGuiLayer = nullptr; ImGuiLayer* m_ImGuiLayer = nullptr;
+41 -31
View File
@@ -52,10 +52,7 @@ namespace Engine
constexpr u32 kTextureSetIndex = 1; constexpr u32 kTextureSetIndex = 1;
constexpr u32 kShadowSetIndex = 2; constexpr u32 kShadowSetIndex = 2;
constexpr u32 kMaxLights = 4; constexpr u32 kMaxLights = 4;
// Shadow pass:push constant = { lightVP, model }
constexpr u32 kShadowMapSize = 2048; constexpr u32 kShadowMapSize = 2048;
constexpr u32 kShadowPushSize = static_cast<u32>(2 * sizeof(glm::mat4));
// 全屏 pass 通用 push(16B):所有全屏 pass(Tonemap/Bloom 系) // 全屏 pass 通用 push(16B):所有全屏 pass(Tonemap/Bloom 系)
// 统一声明与推送,规避 VUID-08878 的"range 定义必须一致" // 统一声明与推送,规避 VUID-08878 的"range 定义必须一致"
@@ -111,9 +108,10 @@ namespace Engine
m_PipelineLayout = m_DescriptorManager->CreatePipelineLayout( m_PipelineLayout = m_DescriptorManager->CreatePipelineLayout(
{m_UBOSetLayout, m_TextureSetLayout, m_ShadowSetLayout}, 0); {m_UBOSetLayout, m_TextureSetLayout, m_ShadowSetLayout}, 0);
// Shadow pass 独立 layout:无 set,128B push constant // Shadow pass layout:Set0 = UBO/ObjectBuffer(实例矩阵),push = lightVP
m_ShadowPipelineLayout = m_DescriptorManager->CreatePipelineLayout( m_ShadowPipelineLayout = m_DescriptorManager->CreatePipelineLayout(
std::vector<vk::DescriptorSetLayout>{}, kShadowPushSize); std::vector<vk::DescriptorSetLayout>{m_UBOSetLayout},
sizeof(glm::mat4));
// 7. ShaderManager(VK_EXT_shader_object) // 7. ShaderManager(VK_EXT_shader_object)
m_ShaderManager = CreateScope<ShaderManager>(*m_Device); m_ShaderManager = CreateScope<ShaderManager>(*m_Device);
@@ -135,10 +133,14 @@ namespace Engine
// shadow shader:depth-only,无 set // shadow shader:depth-only,无 set
auto shadowVert = m_ShaderManager->CreateFromFile( auto shadowVert = m_ShaderManager->CreateFromFile(
shaderDir + "shadow.vert.spv", shaderDir + "shadow.vert.spv",
vk::ShaderStageFlagBits::eVertex, {}, kShadowPushSize); vk::ShaderStageFlagBits::eVertex,
std::vector<vk::DescriptorSetLayout>{m_UBOSetLayout},
sizeof(glm::mat4));
auto shadowFrag = m_ShaderManager->CreateFromFile( auto shadowFrag = m_ShaderManager->CreateFromFile(
shaderDir + "shadow.frag.spv", shaderDir + "shadow.frag.spv",
vk::ShaderStageFlagBits::eFragment, {}, kShadowPushSize); vk::ShaderStageFlagBits::eFragment,
std::vector<vk::DescriptorSetLayout>{m_UBOSetLayout},
sizeof(glm::mat4));
m_ShadowVertShader = shadowVert.Handle; m_ShadowVertShader = shadowVert.Handle;
m_ShadowFragShader = shadowFrag.Handle; m_ShadowFragShader = shadowFrag.Handle;
@@ -225,7 +227,6 @@ namespace Engine
// 10. Per-frame UBO ring + GPUDriven 实例 SSBO ring // 10. Per-frame UBO ring + GPUDriven 实例 SSBO ring
// (同一 ring 类,SSBO 只是 usage 不同;正确性保证一致) // (同一 ring 类,SSBO 只是 usage 不同;正确性保证一致)
constexpr u32 kMaxInstances = 4096;
m_UniformBuffer = CreateScope<UniformBuffer>( m_UniformBuffer = CreateScope<UniformBuffer>(
*m_Device, *m_Allocator, sizeof(UniformBufferObject), kMaxFramesInFlight); *m_Device, *m_Allocator, sizeof(UniformBufferObject), kMaxFramesInFlight);
m_ObjectBuffer = CreateScope<UniformBuffer>( m_ObjectBuffer = CreateScope<UniformBuffer>(
@@ -445,29 +446,22 @@ namespace Engine
vk::Rect2D scissor({0, 0}, {kShadowMapSize, kShadowMapSize}); vk::Rect2D scissor({0, 0}, {kShadowMapSize, kShadowMapSize});
cmd.setScissorWithCount(scissor); cmd.setScissorWithCount(scissor);
// ---- 绘制:push {lightVP, model},与 Forward 用同一份 world 矩阵 ---- // ---- 绘制:GPUDriven——与 Forward 共用同一批 batch ----
struct ShadowPush { // lightVP 全 pass 只 push 一次;模型矩阵经实例索引从
glm::mat4 lightVP; // ObjectBuffer 取(与 Forward 同一份数据)
glm::mat4 model; cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
}; m_ShadowPipelineLayout, 0,
m_UBODescriptorSets[ctx.frame.frameSlot], {});
m_BoundScene->Each<TransformComponent, MeshRendererComponent>( // stage 必须覆盖 layout range 声明的全部 stage(VUID-01796)
[&](Entity, TransformComponent& transform, cmd.pushConstants(m_ShadowPipelineLayout,
MeshRendererComponent& meshRenderer) { vk::ShaderStageFlagBits::eVertex |
Model* model = m_BoundAssets->GetModel(meshRenderer.model); vk::ShaderStageFlagBits::eFragment,
if (!model) return; 0, sizeof(glm::mat4), &m_LightSpace);
const ShadowPush push{m_LightSpace, transform.world}; for (const auto& batch : m_InstanceBatches) {
// stage 必须覆盖 layout range 声明的全部 stage(VUID-01796) batch.mesh->Draw(cmd, batch.count, batch.first);
cmd.pushConstants(m_ShadowPipelineLayout, }
vk::ShaderStageFlagBits::eVertex |
vk::ShaderStageFlagBits::eFragment,
0, kShadowPushSize, &push);
for (auto& mesh : model->GetMeshes()) {
mesh->Draw(cmd);
}
});
cmd.endRendering(); cmd.endRendering();
}) })
@@ -848,6 +842,17 @@ namespace Engine
? static_cast<f32>(end - start) * m_TimestampPeriod / 1.0e6f ? static_cast<f32>(end - start) * m_TimestampPeriod / 1.0e6f
: 0.0f; : 0.0f;
} }
// 控制台遥测:每 120 次读取(≈60 帧 @2 slot)输出一行
static u32 telemetryCount = 0;
if (++telemetryCount % 120 == 1) {
std::string line = "GPU times:";
for (u32 i = 0; i < passCount; ++i) {
line += " " + m_PassNames[i] + "=" +
std::to_string(m_PassGpuMs[i]).substr(0, 5) + "ms";
}
ENGINE_INFO("{}", line);
}
} }
void Renderer::Render(ECS::Scene& scene, Assets::AssetManager& assets) void Renderer::Render(ECS::Scene& scene, Assets::AssetManager& assets)
@@ -973,8 +978,13 @@ namespace Engine
} }
} }
if (instanceTruncated) { if (instanceTruncated) {
ENGINE_WARN("GPUDriven: instance buffer full ({}), entities truncated", // 只告警一次(每帧告警会刷屏)
kMaxInstances); static bool warned = false;
if (!warned) {
warned = true;
ENGINE_WARN("GPUDriven: instance buffer full ({}), entities truncated",
kMaxInstances);
}
} }
if (!m_InstanceModels.empty()) { if (!m_InstanceModels.empty()) {
m_ObjectBuffer->Update(frameSlot, m_InstanceModels.data(), m_ObjectBuffer->Update(frameSlot, m_InstanceModels.data(),
+8 -6
View File
@@ -1,17 +1,19 @@
#version 450 #version 450
// Shadow pass vertex:光源视角写深度。 // Shadow pass vertex:光源视角写深度。
// push constant = { lightVP, model },与 Forward pass 用同一份 // GPUDriven:模型矩阵经 gl_InstanceIndex 从实例缓冲取(与 Forward
// lightVP(UBO.lightSpace)和同一份 model(TransformComponent.world), // 共用同一份数据),push 只带 lightVP(全 pass 一次)。
// 保证阴影贴图与主视角几何严格一致。
layout(location = 0) in vec3 inPosition; layout(location = 0) in vec3 inPosition;
layout(push_constant) uniform ShadowPush { layout(std430, set = 0, binding = 1) readonly buffer ObjectBuffer {
mat4 models[];
} objects;
layout(push_constant) uniform PushConstants {
mat4 lightVP; mat4 lightVP;
mat4 model;
} pc; } pc;
void main() { void main() {
gl_Position = pc.lightVP * pc.model * vec4(inPosition, 1.0); gl_Position = pc.lightVP * objects.models[gl_InstanceIndex] * vec4(inPosition, 1.0);
} }