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 消费)
|
// 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;
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user