diff --git a/destrum/CMakeLists.txt b/destrum/CMakeLists.txt index 40e93fd..c414c41 100644 --- a/destrum/CMakeLists.txt +++ b/destrum/CMakeLists.txt @@ -64,6 +64,7 @@ set(SRC_FILES "src/Graphics/GfxDevice.cpp" "src/Graphics/Caches/ImageCache.cpp" "src/Graphics/ImageLoader.cpp" + "src/Graphics/IndirectDrawList.cpp" "src/Graphics/ImmediateExecuter.cpp" "src/Graphics/Init.cpp" "src/Graphics/Caches/MaterialCache.cpp" @@ -232,6 +233,7 @@ if (SPIRV_VAL_EXECUTABLE) file(RELATIVE_PATH shader_relative "${ASSETS_SRC_DIR}" "${shader_source}") list(APPEND VALIDATE_SHADER_COMMANDS COMMAND "${SPIRV_VAL_EXECUTABLE}" + --target-env vulkan1.3 --scalar-block-layout "${ASSETS_RUNTIME_DIR}/${shader_relative}.spv") endforeach() diff --git a/destrum/assets_src/shaders/instance_data.glsl b/destrum/assets_src/shaders/instance_data.glsl new file mode 100644 index 0000000..fa51e92 --- /dev/null +++ b/destrum/assets_src/shaders/instance_data.glsl @@ -0,0 +1,21 @@ +#ifndef INSTANCE_DATA_GLSL +#define INSTANCE_DATA_GLSL + +#extension GL_EXT_buffer_reference : require +#extension GL_EXT_scalar_block_layout : require + +#include "vertex.glsl" + +// Must match GPUInstanceData in IndirectDrawList.h +struct InstanceData { + mat4 transform; + VertexBuffer vertexBuffer; + uint materialID; + uint padding; +}; + +layout (buffer_reference, scalar) readonly buffer InstanceBuffer { + InstanceData instances[]; +}; + +#endif // INSTANCE_DATA_GLSL diff --git a/destrum/assets_src/shaders/mesh.frag b/destrum/assets_src/shaders/mesh.frag index 27d306b..1497bb4 100644 --- a/destrum/assets_src/shaders/mesh.frag +++ b/destrum/assets_src/shaders/mesh.frag @@ -12,6 +12,7 @@ layout (location = 0) in vec3 inPos; layout (location = 1) in vec2 inUV; layout (location = 2) in vec3 inNormal; layout (location = 3) in vec4 inTangent; +layout (location = 4) flat in uint inMaterialID; layout (location = 0) out vec4 outFragColor; @@ -62,7 +63,7 @@ float calculateShadow(Light light, vec3 fragPos, vec3 geometryNormal) void main() { - MaterialData material = pcs.sceneData.materials.data[pcs.materialID]; + MaterialData material = pcs.sceneData.materials.data[inMaterialID]; uint samplerID = material.textureFilteringMode; vec4 diffuse = sampleTexture2D(material.diffuseTex, samplerID, inUV) * material.baseColor; diff --git a/destrum/assets_src/shaders/mesh.vert b/destrum/assets_src/shaders/mesh.vert index 0a4b25d..cbf1840 100644 --- a/destrum/assets_src/shaders/mesh.vert +++ b/destrum/assets_src/shaders/mesh.vert @@ -8,6 +8,7 @@ layout (location = 0) out vec3 outPos; layout (location = 1) out vec2 outUV; layout (location = 2) out vec3 outNormal; layout (location = 3) out vec4 outTangent; +layout (location = 4) flat out uint outMaterialID; // The depth pre-pass reuses this shader, positions have to match bit for bit // so the main pass can depth test against it with LESS_OR_EQUAL @@ -15,9 +16,11 @@ invariant gl_Position; void main() { - Vertex v = pcs.vertexBuffer.vertices[gl_VertexIndex]; + // gl_InstanceIndex already includes the indirect command's firstInstance + InstanceData instance = pcs.instanceBuffer.instances[gl_InstanceIndex]; + Vertex v = instance.vertexBuffer.vertices[gl_VertexIndex]; - vec4 worldPos = pcs.transform * vec4(v.position, 1.0f); + vec4 worldPos = instance.transform * vec4(v.position, 1.0f); gl_Position = pcs.sceneData.viewProj * worldPos; outPos = worldPos.xyz; @@ -25,9 +28,10 @@ void main() // A bit inefficient, but okay - this is needed for non-uniform scale // models. See: http://www.lighthouse3d.com/tutorials/glsl-12-tutorial/the-normal-matrix/ // Simpler case, when everything is uniform - // outNormal = (pcs.transform * vec4(v.normal, 0.0)).xyz; - outNormal = mat3(transpose(inverse(pcs.transform))) * v.normal; + // outNormal = (instance.transform * vec4(v.normal, 0.0)).xyz; + outNormal = mat3(transpose(inverse(instance.transform))) * v.normal; // w is the bitangent sign, the fragment shader builds the TBN from the interpolated vectors - outTangent = vec4(mat3(pcs.transform) * v.tangent.xyz, v.tangent.w); + outTangent = vec4(mat3(instance.transform) * v.tangent.xyz, v.tangent.w); + outMaterialID = instance.materialID; } diff --git a/destrum/assets_src/shaders/mesh_pcs.glsl b/destrum/assets_src/shaders/mesh_pcs.glsl index bba8c1b..bdef682 100644 --- a/destrum/assets_src/shaders/mesh_pcs.glsl +++ b/destrum/assets_src/shaders/mesh_pcs.glsl @@ -2,14 +2,10 @@ #extension GL_EXT_scalar_block_layout: require #include "scene_data.glsl" -#include "vertex.glsl" +#include "instance_data.glsl" layout (push_constant, scalar) uniform constants { - mat4 transform; SceneDataBuffer sceneData; - VertexBuffer vertexBuffer; - uint materialID; - uint padding; + InstanceBuffer instanceBuffer; } pcs; - diff --git a/destrum/assets_src/shaders/shadow.vert b/destrum/assets_src/shaders/shadow.vert index 132454f..96fa57f 100644 --- a/destrum/assets_src/shaders/shadow.vert +++ b/destrum/assets_src/shaders/shadow.vert @@ -4,17 +4,18 @@ #extension GL_EXT_buffer_reference : require #extension GL_EXT_scalar_block_layout : require -#include "vertex.glsl" +#include "instance_data.glsl" layout (push_constant, scalar) uniform constants { - mat4 modelToLightClip; - VertexBuffer vertexBuffer; + mat4 lightViewProj; + InstanceBuffer instanceBuffer; } pcs; void main() { - Vertex v = pcs.vertexBuffer.vertices[gl_VertexIndex]; + InstanceData instance = pcs.instanceBuffer.instances[gl_InstanceIndex]; + Vertex v = instance.vertexBuffer.vertices[gl_VertexIndex]; - gl_Position = pcs.modelToLightClip * vec4(v.position, 1.0f); + gl_Position = pcs.lightViewProj * instance.transform * vec4(v.position, 1.0f); } diff --git a/destrum/include/destrum/Graphics/Caches/MeshCache.h b/destrum/include/destrum/Graphics/Caches/MeshCache.h index 48b7fc2..b6cdfe8 100644 --- a/destrum/include/destrum/Graphics/Caches/MeshCache.h +++ b/destrum/include/destrum/Graphics/Caches/MeshCache.h @@ -16,12 +16,24 @@ public: MeshID addMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh); const GPUMesh& getMesh(MeshID id) const; + [[nodiscard]] const std::vector& getMeshes() const { return meshes; } const CPUMesh& getCPUMesh(MeshID id) const; [[nodiscard]] const std::string& getMeshKey(MeshID id) const; [[nodiscard]] std::optional 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 meshes; std::vector cpuMeshes; diff --git a/destrum/include/destrum/Graphics/IndirectDrawList.h b/destrum/include/destrum/Graphics/IndirectDrawList.h new file mode 100644 index 0000000..308ef33 --- /dev/null +++ b/destrum/include/destrum/Graphics/IndirectDrawList.h @@ -0,0 +1,80 @@ +#ifndef INDIRECTDRAWLIST_H +#define INDIRECTDRAWLIST_H + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +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& meshes, + const std::vector& drawCommands, + const std::vector& 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& getInstances() const { return instances; } + [[nodiscard]] const std::vector& 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 frames{}; + std::uint32_t currentFrame{0}; + + std::vector instances; + std::vector commands; +}; + +#endif // INDIRECTDRAWLIST_H diff --git a/destrum/include/destrum/Graphics/Managers/VulkanInstanceManager.h b/destrum/include/destrum/Graphics/Managers/VulkanInstanceManager.h index 96c67c0..e0f5d78 100644 --- a/destrum/include/destrum/Graphics/Managers/VulkanInstanceManager.h +++ b/destrum/include/destrum/Graphics/Managers/VulkanInstanceManager.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, diff --git a/destrum/include/destrum/Graphics/Pipelines/MeshPipeline.h b/destrum/include/destrum/Graphics/Pipelines/MeshPipeline.h index 7feec65..d71e7db 100644 --- a/destrum/include/destrum/Graphics/Pipelines/MeshPipeline.h +++ b/destrum/include/destrum/Graphics/Pipelines/MeshPipeline.h @@ -9,9 +9,8 @@ #include #include -#include +#include #include -#include 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& drawCommands, - const std::vector& 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& drawCommands, - const std::vector& 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& drawCommands, - const std::vector& sortedDrawCommands); + const IndirectDrawList& drawList, + const IndirectDrawList::View& view); private: VkPipelineLayout m_pipelineLayout{VK_NULL_HANDLE}; diff --git a/destrum/include/destrum/Graphics/Pipelines/ShadowMapPipeline.h b/destrum/include/destrum/Graphics/Pipelines/ShadowMapPipeline.h index 4949e32..ae62bc4 100644 --- a/destrum/include/destrum/Graphics/Pipelines/ShadowMapPipeline.h +++ b/destrum/include/destrum/Graphics/Pipelines/ShadowMapPipeline.h @@ -11,7 +11,7 @@ #include #include #include -#include +#include class GfxDevice; struct GPUImage; @@ -32,15 +32,15 @@ public: void drawDirectionalShadow( VkCommandBuffer cmd, const RenderResources& resources, - const std::vector& drawCommands, - const std::vector& sortedDrawCommands, + const IndirectDrawList& drawList, + const IndirectDrawList::View& view, const glm::mat4& lightViewProj); void drawSpotShadows( VkCommandBuffer cmd, const RenderResources& resources, - const std::vector& drawCommands, - const std::vector& sortedDrawCommands, + const IndirectDrawList& drawList, + const std::vector& views, const std::vector& 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& drawCommands, - const std::vector& sortedDrawCommands, + const IndirectDrawList& drawList, + const IndirectDrawList::View& view, const glm::mat4& lightViewProj); VkPipelineLayout m_pipelineLayout{VK_NULL_HANDLE}; diff --git a/destrum/include/destrum/Graphics/Renderer.h b/destrum/include/destrum/Graphics/Renderer.h index 4f35216..f154522 100644 --- a/destrum/include/destrum/Graphics/Renderer.h +++ b/destrum/include/destrum/Graphics/Renderer.h @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -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 meshDrawCommands; std::vector sortedMeshDrawCommands; + IndirectDrawList drawList; + DrawStats lastDrawStats; VkFormat drawImageFormat{VK_FORMAT_R16G16B16A16_SFLOAT}; VkFormat depthImageFormat{VK_FORMAT_D32_SFLOAT}; diff --git a/destrum/include/destrum/Graphics/Resources/Mesh.h b/destrum/include/destrum/Graphics/Resources/Mesh.h index d4ddbdc..b3cac83 100644 --- a/destrum/include/destrum/Graphics/Resources/Mesh.h +++ b/destrum/include/destrum/Graphics/Resources/Mesh.h @@ -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; diff --git a/destrum/src/App.cpp b/destrum/src/App.cpp index b5e6a30..b297321 100644 --- a/destrum/src/App.cpp +++ b/destrum/src/App.cpp @@ -208,6 +208,13 @@ if (!isRunning) break; if (ImGui::Begin("Debug", &m_DebugWindows.stats)) { ImGui::Text("FPS: %.2f", avgFPS); + + const auto& stats = renderer.getDrawStats(); + ImGui::SeparatorText("Meshes"); + ImGui::Text("Submitted meshes: %u", stats.meshDraws); + ImGui::Text("Camera: %u indirect draws, %u instances", stats.mainViewCommands, stats.mainViewInstances); + ImGui::Text("All views: %u indirect draws, %u instances", stats.indirectCommands, stats.instances); + ImGui::Text("Pending GPU deletions: %zu", gfxDevice.getPendingDeletionCount()); } ImGui::End(); } diff --git a/destrum/src/Graphics/IndirectDrawList.cpp b/destrum/src/Graphics/IndirectDrawList.cpp new file mode 100644 index 0000000..b43532a --- /dev/null +++ b/destrum/src/Graphics/IndirectDrawList.cpp @@ -0,0 +1,175 @@ +#include + +#include +#include +#include + +#include + +#include + +#include +#include +#include + +namespace { + std::size_t grownCapacity(std::size_t current, std::size_t initial, std::size_t required) + { + std::size_t capacity = std::max(current, initial); + while (capacity < required) { + capacity *= 2; + } + return capacity; + } +} + +void IndirectDrawList::cleanup(GfxDevice& gfxDevice) +{ + for (auto& frame : frames) { + gfxDevice.destroyBuffer(frame.instances); + gfxDevice.destroyBuffer(frame.commands); + frame.instanceCapacity = 0; + frame.commandCapacity = 0; + } + instances.clear(); + commands.clear(); +} + +void IndirectDrawList::begin(std::uint32_t frameIndex) +{ + assert(frameIndex < FRAMES_IN_FLIGHT); + currentFrame = frameIndex; + instances.clear(); + commands.clear(); +} + +IndirectDrawList::View IndirectDrawList::addView( + const std::vector& meshes, + const std::vector& drawCommands, + const std::vector& sortedDrawCommands, + const Frustum& frustum, + Filter filter) +{ + ZoneScopedN("IndirectDrawList::addView"); + + View view{ + .firstCommand = static_cast(commands.size()), + }; + MeshID batchMesh = NULL_MESH_ID; + + const auto addDraw = [&](const MeshDrawCommand& dc) + { + if (filter == Filter::ShadowCasters && !dc.castShadow) { + return; + } + if (!edge::isInFrustum(frustum, dc.worldBoundingSphere)) { + return; + } + + assert(dc.meshId < meshes.size()); + assert(dc.materialId != NULL_MATERIAL_ID); + const GPUMesh& mesh = meshes[dc.meshId]; + + // Draws arrive sorted by mesh, so every run of the same mesh becomes one + // instanced command. Each instance carries its own transform, material and + // vertex buffer (skinned meshes point at their own skinned copy). + if (dc.meshId != batchMesh) { + batchMesh = dc.meshId; + commands.push_back(VkDrawIndexedIndirectCommand{ + .indexCount = mesh.numIndices, + .instanceCount = 0, + .firstIndex = mesh.firstIndex, + .vertexOffset = 0, + .firstInstance = static_cast(instances.size()), + }); + ++view.commandCount; + } + + instances.push_back(GPUInstanceData{ + .transform = dc.transformMatrix, + .vertexBuffer = dc.skinnedMesh != nullptr + ? dc.skinnedMesh->skinnedVertexBuffer.address + : mesh.vertexBuffer.address, + .materialId = dc.materialId, + .padding = 0, + }); + ++commands.back().instanceCount; + ++view.instanceCount; + }; + + if (!sortedDrawCommands.empty()) { + for (std::size_t drawIndex : sortedDrawCommands) { + assert(drawIndex < drawCommands.size()); + addDraw(drawCommands[drawIndex]); + } + } else { + for (const MeshDrawCommand& dc : drawCommands) { + addDraw(dc); + } + } + + return view; +} + +void IndirectDrawList::upload(GfxDevice& gfxDevice) +{ + ZoneScopedN("IndirectDrawList::upload"); + + PerFrameBuffers& frame = frames[currentFrame]; + + // Growing is cheap thanks to the deletion queue: the old buffer may still be + // read by this slot's previous frame, so it is retired instead of destroyed. + if (instances.size() > frame.instanceCapacity || frame.instances.buffer == VK_NULL_HANDLE) { + gfxDevice.destroyBuffer(frame.instances); + frame.instanceCapacity = grownCapacity(frame.instanceCapacity, InitialInstanceCapacity, instances.size()); + frame.instances = gfxDevice.createBuffer( + frame.instanceCapacity * sizeof(GPUInstanceData), + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, + VMA_MEMORY_USAGE_CPU_TO_GPU); + vkutil::addDebugLabel(gfxDevice.getDevice(), frame.instances.buffer, "mesh instances"); + } + + if (commands.size() > frame.commandCapacity || frame.commands.buffer == VK_NULL_HANDLE) { + gfxDevice.destroyBuffer(frame.commands); + frame.commandCapacity = grownCapacity(frame.commandCapacity, InitialCommandCapacity, commands.size()); + frame.commands = gfxDevice.createBuffer( + frame.commandCapacity * sizeof(VkDrawIndexedIndirectCommand), + VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, + VMA_MEMORY_USAGE_CPU_TO_GPU); + vkutil::addDebugLabel(gfxDevice.getDevice(), frame.commands.buffer, "mesh indirect commands"); + } + + // Each frame in flight has its own buffers, so nothing on the GPU is reading + // these while we write. vkQueueSubmit makes the host writes visible to the GPU. + if (!instances.empty()) { + std::memcpy(frame.instances.info.pMappedData, instances.data(), instances.size() * sizeof(GPUInstanceData)); + gfxDevice.getMemoryManager().flushAllocation(frame.instances, 0, instances.size() * sizeof(GPUInstanceData)); + } + if (!commands.empty()) { + std::memcpy(frame.commands.info.pMappedData, commands.data(), commands.size() * sizeof(VkDrawIndexedIndirectCommand)); + gfxDevice.getMemoryManager().flushAllocation(frame.commands, 0, commands.size() * sizeof(VkDrawIndexedIndirectCommand)); + } +} + +void IndirectDrawList::draw(VkCommandBuffer cmd, const GPUBuffer& indexBuffer, const View& view) const +{ + if (view.commandCount == 0) { + return; + } + + const PerFrameBuffers& frame = frames[currentFrame]; + assert(frame.commands.buffer != VK_NULL_HANDLE && "IndirectDrawList::upload must run before draw"); + + vkCmdBindIndexBuffer(cmd, indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); + vkCmdDrawIndexedIndirect( + cmd, + frame.commands.buffer, + static_cast(view.firstCommand) * sizeof(VkDrawIndexedIndirectCommand), + view.commandCount, + sizeof(VkDrawIndexedIndirectCommand)); +} + +VkDeviceAddress IndirectDrawList::getInstanceBufferAddress() const +{ + return frames[currentFrame].instances.address; +} diff --git a/destrum/src/Graphics/MeshCache.cpp b/destrum/src/Graphics/MeshCache.cpp index d558b2a..6bfa8fb 100644 --- a/destrum/src/Graphics/MeshCache.cpp +++ b/destrum/src/Graphics/MeshCache.cpp @@ -49,15 +49,73 @@ MeshID MeshCache::addMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh) return id; } -void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh& gpuMesh) const +void MeshCache::ensureIndexCapacity(GfxDevice& gfxDevice, std::uint32_t requiredIndices) +{ + if (requiredIndices <= indexCapacity) { + return; + } + + std::uint32_t newCapacity = std::max(indexCapacity, InitialIndexCapacity); + while (newCapacity < requiredIndices) { + newCapacity *= 2; + } + + GPUBuffer newBuffer = gfxDevice.createBuffer( + static_cast(newCapacity) * sizeof(std::uint32_t), + VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE); + vkutil::addDebugLabel(gfxDevice.getDevice(), newBuffer.buffer, "mesh indices"); + + if (indexCount > 0) { + try { + gfxDevice.immediateSubmit([&](VkCommandBuffer cmd) { + const VkBufferMemoryBarrier2 previousCopies{ + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2, + .srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT, + .srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT, + .dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT, + .dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .buffer = indexBuffer.buffer, + .offset = 0, + .size = VK_WHOLE_SIZE, + }; + const VkDependencyInfo dependency{ + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .bufferMemoryBarrierCount = 1, + .pBufferMemoryBarriers = &previousCopies, + }; + vkCmdPipelineBarrier2(cmd, &dependency); + + const VkBufferCopy copy{ + .srcOffset = 0, + .dstOffset = 0, + .size = static_cast(indexCount) * sizeof(std::uint32_t), + }; + vkCmdCopyBuffer(cmd, indexBuffer.buffer, newBuffer.buffer, 1, ©); + }); + } catch (...) { + gfxDevice.destroyBufferNow(newBuffer); + throw; + } + } + + // frames in flight were recorded with the old buffer, the deletion queue keeps it alive for them + gfxDevice.destroyBuffer(indexBuffer); + indexBuffer = newBuffer; + indexCapacity = newCapacity; +} + +void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh& gpuMesh) { try { - // create index buffer + const auto numIndices = static_cast(cpuMesh.indices.size()); + ensureIndexCapacity(gfxDevice, indexCount + numIndices); + gpuMesh.firstIndex = indexCount; + const auto indexBufferSize = cpuMesh.indices.size() * sizeof(std::uint32_t); - gpuMesh.indexBuffer = gfxDevice.createBuffer( - indexBufferSize, - VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE); + const auto indexBufferOffset = static_cast(gpuMesh.firstIndex) * sizeof(std::uint32_t); // create vertex buffer const auto vertexBufferSize = cpuMesh.vertices.size() * sizeof(CPUMesh::Vertex); @@ -81,6 +139,8 @@ void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh memcpy(static_cast(vertexIndexData) + vertexBufferSize, cpuMesh.indices.data(), indexBufferSize); gfxDevice.getMemoryManager().flushAllocation(vertexIndexStaging); + // Writing into the shared index buffer is safe while frames are in flight: + // this range is new, they only read the ranges of meshes added before it. gfxDevice.immediateSubmit([&](VkCommandBuffer cmd) { vkutil::bufferHostWriteToTransferReadBarrier( cmd, @@ -97,17 +157,17 @@ void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh const auto indexCopy = VkBufferCopy{ .srcOffset = vertexBufferSize, - .dstOffset = 0, + .dstOffset = indexBufferOffset, .size = indexBufferSize, }; - vkCmdCopyBuffer(cmd, vertexIndexStaging.buffer, gpuMesh.indexBuffer.buffer, 1, &indexCopy); + vkCmdCopyBuffer(cmd, vertexIndexStaging.buffer, indexBuffer.buffer, 1, &indexCopy); const std::array destinationBarriers{{ { .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2, .srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT, .srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT, - .dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT, + .dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, .dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, @@ -119,13 +179,13 @@ void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2, .srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT, .srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT, - .dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_INPUT_BIT, - .dstAccessMask = VK_ACCESS_2_INDEX_READ_BIT, + .dstStageMask = VK_PIPELINE_STAGE_2_INDEX_INPUT_BIT | VK_PIPELINE_STAGE_2_COPY_BIT, + .dstAccessMask = VK_ACCESS_2_INDEX_READ_BIT | VK_ACCESS_2_TRANSFER_READ_BIT, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .buffer = gpuMesh.indexBuffer.buffer, - .offset = 0, - .size = VK_WHOLE_SIZE, + .buffer = indexBuffer.buffer, + .offset = indexBufferOffset, + .size = indexBufferSize, } }}; const VkDependencyInfo destinationDependency{ @@ -141,11 +201,10 @@ void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh } gfxDevice.destroyBufferNow(vertexIndexStaging); + indexCount += numIndices; const auto vtxBufferName = cpuMesh.name + " (vtx)"; - const auto idxBufferName = cpuMesh.name + " (idx)"; vkutil::addDebugLabel(gfxDevice.getDevice(), gpuMesh.vertexBuffer.buffer, vtxBufferName.c_str()); - vkutil::addDebugLabel(gfxDevice.getDevice(), gpuMesh.indexBuffer.buffer, idxBufferName.c_str()); if (gpuMesh.hasSkeleton) { // create skinning data buffer @@ -214,7 +273,6 @@ void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh } catch (...) { gfxDevice.destroyBufferNow(gpuMesh.skinningDataBuffer); gfxDevice.destroyBufferNow(gpuMesh.vertexBuffer); - gfxDevice.destroyBufferNow(gpuMesh.indexBuffer); gpuMesh = {}; throw; } @@ -246,8 +304,11 @@ std::optional MeshCache::findMeshByKey(std::string_view key) const void MeshCache::cleanup(GfxDevice& gfxDevice) { + gfxDevice.destroyBuffer(indexBuffer); + indexCount = 0; + indexCapacity = 0; + for (auto& mesh : meshes) { - gfxDevice.destroyBuffer(mesh.indexBuffer); gfxDevice.destroyBuffer(mesh.vertexBuffer); gfxDevice.destroyBuffer(mesh.skinningDataBuffer); } diff --git a/destrum/src/Graphics/Pipelines/MeshPipeline.cpp b/destrum/src/Graphics/Pipelines/MeshPipeline.cpp index 3455db7..b4985b7 100644 --- a/destrum/src/Graphics/Pipelines/MeshPipeline.cpp +++ b/destrum/src/Graphics/Pipelines/MeshPipeline.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include "volk.h" @@ -119,10 +118,9 @@ void MeshPipeline::drawDepthPrepass( VkCommandBuffer cmd, VkExtent2D renderExtent, const RenderResources& resources, - const Camera& camera, const GPUBuffer& sceneDataBuffer, - const std::vector& drawCommands, - const std::vector& sortedDrawCommands) + const IndirectDrawList& drawList, + const IndirectDrawList::View& view) { if (!m_depthPrepassPipeline) { @@ -134,20 +132,18 @@ void MeshPipeline::drawDepthPrepass( *m_depthPrepassPipeline, renderExtent, resources, - camera, sceneDataBuffer, - drawCommands, - sortedDrawCommands); + drawList, + view); } void MeshPipeline::draw( VkCommandBuffer cmd, VkExtent2D renderExtent, const RenderResources& resources, - const Camera& camera, const GPUBuffer& sceneDataBuffer, - const std::vector& drawCommands, - const std::vector& sortedDrawCommands) + const IndirectDrawList& drawList, + const IndirectDrawList::View& view) { if (!m_pipeline) { @@ -159,10 +155,9 @@ void MeshPipeline::draw( *m_pipeline, renderExtent, resources, - camera, sceneDataBuffer, - drawCommands, - sortedDrawCommands); + drawList, + view); } void MeshPipeline::drawMeshes( @@ -170,13 +165,10 @@ void MeshPipeline::drawMeshes( const Pipeline& pipeline, VkExtent2D renderExtent, const RenderResources& resources, - const Camera& camera, const GPUBuffer& sceneDataBuffer, - const std::vector& drawCommands, - const std::vector& sortedDrawCommands) + const IndirectDrawList& drawList, + const IndirectDrawList::View& view) { - const MeshCache& meshCache = resources.meshes(); - pipeline.bind(cmd); resources.bindBindlessDescSet(cmd, m_pipelineLayout); @@ -204,86 +196,32 @@ void MeshPipeline::drawMeshes( m_renderWireframe ? VK_POLYGON_MODE_LINE : VK_POLYGON_MODE_FILL ); - MeshID prevMeshId = NULL_MESH_ID; - int actualDrawCalls = 0; - - const auto frustum = edge::createFrustumFromCamera(camera); - - const auto drawOne = [&](const MeshDrawCommand& dc) - { - if (!edge::isInFrustum(frustum, dc.worldBoundingSphere)) - { - return; - } - - ++actualDrawCalls; - - const auto& mesh = meshCache.getMesh(dc.meshId); - - const std::string& meshKey = meshCache.getMeshKey(dc.meshId); - vkutil::cmdBeginLabelFormatted( - cmd, - vkutil::labelColors::draw, - "%s [mesh %zu, material %u%s]", - meshKey.empty() ? "Mesh" : meshKey.c_str(), - dc.meshId, - dc.materialId, - dc.skinnedMesh != nullptr ? ", skinned" : ""); - - if (dc.meshId != prevMeshId) - { - prevMeshId = dc.meshId; - - vkCmdBindIndexBuffer( - cmd, - mesh.indexBuffer.buffer, - 0, - VK_INDEX_TYPE_UINT32 - ); - } - - assert(dc.materialId != NULL_MATERIAL_ID); - - const auto pushConstants = PushConstants{ - .transform = dc.transformMatrix, - .sceneDataBuffer = sceneDataBuffer.address, - .vertexBuffer = - dc.skinnedMesh != nullptr - ? dc.skinnedMesh->skinnedVertexBuffer.address - : mesh.vertexBuffer.address, - .materialId = dc.materialId, - .padding = 0, - }; - - vkCmdPushConstants( - cmd, - m_pipelineLayout, - VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, - 0, - sizeof(PushConstants), - &pushConstants - ); - - vkCmdDrawIndexed(cmd, mesh.numIndices, 1, 0, 0, 0); - - vkutil::cmdEndLabel(cmd); + // The same for every draw, per-draw data (transform, material, vertex buffer) + // lives in the instance buffer and is looked up with gl_InstanceIndex + const auto pushConstants = PushConstants{ + .sceneDataBuffer = sceneDataBuffer.address, + .instanceBuffer = drawList.getInstanceBufferAddress(), }; - if (!sortedDrawCommands.empty()) - { - for (std::size_t drawIndex : sortedDrawCommands) - { - assert(drawIndex < drawCommands.size()); - drawOne(drawCommands[drawIndex]); - } - } - else - { - for (const MeshDrawCommand& dc : drawCommands) - { - drawOne(dc); - } - } + vkCmdPushConstants( + cmd, + m_pipelineLayout, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, + 0, + sizeof(PushConstants), + &pushConstants + ); + + vkutil::cmdBeginLabelFormatted( + cmd, + vkutil::labelColors::draw, + "%u indirect draws, %u instances", + view.commandCount, + view.instanceCount); + + drawList.draw(cmd, resources.meshes().getIndexBuffer(), view); + + vkutil::cmdEndLabel(cmd); } void MeshPipeline::cleanup(VkDevice device) diff --git a/destrum/src/Graphics/Pipelines/ShadowMapPipeline.cpp b/destrum/src/Graphics/Pipelines/ShadowMapPipeline.cpp index bf66af9..f2bed53 100644 --- a/destrum/src/Graphics/Pipelines/ShadowMapPipeline.cpp +++ b/destrum/src/Graphics/Pipelines/ShadowMapPipeline.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include "volk.h" @@ -206,8 +205,8 @@ float ShadowMapPipeline::getNormalOffset() const void ShadowMapPipeline::drawDirectionalShadow( VkCommandBuffer cmd, const RenderResources& resources, - const std::vector& drawCommands, - const std::vector& sortedDrawCommands, + const IndirectDrawList& drawList, + const IndirectDrawList::View& view, const glm::mat4& lightViewProj) { if (!m_pipeline || shadowMapId == NULL_IMAGE_ID) @@ -219,15 +218,15 @@ void ShadowMapPipeline::drawDirectionalShadow( beginDepthRendering(cmd, resources, shadowMap); setRenderRegion(cmd, VkRect2D{.offset = {}, .extent = shadowMap.getExtent2D()}); - drawShadowCasters(cmd, resources, drawCommands, sortedDrawCommands, lightViewProj); + drawShadowCasters(cmd, resources, drawList, view, lightViewProj); endDepthRendering(cmd, shadowMap); } void ShadowMapPipeline::drawSpotShadows( VkCommandBuffer cmd, const RenderResources& resources, - const std::vector& drawCommands, - const std::vector& sortedDrawCommands, + const IndirectDrawList& drawList, + const std::vector& views, const std::vector& lightViewProjs) { if (!m_pipeline || spotShadowAtlasId == NULL_IMAGE_ID || lightViewProjs.empty()) @@ -236,6 +235,7 @@ void ShadowMapPipeline::drawSpotShadows( } assert(lightViewProjs.size() <= MAX_SPOT_SHADOWS); + assert(views.size() == lightViewProjs.size()); const auto& spotShadowAtlas = resources.getImage(spotShadowAtlasId); beginDepthRendering(cmd, resources, spotShadowAtlas); @@ -244,7 +244,7 @@ void ShadowMapPipeline::drawSpotShadows( vkutil::cmdBeginLabelFormatted(cmd, vkutil::labelColors::shadow, "Spot shadow %u", shadowIndex); setRenderRegion(cmd, spotShadowTileRegion(shadowIndex)); - drawShadowCasters(cmd, resources, drawCommands, sortedDrawCommands, lightViewProjs[shadowIndex]); + drawShadowCasters(cmd, resources, drawList, views[shadowIndex], lightViewProjs[shadowIndex]); vkutil::cmdEndLabel(cmd); } @@ -306,73 +306,31 @@ void ShadowMapPipeline::setRenderRegion(VkCommandBuffer cmd, const VkRect2D& reg void ShadowMapPipeline::drawShadowCasters( VkCommandBuffer cmd, const RenderResources& resources, - const std::vector& drawCommands, - const std::vector& sortedDrawCommands, + const IndirectDrawList& drawList, + const IndirectDrawList::View& view, const glm::mat4& lightViewProj) { - const MeshCache& meshCache = resources.meshes(); - const Frustum lightFrustum = edge::createFrustumFromViewProj(lightViewProj); - MeshID previousMeshId = NULL_MESH_ID; - - const auto drawCaster = [&](const MeshDrawCommand& dc) - { - if (!dc.castShadow || !edge::isInFrustum(lightFrustum, dc.worldBoundingSphere)) - { - return; - } - - const auto& mesh = meshCache.getMesh(dc.meshId); - - const std::string& meshKey = meshCache.getMeshKey(dc.meshId); - vkutil::cmdBeginLabelFormatted( - cmd, - vkutil::labelColors::draw, - "%s [mesh %zu, material %u%s]", - meshKey.empty() ? "Mesh" : meshKey.c_str(), - dc.meshId, - dc.materialId, - dc.skinnedMesh != nullptr ? ", skinned" : ""); - - if (dc.meshId != previousMeshId) - { - previousMeshId = dc.meshId; - vkCmdBindIndexBuffer(cmd, mesh.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); - } - - const auto pushConstants = PushConstants{ - .modelToLightClip = lightViewProj * dc.transformMatrix, - .vertexBuffer = - dc.skinnedMesh != nullptr - ? dc.skinnedMesh->skinnedVertexBuffer.address - : mesh.vertexBuffer.address, - }; - - vkCmdPushConstants( - cmd, - m_pipelineLayout, - VK_SHADER_STAGE_VERTEX_BIT, - 0, - sizeof(PushConstants), - &pushConstants); - - vkCmdDrawIndexed(cmd, mesh.numIndices, 1, 0, 0, 0); - - vkutil::cmdEndLabel(cmd); + const auto pushConstants = PushConstants{ + .lightViewProj = lightViewProj, + .instanceBuffer = drawList.getInstanceBufferAddress(), }; - if (!sortedDrawCommands.empty()) - { - for (std::size_t drawIndex : sortedDrawCommands) - { - assert(drawIndex < drawCommands.size()); - drawCaster(drawCommands[drawIndex]); - } - } - else - { - for (const MeshDrawCommand& dc : drawCommands) - { - drawCaster(dc); - } - } + vkCmdPushConstants( + cmd, + m_pipelineLayout, + VK_SHADER_STAGE_VERTEX_BIT, + 0, + sizeof(PushConstants), + &pushConstants); + + vkutil::cmdBeginLabelFormatted( + cmd, + vkutil::labelColors::draw, + "%u indirect draws, %u instances", + view.commandCount, + view.instanceCount); + + drawList.draw(cmd, resources.meshes().getIndexBuffer(), view); + + vkutil::cmdEndLabel(cmd); } diff --git a/destrum/src/Graphics/Renderer.cpp b/destrum/src/Graphics/Renderer.cpp index c2699ae..4818006 100644 --- a/destrum/src/Graphics/Renderer.cpp +++ b/destrum/src/Graphics/Renderer.cpp @@ -79,22 +79,14 @@ void GameRenderer::endDrawing() sortedMeshDrawCommands.resize(meshDrawCommands.size()); std::iota(sortedMeshDrawCommands.begin(), sortedMeshDrawCommands.end(), 0); + // Grouping by mesh lets IndirectDrawList merge each run into one instanced draw. + // Material and vertex buffer are per instance, so they don't break a batch. std::stable_sort( sortedMeshDrawCommands.begin(), sortedMeshDrawCommands.end(), [this](std::size_t left, std::size_t right) { - const auto& lhs = meshDrawCommands[left]; - const auto& rhs = meshDrawCommands[right]; - if (lhs.meshId != rhs.meshId) - { - return lhs.meshId < rhs.meshId; - } - if (lhs.materialId != rhs.materialId) - { - return lhs.materialId < rhs.materialId; - } - return (lhs.skinnedMesh != nullptr) < (rhs.skinnedMesh != nullptr); + return meshDrawCommands[left].meshId < meshDrawCommands[right].meshId; }); } @@ -147,6 +139,55 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera& spotShadowViewProjs.push_back(spotShadow.lightViewProj); } + // Culling stays on the CPU: every view (camera, directional light, each spot light) + // gets its own range of indirect commands in this frame's draw list. + IndirectDrawList::View mainView; + IndirectDrawList::View directionalShadowView; + std::vector spotShadowViews; + { + ZoneScopedN("Build Indirect Draws"); + + const auto& meshes = resources->meshes().getMeshes(); + drawList.begin(gfxDevice.getCurrentFrameIndex()); + + mainView = drawList.addView( + meshes, + meshDrawCommands, + sortedMeshDrawCommands, + edge::createFrustumFromCamera(camera), + IndirectDrawList::Filter::All); + + if (shadowLightDirection) + { + directionalShadowView = drawList.addView( + meshes, + meshDrawCommands, + sortedMeshDrawCommands, + edge::createFrustumFromViewProj(lightViewProj), + IndirectDrawList::Filter::ShadowCasters); + } + + spotShadowViews.reserve(spotShadowViewProjs.size()); + for (const glm::mat4& spotViewProj : spotShadowViewProjs) + { + spotShadowViews.push_back(drawList.addView( + meshes, + meshDrawCommands, + sortedMeshDrawCommands, + edge::createFrustumFromViewProj(spotViewProj), + IndirectDrawList::Filter::ShadowCasters)); + } + + drawList.upload(gfxDevice); + lastDrawStats = DrawStats{ + .meshDraws = static_cast(meshDrawCommands.size()), + .indirectCommands = static_cast(drawList.getCommands().size()), + .instances = static_cast(drawList.getInstances().size()), + .mainViewCommands = mainView.commandCount, + .mainViewInstances = mainView.instanceCount, + }; + } + // TODO: there has to be a better way to do this const auto gpuSceneData = GPUSceneData{ .view = sceneData.camera.GetViewMatrix(), @@ -225,8 +266,8 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera& shadowMapPipeline->drawDirectionalShadow( cmd, *resources, - meshDrawCommands, - sortedMeshDrawCommands, + drawList, + directionalShadowView, lightViewProj); } @@ -238,8 +279,8 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera& shadowMapPipeline->drawSpotShadows( cmd, *resources, - meshDrawCommands, - sortedMeshDrawCommands, + drawList, + spotShadowViews, spotShadowViewProjs); } @@ -260,10 +301,9 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera& cmd, drawImage.getExtent2D(), *resources, - camera, sceneDataBuffer.getBuffer(), - meshDrawCommands, - sortedMeshDrawCommands); + drawList, + mainView); vkCmdEndRendering(cmd); } @@ -319,10 +359,9 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera& cmd, drawImage.getExtent2D(), *resources, - camera, sceneDataBuffer.getBuffer(), - meshDrawCommands, - sortedMeshDrawCommands); + drawList, + mainView); } { @@ -400,6 +439,7 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice) meshPipeline->cleanup(device); sceneDataBuffer.cleanup(gfxDevice); + drawList.cleanup(gfxDevice); // if (drawImageId != NULL_IMAGE_ID) // gfxDevice.destroyImage();