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:
@@ -140,7 +140,7 @@ namespace Engine {
|
||||
// GPUDriven:当帧实例数据(Render 期收集,Forward pass 消费)
|
||||
std::vector<InstanceBatch> m_InstanceBatches;
|
||||
std::vector<glm::mat4> m_InstanceModels;
|
||||
static constexpr u32 kMaxInstances = 4096;
|
||||
static constexpr u32 kMaxInstances = 16384;
|
||||
|
||||
// 可选叠加层(不拥有;Application 持有)
|
||||
ImGuiLayer* m_ImGuiLayer = nullptr;
|
||||
|
||||
@@ -52,10 +52,7 @@ namespace Engine
|
||||
constexpr u32 kTextureSetIndex = 1;
|
||||
constexpr u32 kShadowSetIndex = 2;
|
||||
constexpr u32 kMaxLights = 4;
|
||||
|
||||
// Shadow pass:push constant = { lightVP, model }
|
||||
constexpr u32 kShadowMapSize = 2048;
|
||||
constexpr u32 kShadowPushSize = static_cast<u32>(2 * sizeof(glm::mat4));
|
||||
|
||||
// 全屏 pass 通用 push(16B):所有全屏 pass(Tonemap/Bloom 系)
|
||||
// 统一声明与推送,规避 VUID-08878 的"range 定义必须一致"
|
||||
@@ -111,9 +108,10 @@ namespace Engine
|
||||
m_PipelineLayout = m_DescriptorManager->CreatePipelineLayout(
|
||||
{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(
|
||||
std::vector<vk::DescriptorSetLayout>{}, kShadowPushSize);
|
||||
std::vector<vk::DescriptorSetLayout>{m_UBOSetLayout},
|
||||
sizeof(glm::mat4));
|
||||
|
||||
// 7. ShaderManager(VK_EXT_shader_object)
|
||||
m_ShaderManager = CreateScope<ShaderManager>(*m_Device);
|
||||
@@ -135,10 +133,14 @@ namespace Engine
|
||||
// shadow shader:depth-only,无 set
|
||||
auto shadowVert = m_ShaderManager->CreateFromFile(
|
||||
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(
|
||||
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_ShadowFragShader = shadowFrag.Handle;
|
||||
@@ -225,7 +227,6 @@ namespace Engine
|
||||
|
||||
// 10. Per-frame UBO ring + GPUDriven 实例 SSBO ring
|
||||
// (同一 ring 类,SSBO 只是 usage 不同;正确性保证一致)
|
||||
constexpr u32 kMaxInstances = 4096;
|
||||
m_UniformBuffer = CreateScope<UniformBuffer>(
|
||||
*m_Device, *m_Allocator, sizeof(UniformBufferObject), kMaxFramesInFlight);
|
||||
m_ObjectBuffer = CreateScope<UniformBuffer>(
|
||||
@@ -445,29 +446,22 @@ namespace Engine
|
||||
vk::Rect2D scissor({0, 0}, {kShadowMapSize, kShadowMapSize});
|
||||
cmd.setScissorWithCount(scissor);
|
||||
|
||||
// ---- 绘制:push {lightVP, model},与 Forward 用同一份 world 矩阵 ----
|
||||
struct ShadowPush {
|
||||
glm::mat4 lightVP;
|
||||
glm::mat4 model;
|
||||
};
|
||||
// ---- 绘制:GPUDriven——与 Forward 共用同一批 batch ----
|
||||
// lightVP 全 pass 只 push 一次;模型矩阵经实例索引从
|
||||
// ObjectBuffer 取(与 Forward 同一份数据)
|
||||
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
|
||||
m_ShadowPipelineLayout, 0,
|
||||
m_UBODescriptorSets[ctx.frame.frameSlot], {});
|
||||
|
||||
m_BoundScene->Each<TransformComponent, MeshRendererComponent>(
|
||||
[&](Entity, TransformComponent& transform,
|
||||
MeshRendererComponent& meshRenderer) {
|
||||
Model* model = m_BoundAssets->GetModel(meshRenderer.model);
|
||||
if (!model) return;
|
||||
// stage 必须覆盖 layout range 声明的全部 stage(VUID-01796)
|
||||
cmd.pushConstants(m_ShadowPipelineLayout,
|
||||
vk::ShaderStageFlagBits::eVertex |
|
||||
vk::ShaderStageFlagBits::eFragment,
|
||||
0, sizeof(glm::mat4), &m_LightSpace);
|
||||
|
||||
const ShadowPush push{m_LightSpace, transform.world};
|
||||
// stage 必须覆盖 layout range 声明的全部 stage(VUID-01796)
|
||||
cmd.pushConstants(m_ShadowPipelineLayout,
|
||||
vk::ShaderStageFlagBits::eVertex |
|
||||
vk::ShaderStageFlagBits::eFragment,
|
||||
0, kShadowPushSize, &push);
|
||||
|
||||
for (auto& mesh : model->GetMeshes()) {
|
||||
mesh->Draw(cmd);
|
||||
}
|
||||
});
|
||||
for (const auto& batch : m_InstanceBatches) {
|
||||
batch.mesh->Draw(cmd, batch.count, batch.first);
|
||||
}
|
||||
|
||||
cmd.endRendering();
|
||||
})
|
||||
@@ -848,6 +842,17 @@ namespace Engine
|
||||
? static_cast<f32>(end - start) * m_TimestampPeriod / 1.0e6f
|
||||
: 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)
|
||||
@@ -973,8 +978,13 @@ namespace Engine
|
||||
}
|
||||
}
|
||||
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()) {
|
||||
m_ObjectBuffer->Update(frameSlot, m_InstanceModels.data(),
|
||||
|
||||
Reference in New Issue
Block a user