feat: draw meshes with instanced multi-draw indirect

This commit is contained in:
2026-10-19 00:45:25 +02:00
parent 997844eaa7
commit 92c1daf396
19 changed files with 556 additions and 250 deletions
@@ -16,12 +16,24 @@ public:
MeshID addMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh);
const GPUMesh& getMesh(MeshID id) const;
[[nodiscard]] const std::vector<GPUMesh>& getMeshes() const { return meshes; }
const CPUMesh& getCPUMesh(MeshID id) const;
[[nodiscard]] const std::string& getMeshKey(MeshID id) const;
[[nodiscard]] std::optional<MeshID> findMeshByKey(std::string_view key) const;
[[nodiscard]] const GPUBuffer& getIndexBuffer() const { return indexBuffer; }
[[nodiscard]] std::uint32_t getIndexCount() const { return indexCount; }
[[nodiscard]] std::uint32_t getIndexCapacity() const { return indexCapacity; }
private:
void uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh& gpuMesh) const;
void uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh& gpuMesh);
void ensureIndexCapacity(GfxDevice& gfxDevice, std::uint32_t requiredIndices);
static constexpr std::uint32_t InitialIndexCapacity = 1u << 20;
GPUBuffer indexBuffer;
std::uint32_t indexCount{0};
std::uint32_t indexCapacity{0};
std::vector<GPUMesh> meshes;
std::vector<CPUMesh> cpuMeshes;
@@ -0,0 +1,80 @@
#ifndef INDIRECTDRAWLIST_H
#define INDIRECTDRAWLIST_H
#include <array>
#include <cstddef>
#include <cstdint>
#include <vector>
#include <glm/mat4x4.hpp>
#include <vulkan/vulkan.h>
#include <destrum/Graphics/FrameConfig.h>
#include <destrum/Graphics/Frustum.h>
#include <destrum/Graphics/MeshDrawCommand.h>
#include <destrum/Graphics/Resources/Buffer.h>
class GfxDevice;
struct GPUMesh;
// Must match InstanceData in instance_data.glsl
struct GPUInstanceData {
glm::mat4 transform;
VkDeviceAddress vertexBuffer;
std::uint32_t materialId;
std::uint32_t padding;
};
static_assert(sizeof(GPUInstanceData) == 80);
class IndirectDrawList {
public:
struct View {
std::uint32_t firstCommand{0};
std::uint32_t commandCount{0};
std::uint32_t instanceCount{0};
};
enum class Filter {
All,
ShadowCasters,
};
void cleanup(GfxDevice& gfxDevice);
void begin(std::uint32_t frameIndex);
View addView(
const std::vector<GPUMesh>& meshes,
const std::vector<MeshDrawCommand>& drawCommands,
const std::vector<std::size_t>& sortedDrawCommands,
const Frustum& frustum,
Filter filter);
void upload(GfxDevice& gfxDevice);
void draw(VkCommandBuffer cmd, const GPUBuffer& indexBuffer, const View& view) const;
[[nodiscard]] VkDeviceAddress getInstanceBufferAddress() const;
[[nodiscard]] const std::vector<GPUInstanceData>& getInstances() const { return instances; }
[[nodiscard]] const std::vector<VkDrawIndexedIndirectCommand>& getCommands() const { return commands; }
private:
struct PerFrameBuffers {
GPUBuffer instances;
GPUBuffer commands;
std::size_t instanceCapacity{0};
std::size_t commandCapacity{0};
};
static constexpr std::size_t InitialInstanceCapacity = 1024;
static constexpr std::size_t InitialCommandCapacity = 256;
std::array<PerFrameBuffers, FRAMES_IN_FLIGHT> frames{};
std::uint32_t currentFrame{0};
std::vector<GPUInstanceData> instances;
std::vector<VkDrawIndexedIndirectCommand> commands;
};
#endif // INDIRECTDRAWLIST_H
@@ -27,6 +27,8 @@ public:
VkPhysicalDeviceFeatures device{
.imageCubeArray = VK_TRUE,
.geometryShader = VK_TRUE,
.multiDrawIndirect = VK_TRUE,
.drawIndirectFirstInstance = VK_TRUE,
.depthClamp = VK_TRUE,
.fillModeNonSolid = VK_TRUE,
.samplerAnisotropy = VK_TRUE,
@@ -9,9 +9,8 @@
#include <vulkan/vulkan.h>
#include <destrum/Graphics/Pipeline.h>
#include <destrum/Graphics/Camera.h>
#include <destrum/Graphics/IndirectDrawList.h>
#include <destrum/Graphics/Resources/Buffer.h>
#include <destrum/Graphics/MeshDrawCommand.h>
class GfxDevice;
class RenderResources;
@@ -31,19 +30,17 @@ public:
VkCommandBuffer cmd,
VkExtent2D renderExtent,
const RenderResources& resources,
const Camera& camera,
const GPUBuffer& sceneDataBuffer,
const std::vector<MeshDrawCommand>& drawCommands,
const std::vector<std::size_t>& sortedDrawCommands);
const IndirectDrawList& drawList,
const IndirectDrawList::View& view);
void draw(
VkCommandBuffer cmd,
VkExtent2D renderExtent,
const RenderResources& resources,
const Camera& camera,
const GPUBuffer& sceneDataBuffer,
const std::vector<MeshDrawCommand>& drawCommands,
const std::vector<std::size_t>& sortedDrawCommands);
const IndirectDrawList& drawList,
const IndirectDrawList::View& view);
void cleanup(VkDevice device);
@@ -57,11 +54,8 @@ public:
private:
struct PushConstants {
glm::mat4 transform;
VkDeviceAddress sceneDataBuffer;
VkDeviceAddress vertexBuffer;
std::uint32_t materialId;
std::uint32_t padding;
VkDeviceAddress instanceBuffer;
};
void drawMeshes(
@@ -69,10 +63,9 @@ private:
const Pipeline& pipeline,
VkExtent2D renderExtent,
const RenderResources& resources,
const Camera& camera,
const GPUBuffer& sceneDataBuffer,
const std::vector<MeshDrawCommand>& drawCommands,
const std::vector<std::size_t>& sortedDrawCommands);
const IndirectDrawList& drawList,
const IndirectDrawList::View& view);
private:
VkPipelineLayout m_pipelineLayout{VK_NULL_HANDLE};
@@ -11,7 +11,7 @@
#include <destrum/Graphics/Light.h>
#include <destrum/Graphics/Pipeline.h>
#include <destrum/Graphics/ids.h>
#include <destrum/Graphics/MeshDrawCommand.h>
#include <destrum/Graphics/IndirectDrawList.h>
class GfxDevice;
struct GPUImage;
@@ -32,15 +32,15 @@ public:
void drawDirectionalShadow(
VkCommandBuffer cmd,
const RenderResources& resources,
const std::vector<MeshDrawCommand>& drawCommands,
const std::vector<std::size_t>& sortedDrawCommands,
const IndirectDrawList& drawList,
const IndirectDrawList::View& view,
const glm::mat4& lightViewProj);
void drawSpotShadows(
VkCommandBuffer cmd,
const RenderResources& resources,
const std::vector<MeshDrawCommand>& drawCommands,
const std::vector<std::size_t>& sortedDrawCommands,
const IndirectDrawList& drawList,
const std::vector<IndirectDrawList::View>& views,
const std::vector<glm::mat4>& lightViewProjs);
[[nodiscard]] glm::mat4 calculateDirectionalViewProj(const glm::vec3& lightDirection, const glm::vec3& center) const;
@@ -57,8 +57,8 @@ public:
private:
struct PushConstants
{
glm::mat4 modelToLightClip;
VkDeviceAddress vertexBuffer;
glm::mat4 lightViewProj;
VkDeviceAddress instanceBuffer;
};
void beginDepthRendering(VkCommandBuffer cmd, const RenderResources& resources, const GPUImage& depthImage);
@@ -67,8 +67,8 @@ private:
void drawShadowCasters(
VkCommandBuffer cmd,
const RenderResources& resources,
const std::vector<MeshDrawCommand>& drawCommands,
const std::vector<std::size_t>& sortedDrawCommands,
const IndirectDrawList& drawList,
const IndirectDrawList::View& view,
const glm::mat4& lightViewProj);
VkPipelineLayout m_pipelineLayout{VK_NULL_HANDLE};
@@ -5,6 +5,7 @@
#include <glm/vec3.hpp>
#include <destrum/Graphics/Camera.h>
#include <destrum/Graphics/IndirectDrawList.h>
#include <destrum/Graphics/Light.h>
#include <destrum/Graphics/Pipelines/LightCullingPipeline.h>
#include <destrum/Graphics/Pipelines/LineRenderingPass.h>
@@ -29,6 +30,15 @@
class GameRenderer
{
public:
struct DrawStats
{
std::uint32_t meshDraws{0};
std::uint32_t indirectCommands{0};
std::uint32_t instances{0};
std::uint32_t mainViewCommands{0};
std::uint32_t mainViewInstances{0};
};
struct SceneData
{
const Camera& camera;
@@ -106,6 +116,8 @@ public:
return resources;
}
[[nodiscard]] const DrawStats& getDrawStats() const { return lastDrawStats; }
void draw2D(
VkCommandBuffer cmd,
GfxDevice& gfxDevice,
@@ -125,6 +137,8 @@ private:
std::vector<MeshDrawCommand> meshDrawCommands;
std::vector<std::size_t> sortedMeshDrawCommands;
IndirectDrawList drawList;
DrawStats lastDrawStats;
VkFormat drawImageFormat{VK_FORMAT_R16G16B16A16_SFLOAT};
VkFormat depthImageFormat{VK_FORMAT_D32_SFLOAT};
@@ -20,10 +20,11 @@
struct GPUMesh {
GPUBuffer vertexBuffer;
GPUBuffer indexBuffer;
std::uint32_t numVertices{0};
std::uint32_t numIndices{0};
// offset into MeshCache::getIndexBuffer(), every mesh shares that one index buffer
std::uint32_t firstIndex{0};
// AABB
glm::vec3 minPos;