feat: GPUDriven 实例化渲染——draw 数与实体数解耦
- UniformBuffer ring 支持 eStorageBuffer usage(GPUDriven 实例矩阵 per-frame-slot,正确性保证与 UBO 一致:fence 等待后写入) - DescriptorManager 加 UpdateStorageBuffer;池补 STORAGE_BUFFER 类型 - Set0 新增 binding1 = 实例矩阵 SSBO(vertex 读取,std430) - Forward pass 重写:每帧从 ECS 收集实例矩阵(按 mesh 聚合 batch, 两趟遍历保证 batch 区间连续),同 mesh 的 N 个实体合并为 一次 drawIndexed(instanceCount=N),firstInstance 指向 batch 起点; vertex shader 经 gl_InstanceIndex 取模型矩阵,forward 不再用 push - draw 数 = 唯一 mesh 数,与实体数量解耦(4096 实例上限, 超出截断告警;聚合改哈希为后续 TODO) 验证:8 pass 全活,validation 0 error 0 warning,GPU Timings 面板 实测各 pass 毫秒,场景渲染与阴影正确
This commit is contained in:
@@ -30,7 +30,9 @@ namespace Engine
|
||||
Mesh(const Mesh&) = delete;
|
||||
Mesh& operator=(const Mesh&) = delete;
|
||||
|
||||
void Draw(vk::CommandBuffer cmd) const;
|
||||
// instanceCount/firstInstance:GPUDriven 按 mesh 分 batch 后,
|
||||
// 同 mesh 的 N 个实体一次 draw 完成,模型矩阵经实例索引取
|
||||
void Draw(vk::CommandBuffer cmd, u32 instanceCount = 1, u32 firstInstance = 0) const;
|
||||
|
||||
Material* GetMaterial() const { return m_Material.get(); }
|
||||
u32 GetIndexCount() const { return m_IndexCount; }
|
||||
|
||||
@@ -31,6 +31,14 @@ namespace Engine {
|
||||
namespace ECS { class Scene; }
|
||||
namespace Assets { class AssetManager; }
|
||||
class ImGuiLayer;
|
||||
class Mesh;
|
||||
|
||||
// GPUDriven:按 mesh 聚合的绘制批
|
||||
struct InstanceBatch {
|
||||
Mesh* mesh;
|
||||
u32 first; // 在实例矩阵数组中的起始下标
|
||||
u32 count; // 实例数
|
||||
};
|
||||
|
||||
// Renderer 职责(收敛后):
|
||||
// 1. 装配底层模块(Device/Swapchain/Sync/Command/Descriptor/Shader)
|
||||
@@ -78,6 +86,7 @@ namespace Engine {
|
||||
Scope<SyncManager> m_SyncManager;
|
||||
Scope<CommandManager> m_CommandManager;
|
||||
Scope<UniformBuffer> m_UniformBuffer;
|
||||
Scope<UniformBuffer> m_ObjectBuffer; // GPUDriven 实例矩阵 ring(SSBO)
|
||||
Scope<RG::RenderGraph> m_RenderGraph;
|
||||
|
||||
// ---- 管线装配(shader object 方案,无 vk::Pipeline)----
|
||||
@@ -128,6 +137,11 @@ namespace Engine {
|
||||
Assets::AssetManager* m_BoundAssets = nullptr;
|
||||
glm::mat4 m_LightSpace{1.0f}; // 阴影光源 VP(每帧由 Render 算好)
|
||||
|
||||
// GPUDriven:当帧实例数据(Render 期收集,Forward pass 消费)
|
||||
std::vector<InstanceBatch> m_InstanceBatches;
|
||||
std::vector<glm::mat4> m_InstanceModels;
|
||||
static constexpr u32 kMaxInstances = 4096;
|
||||
|
||||
// 可选叠加层(不拥有;Application 持有)
|
||||
ImGuiLayer* m_ImGuiLayer = nullptr;
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ namespace Engine {
|
||||
|
||||
void UpdateUniformBuffer(vk::DescriptorSet set, u32 binding,
|
||||
vk::Buffer buffer, u64 size);
|
||||
void UpdateStorageBuffer(vk::DescriptorSet set, u32 binding,
|
||||
vk::Buffer buffer, u64 size);
|
||||
|
||||
void UpdateTexture(vk::DescriptorSet set, u32 binding,
|
||||
vk::Sampler sampler, vk::ImageView view);
|
||||
|
||||
@@ -25,8 +25,11 @@ namespace Engine {
|
||||
// 接口已按"写入哪个槽位"设计,届时只是内部实现替换。
|
||||
class UniformBuffer {
|
||||
public:
|
||||
// usage:默认 eUniformBuffer;GPUDriven 实例数据传 eStorageBuffer
|
||||
//(ring 化的正确性保证对所有 per-frame 数据一致)
|
||||
UniformBuffer(Device& device, MemoryAllocator& allocator,
|
||||
u64 size, u32 frameCount);
|
||||
u64 size, u32 frameCount,
|
||||
vk::BufferUsageFlags usage = vk::BufferUsageFlagBits::eUniformBuffer);
|
||||
~UniformBuffer() = default;
|
||||
|
||||
UniformBuffer(const UniformBuffer&) = delete;
|
||||
|
||||
@@ -55,10 +55,10 @@ namespace Engine
|
||||
STATS.RemoveMesh(m_VertexBytes, m_IndexBytes);
|
||||
}
|
||||
|
||||
void Mesh::Draw(vk::CommandBuffer cmd) const
|
||||
void Mesh::Draw(vk::CommandBuffer cmd, u32 instanceCount, u32 firstInstance) const
|
||||
{
|
||||
cmd.bindVertexBuffers(0, m_VertexBuffer->Get(), {0});
|
||||
cmd.bindIndexBuffer(m_IndexBuffer->Get(), 0, vk::IndexType::eUint32);
|
||||
cmd.drawIndexed(m_IndexCount, 1, 0, 0, 0);
|
||||
cmd.drawIndexed(m_IndexCount, instanceCount, 0, 0, firstInstance);
|
||||
}
|
||||
} // namespace Engine
|
||||
|
||||
@@ -47,6 +47,7 @@ namespace Engine
|
||||
};
|
||||
|
||||
constexpr u32 kUboBinding = 0;
|
||||
constexpr u32 kObjectBinding = 1;
|
||||
constexpr u32 kUBOSetIndex = 0;
|
||||
constexpr u32 kTextureSetIndex = 1;
|
||||
constexpr u32 kShadowSetIndex = 2;
|
||||
@@ -85,10 +86,12 @@ namespace Engine
|
||||
// 3. DescriptorManager
|
||||
m_DescriptorManager = CreateScope<DescriptorManager>(*m_Device);
|
||||
|
||||
// 4. Set 0: UBO
|
||||
// 4. Set 0: UBO + GPUDriven 实例数据(SSBO,vertex 读取)
|
||||
std::vector<DescriptorBinding> uboBindings = {
|
||||
{kUboBinding, vk::DescriptorType::eUniformBuffer,
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||
{kObjectBinding, vk::DescriptorType::eStorageBuffer,
|
||||
vk::ShaderStageFlagBits::eVertex},
|
||||
};
|
||||
m_UBOSetLayout = m_DescriptorManager->CreateLayout(uboBindings);
|
||||
|
||||
@@ -104,10 +107,9 @@ namespace Engine
|
||||
};
|
||||
m_ShadowSetLayout = m_DescriptorManager->CreateLayout(shadowBindings);
|
||||
|
||||
// 6. PipelineLayout:三个 set;push constant 上限 128B
|
||||
//(Shadow pass 的 {lightVP, model} 用满;forward shader block 只用前 64B)
|
||||
// 6. PipelineLayout:三个 set;forward 无 push(GPUDriven 经 SSBO)
|
||||
m_PipelineLayout = m_DescriptorManager->CreatePipelineLayout(
|
||||
{m_UBOSetLayout, m_TextureSetLayout, m_ShadowSetLayout}, kShadowPushSize);
|
||||
{m_UBOSetLayout, m_TextureSetLayout, m_ShadowSetLayout}, 0);
|
||||
|
||||
// Shadow pass 独立 layout:无 set,128B push constant
|
||||
m_ShadowPipelineLayout = m_DescriptorManager->CreatePipelineLayout(
|
||||
@@ -117,14 +119,15 @@ namespace Engine
|
||||
m_ShaderManager = CreateScope<ShaderManager>(*m_Device);
|
||||
|
||||
std::string shaderDir = "shaders/";
|
||||
// forward vert/frag 均无 push(range 定义一致:都没有,VUID-08878)
|
||||
auto vert = m_ShaderManager->CreateFromFile(
|
||||
shaderDir + "simple_shader.vert.spv",
|
||||
vk::ShaderStageFlagBits::eVertex,
|
||||
{m_UBOSetLayout, m_TextureSetLayout, m_ShadowSetLayout}, kShadowPushSize);
|
||||
{m_UBOSetLayout, m_TextureSetLayout, m_ShadowSetLayout}, 0);
|
||||
auto frag = m_ShaderManager->CreateFromFile(
|
||||
shaderDir + "simple_shader.frag.spv",
|
||||
vk::ShaderStageFlagBits::eFragment,
|
||||
{m_UBOSetLayout, m_TextureSetLayout, m_ShadowSetLayout}, kShadowPushSize);
|
||||
{m_UBOSetLayout, m_TextureSetLayout, m_ShadowSetLayout}, 0);
|
||||
|
||||
m_VertShader = vert.Handle;
|
||||
m_FragShader = frag.Handle;
|
||||
@@ -220,9 +223,15 @@ namespace Engine
|
||||
*m_Device, kMaxFramesInFlight, m_Swapchain->GetImageCount());
|
||||
m_CommandManager = CreateScope<CommandManager>(*m_Device, kMaxFramesInFlight);
|
||||
|
||||
// 10. Per-frame UBO ring + 每槽位的 UBO descriptor set
|
||||
// 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>(
|
||||
*m_Device, *m_Allocator,
|
||||
sizeof(glm::mat4) * kMaxInstances, kMaxFramesInFlight,
|
||||
vk::BufferUsageFlagBits::eStorageBuffer);
|
||||
|
||||
m_UBODescriptorSets.resize(kMaxFramesInFlight);
|
||||
m_ShadowDescriptorSets.resize(kMaxFramesInFlight);
|
||||
@@ -236,6 +245,9 @@ namespace Engine
|
||||
m_DescriptorManager->UpdateUniformBuffer(
|
||||
m_UBODescriptorSets[slot], kUboBinding,
|
||||
m_UniformBuffer->Get(slot), m_UniformBuffer->GetSize());
|
||||
m_DescriptorManager->UpdateStorageBuffer(
|
||||
m_UBODescriptorSets[slot], kObjectBinding,
|
||||
m_ObjectBuffer->Get(slot), m_ObjectBuffer->GetSize());
|
||||
|
||||
m_ShadowDescriptorSets[slot] =
|
||||
m_DescriptorManager->AllocateDescriptorSet(m_ShadowSetLayout);
|
||||
@@ -564,29 +576,16 @@ namespace Engine
|
||||
vk::ColorComponentFlagBits::eA;
|
||||
cmd.setColorWriteMaskEXT(0, colorWriteMask);
|
||||
|
||||
// ---- 绘制:遍历 ECS 渲染体 ----
|
||||
// Each 以 Transform/MeshRenderer 中较小的池为主表——
|
||||
// 大场景下遍历开销与最小池规模成正比
|
||||
m_BoundScene->Each<TransformComponent, MeshRendererComponent>(
|
||||
[&](Entity, TransformComponent& transform,
|
||||
MeshRendererComponent& meshRenderer) {
|
||||
// 句柄 → 资产(悬空句柄会被 AssetCache FATAL 拦截)
|
||||
Model* model = m_BoundAssets->GetModel(meshRenderer.model);
|
||||
if (!model) return;
|
||||
|
||||
// stage 必须覆盖 layout range 声明的全部 stage(VUID-01796)
|
||||
cmd.pushConstants(m_PipelineLayout,
|
||||
vk::ShaderStageFlagBits::eVertex |
|
||||
vk::ShaderStageFlagBits::eFragment,
|
||||
0, sizeof(glm::mat4), &transform.world);
|
||||
|
||||
for (auto& mesh : model->GetMeshes()) {
|
||||
// ---- 绘制:GPUDriven 按 mesh 分 batch ----
|
||||
// 实例矩阵已在 Render() 里写入 ObjectBuffer(per-slot SSBO);
|
||||
// 同 mesh 的 N 个实体合并为一次 drawIndexed(instanceCount=N),
|
||||
// draw 数 = 唯一 mesh 数,与实体数量解耦
|
||||
for (const auto& batch : m_InstanceBatches) {
|
||||
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
|
||||
m_PipelineLayout, kTextureSetIndex,
|
||||
mesh->GetMaterial()->GetDescriptorSet(), {});
|
||||
mesh->Draw(cmd);
|
||||
batch.mesh->GetMaterial()->GetDescriptorSet(), {});
|
||||
batch.mesh->Draw(cmd, batch.count, batch.first);
|
||||
}
|
||||
});
|
||||
|
||||
cmd.endRendering();
|
||||
})
|
||||
@@ -931,6 +930,57 @@ namespace Engine
|
||||
m_UniformBuffer->Update(frameSlot, &ubo, sizeof(ubo));
|
||||
m_SyncManager->ResetFence(frameSlot);
|
||||
|
||||
// ---- GPUDriven:收集实例数据(按 mesh 聚合 batch)----
|
||||
// 两趟遍历:先收集唯一 mesh(保持首现序),再逐 batch 追加实例矩阵,
|
||||
// 保证每个 batch 的矩阵区间连续(draw 用 firstInstance 指向起点)。
|
||||
// TODO: 实体量大时改哈希聚合 + 单趟填充
|
||||
m_InstanceBatches.clear();
|
||||
m_InstanceModels.clear();
|
||||
bool instanceTruncated = false;
|
||||
|
||||
std::vector<Mesh*> uniqueMeshes;
|
||||
scene.Each<TransformComponent, MeshRendererComponent>(
|
||||
[&](Entity, TransformComponent&, MeshRendererComponent& mr) {
|
||||
Model* model = assets.GetModel(mr.model);
|
||||
if (!model) return;
|
||||
for (auto& mesh : model->GetMeshes()) {
|
||||
if (std::find(uniqueMeshes.begin(), uniqueMeshes.end(),
|
||||
mesh.get()) == uniqueMeshes.end()) {
|
||||
uniqueMeshes.push_back(mesh.get());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (Mesh* meshPtr : uniqueMeshes) {
|
||||
InstanceBatch batch{meshPtr,
|
||||
static_cast<u32>(m_InstanceModels.size()), 0};
|
||||
scene.Each<TransformComponent, MeshRendererComponent>(
|
||||
[&](Entity, TransformComponent& t, MeshRendererComponent& mr) {
|
||||
Model* model = assets.GetModel(mr.model);
|
||||
if (!model) return;
|
||||
for (auto& m : model->GetMeshes()) {
|
||||
if (m.get() != meshPtr) continue;
|
||||
if (m_InstanceModels.size() >= kMaxInstances) {
|
||||
instanceTruncated = true;
|
||||
return;
|
||||
}
|
||||
m_InstanceModels.push_back(t.world);
|
||||
++batch.count;
|
||||
}
|
||||
});
|
||||
if (batch.count > 0) {
|
||||
m_InstanceBatches.push_back(batch);
|
||||
}
|
||||
}
|
||||
if (instanceTruncated) {
|
||||
ENGINE_WARN("GPUDriven: instance buffer full ({}), entities truncated",
|
||||
kMaxInstances);
|
||||
}
|
||||
if (!m_InstanceModels.empty()) {
|
||||
m_ObjectBuffer->Update(frameSlot, m_InstanceModels.data(),
|
||||
m_InstanceModels.size() * sizeof(glm::mat4));
|
||||
}
|
||||
|
||||
// 2. acquire swapchain image
|
||||
u32 imageIndex = 0;
|
||||
try {
|
||||
|
||||
@@ -11,9 +11,10 @@ namespace Engine
|
||||
DescriptorManager::DescriptorManager(Device& device)
|
||||
: m_Device(device)
|
||||
{
|
||||
std::array<vk::DescriptorPoolSize, 2> poolSizes = {
|
||||
std::array<vk::DescriptorPoolSize, 3> poolSizes = {
|
||||
vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, kMaxSets),
|
||||
vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, kMaxSets),
|
||||
vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, kMaxSets),
|
||||
};
|
||||
|
||||
vk::DescriptorPoolCreateInfo poolInfo;
|
||||
@@ -76,6 +77,23 @@ namespace Engine
|
||||
m_Device.GetDevice().updateDescriptorSets(write, {});
|
||||
}
|
||||
|
||||
void DescriptorManager::UpdateStorageBuffer(vk::DescriptorSet set, u32 binding,
|
||||
vk::Buffer buffer, u64 size)
|
||||
{
|
||||
vk::DescriptorBufferInfo bufferInfo;
|
||||
bufferInfo.setBuffer(buffer).setOffset(0).setRange(size);
|
||||
|
||||
vk::WriteDescriptorSet write;
|
||||
write.setDstSet(set)
|
||||
.setDstBinding(binding)
|
||||
.setDstArrayElement(0)
|
||||
.setDescriptorType(vk::DescriptorType::eStorageBuffer)
|
||||
.setDescriptorCount(1)
|
||||
.setBufferInfo(bufferInfo);
|
||||
|
||||
m_Device.GetDevice().updateDescriptorSets(write, {});
|
||||
}
|
||||
|
||||
void DescriptorManager::UpdateTexture(vk::DescriptorSet set, u32 binding,
|
||||
vk::Sampler sampler, vk::ImageView view)
|
||||
{
|
||||
|
||||
@@ -10,15 +10,15 @@
|
||||
namespace Engine {
|
||||
|
||||
UniformBuffer::UniformBuffer(Device& device, MemoryAllocator& allocator,
|
||||
u64 size, u32 frameCount)
|
||||
u64 size, u32 frameCount,
|
||||
vk::BufferUsageFlags usage)
|
||||
: m_Size(size)
|
||||
{
|
||||
m_Buffers.reserve(frameCount);
|
||||
for (u32 i = 0; i < frameCount; ++i) {
|
||||
// Uniform Buffer 需要 CPU 每帧更新,所以用 HOST_VISIBLE | HOST_COHERENT
|
||||
// CPU 每帧更新,所以用 HOST_VISIBLE | HOST_COHERENT
|
||||
m_Buffers.push_back(CreateScope<Buffer>(
|
||||
device, allocator, size,
|
||||
vk::BufferUsageFlagBits::eUniformBuffer,
|
||||
device, allocator, size, usage,
|
||||
vk::MemoryPropertyFlagBits::eHostVisible |
|
||||
vk::MemoryPropertyFlagBits::eHostCoherent));
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
//
|
||||
// Created by atdunbg on 2026/9/20.
|
||||
//
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb/stb_image.h>
|
||||
@@ -16,9 +16,12 @@ layout(location = 1) out vec3 fragNormal;
|
||||
layout(location = 2) out vec3 fragWorldPos;
|
||||
layout(location = 3) out vec3 fragLightSpacePos;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
mat4 model;
|
||||
} pc;
|
||||
// GPUDriven:模型矩阵从实例缓冲按实例索引取。
|
||||
// 同一 mesh 的 N 个实体 = 1 次 drawIndexed(instanceCount=N),
|
||||
// draw 侧用 firstInstance 指向该 batch 在实例数组中的起始下标。
|
||||
layout(std430, set = 0, binding = 1) readonly buffer ObjectBuffer {
|
||||
mat4 models[]; // 连续 mat4 数组(16B 对齐,std430/std140 布局一致)
|
||||
} objects;
|
||||
|
||||
layout(set = 0, binding = 0) uniform UniformBufferObject {
|
||||
mat4 view;
|
||||
@@ -29,14 +32,16 @@ layout(set = 0, binding = 0) uniform UniformBufferObject {
|
||||
} ubo;
|
||||
|
||||
void main() {
|
||||
vec4 worldPos = pc.model * vec4(inPosition, 1.0);
|
||||
mat4 model = objects.models[gl_InstanceIndex];
|
||||
|
||||
vec4 worldPos = model * vec4(inPosition, 1.0);
|
||||
gl_Position = ubo.proj * ubo.view * worldPos;
|
||||
|
||||
fragWorldPos = worldPos.xyz;
|
||||
// 光空间坐标(含 Vulkan [0,1] 深度重映射,与 shadow pass 写入端共用同一矩阵)
|
||||
fragLightSpacePos = (ubo.lightSpace * worldPos).xyz;
|
||||
|
||||
mat3 normalMatrix = mat3(transpose(inverse(pc.model)));
|
||||
mat3 normalMatrix = mat3(transpose(inverse(model)));
|
||||
fragNormal = normalize(normalMatrix * inNormal);
|
||||
|
||||
fragUV = inUV;
|
||||
|
||||
Reference in New Issue
Block a user