diff --git a/Engine/include/Engine/Renderer/Mesh.h b/Engine/include/Engine/Renderer/Mesh.h index ca97423..cb0e1df 100644 --- a/Engine/include/Engine/Renderer/Mesh.h +++ b/Engine/include/Engine/Renderer/Mesh.h @@ -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; } diff --git a/Engine/include/Engine/Renderer/Renderer.h b/Engine/include/Engine/Renderer/Renderer.h index f47531e..9499bab 100644 --- a/Engine/include/Engine/Renderer/Renderer.h +++ b/Engine/include/Engine/Renderer/Renderer.h @@ -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 m_SyncManager; Scope m_CommandManager; Scope m_UniformBuffer; + Scope m_ObjectBuffer; // GPUDriven 实例矩阵 ring(SSBO) Scope 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 m_InstanceBatches; + std::vector m_InstanceModels; + static constexpr u32 kMaxInstances = 4096; + // 可选叠加层(不拥有;Application 持有) ImGuiLayer* m_ImGuiLayer = nullptr; diff --git a/Engine/include/Engine/Vulkan/DescriptorManager.h b/Engine/include/Engine/Vulkan/DescriptorManager.h index d016476..d94e6df 100644 --- a/Engine/include/Engine/Vulkan/DescriptorManager.h +++ b/Engine/include/Engine/Vulkan/DescriptorManager.h @@ -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); diff --git a/Engine/include/Engine/Vulkan/UniformBuffer.h b/Engine/include/Engine/Vulkan/UniformBuffer.h index 45034d9..c3c89c9 100644 --- a/Engine/include/Engine/Vulkan/UniformBuffer.h +++ b/Engine/include/Engine/Vulkan/UniformBuffer.h @@ -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; diff --git a/Engine/src/Renderer/Mesh.cpp b/Engine/src/Renderer/Mesh.cpp index 633d1ab..c1e028b 100644 --- a/Engine/src/Renderer/Mesh.cpp +++ b/Engine/src/Renderer/Mesh.cpp @@ -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 diff --git a/Engine/src/Renderer/Renderer.cpp b/Engine/src/Renderer/Renderer.cpp index 9b49247..5f1a5e4 100644 --- a/Engine/src/Renderer/Renderer.cpp +++ b/Engine/src/Renderer/Renderer.cpp @@ -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(*m_Device); - // 4. Set 0: UBO + // 4. Set 0: UBO + GPUDriven 实例数据(SSBO,vertex 读取) std::vector 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(*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(*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( *m_Device, *m_Allocator, sizeof(UniformBufferObject), kMaxFramesInFlight); + m_ObjectBuffer = CreateScope( + *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( - [&](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()) { - cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, - m_PipelineLayout, kTextureSetIndex, - mesh->GetMaterial()->GetDescriptorSet(), {}); - mesh->Draw(cmd); - } - }); + // ---- 绘制: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, + 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 uniqueMeshes; + scene.Each( + [&](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(m_InstanceModels.size()), 0}; + scene.Each( + [&](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 { diff --git a/Engine/src/Vulkan/DescriptorManager.cpp b/Engine/src/Vulkan/DescriptorManager.cpp index a7005e8..bb15978 100644 --- a/Engine/src/Vulkan/DescriptorManager.cpp +++ b/Engine/src/Vulkan/DescriptorManager.cpp @@ -11,9 +11,10 @@ namespace Engine DescriptorManager::DescriptorManager(Device& device) : m_Device(device) { - std::array poolSizes = { + std::array 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) { diff --git a/Engine/src/Vulkan/UniformBuffer.cpp b/Engine/src/Vulkan/UniformBuffer.cpp index e8903ba..cbb2ccc 100644 --- a/Engine/src/Vulkan/UniformBuffer.cpp +++ b/Engine/src/Vulkan/UniformBuffer.cpp @@ -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( - device, allocator, size, - vk::BufferUsageFlagBits::eUniformBuffer, + device, allocator, size, usage, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent)); } diff --git a/Engine/src/stb_impl.cpp b/Engine/src/stb_impl.cpp deleted file mode 100644 index 3f66f88..0000000 --- a/Engine/src/stb_impl.cpp +++ /dev/null @@ -1,6 +0,0 @@ -// -// Created by atdunbg on 2026/9/20. -// - -#define STB_IMAGE_IMPLEMENTATION -#include diff --git a/shaders/simple_shader.vert b/shaders/simple_shader.vert index ab0cdf3..7d745f5 100644 --- a/shaders/simple_shader.vert +++ b/shaders/simple_shader.vert @@ -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;