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
+2
View File
@@ -64,6 +64,7 @@ set(SRC_FILES
"src/Graphics/GfxDevice.cpp" "src/Graphics/GfxDevice.cpp"
"src/Graphics/Caches/ImageCache.cpp" "src/Graphics/Caches/ImageCache.cpp"
"src/Graphics/ImageLoader.cpp" "src/Graphics/ImageLoader.cpp"
"src/Graphics/IndirectDrawList.cpp"
"src/Graphics/ImmediateExecuter.cpp" "src/Graphics/ImmediateExecuter.cpp"
"src/Graphics/Init.cpp" "src/Graphics/Init.cpp"
"src/Graphics/Caches/MaterialCache.cpp" "src/Graphics/Caches/MaterialCache.cpp"
@@ -232,6 +233,7 @@ if (SPIRV_VAL_EXECUTABLE)
file(RELATIVE_PATH shader_relative "${ASSETS_SRC_DIR}" "${shader_source}") file(RELATIVE_PATH shader_relative "${ASSETS_SRC_DIR}" "${shader_source}")
list(APPEND VALIDATE_SHADER_COMMANDS list(APPEND VALIDATE_SHADER_COMMANDS
COMMAND "${SPIRV_VAL_EXECUTABLE}" COMMAND "${SPIRV_VAL_EXECUTABLE}"
--target-env vulkan1.3 --scalar-block-layout
"${ASSETS_RUNTIME_DIR}/${shader_relative}.spv") "${ASSETS_RUNTIME_DIR}/${shader_relative}.spv")
endforeach() endforeach()
@@ -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
+2 -1
View File
@@ -12,6 +12,7 @@ layout (location = 0) in vec3 inPos;
layout (location = 1) in vec2 inUV; layout (location = 1) in vec2 inUV;
layout (location = 2) in vec3 inNormal; layout (location = 2) in vec3 inNormal;
layout (location = 3) in vec4 inTangent; layout (location = 3) in vec4 inTangent;
layout (location = 4) flat in uint inMaterialID;
layout (location = 0) out vec4 outFragColor; layout (location = 0) out vec4 outFragColor;
@@ -62,7 +63,7 @@ float calculateShadow(Light light, vec3 fragPos, vec3 geometryNormal)
void main() void main()
{ {
MaterialData material = pcs.sceneData.materials.data[pcs.materialID]; MaterialData material = pcs.sceneData.materials.data[inMaterialID];
uint samplerID = material.textureFilteringMode; uint samplerID = material.textureFilteringMode;
vec4 diffuse = sampleTexture2D(material.diffuseTex, samplerID, inUV) * material.baseColor; vec4 diffuse = sampleTexture2D(material.diffuseTex, samplerID, inUV) * material.baseColor;
+9 -5
View File
@@ -8,6 +8,7 @@ layout (location = 0) out vec3 outPos;
layout (location = 1) out vec2 outUV; layout (location = 1) out vec2 outUV;
layout (location = 2) out vec3 outNormal; layout (location = 2) out vec3 outNormal;
layout (location = 3) out vec4 outTangent; 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 // 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 // so the main pass can depth test against it with LESS_OR_EQUAL
@@ -15,9 +16,11 @@ invariant gl_Position;
void main() 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; gl_Position = pcs.sceneData.viewProj * worldPos;
outPos = worldPos.xyz; outPos = worldPos.xyz;
@@ -25,9 +28,10 @@ void main()
// A bit inefficient, but okay - this is needed for non-uniform scale // 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/ // models. See: http://www.lighthouse3d.com/tutorials/glsl-12-tutorial/the-normal-matrix/
// Simpler case, when everything is uniform // Simpler case, when everything is uniform
// outNormal = (pcs.transform * vec4(v.normal, 0.0)).xyz; // outNormal = (instance.transform * vec4(v.normal, 0.0)).xyz;
outNormal = mat3(transpose(inverse(pcs.transform))) * v.normal; outNormal = mat3(transpose(inverse(instance.transform))) * v.normal;
// w is the bitangent sign, the fragment shader builds the TBN from the interpolated vectors // 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;
} }
+2 -6
View File
@@ -2,14 +2,10 @@
#extension GL_EXT_scalar_block_layout: require #extension GL_EXT_scalar_block_layout: require
#include "scene_data.glsl" #include "scene_data.glsl"
#include "vertex.glsl" #include "instance_data.glsl"
layout (push_constant, scalar) uniform constants layout (push_constant, scalar) uniform constants
{ {
mat4 transform;
SceneDataBuffer sceneData; SceneDataBuffer sceneData;
VertexBuffer vertexBuffer; InstanceBuffer instanceBuffer;
uint materialID;
uint padding;
} pcs; } pcs;
+6 -5
View File
@@ -4,17 +4,18 @@
#extension GL_EXT_buffer_reference : require #extension GL_EXT_buffer_reference : require
#extension GL_EXT_scalar_block_layout : require #extension GL_EXT_scalar_block_layout : require
#include "vertex.glsl" #include "instance_data.glsl"
layout (push_constant, scalar) uniform constants layout (push_constant, scalar) uniform constants
{ {
mat4 modelToLightClip; mat4 lightViewProj;
VertexBuffer vertexBuffer; InstanceBuffer instanceBuffer;
} pcs; } pcs;
void main() 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);
} }
@@ -16,12 +16,24 @@ public:
MeshID addMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh); MeshID addMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh);
const GPUMesh& getMesh(MeshID id) const; const GPUMesh& getMesh(MeshID id) const;
[[nodiscard]] const std::vector<GPUMesh>& getMeshes() const { return meshes; }
const CPUMesh& getCPUMesh(MeshID id) const; const CPUMesh& getCPUMesh(MeshID id) const;
[[nodiscard]] const std::string& getMeshKey(MeshID id) const; [[nodiscard]] const std::string& getMeshKey(MeshID id) const;
[[nodiscard]] std::optional<MeshID> findMeshByKey(std::string_view key) 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: 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<GPUMesh> meshes;
std::vector<CPUMesh> cpuMeshes; 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{ VkPhysicalDeviceFeatures device{
.imageCubeArray = VK_TRUE, .imageCubeArray = VK_TRUE,
.geometryShader = VK_TRUE, .geometryShader = VK_TRUE,
.multiDrawIndirect = VK_TRUE,
.drawIndirectFirstInstance = VK_TRUE,
.depthClamp = VK_TRUE, .depthClamp = VK_TRUE,
.fillModeNonSolid = VK_TRUE, .fillModeNonSolid = VK_TRUE,
.samplerAnisotropy = VK_TRUE, .samplerAnisotropy = VK_TRUE,
@@ -9,9 +9,8 @@
#include <vulkan/vulkan.h> #include <vulkan/vulkan.h>
#include <destrum/Graphics/Pipeline.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/Resources/Buffer.h>
#include <destrum/Graphics/MeshDrawCommand.h>
class GfxDevice; class GfxDevice;
class RenderResources; class RenderResources;
@@ -31,19 +30,17 @@ public:
VkCommandBuffer cmd, VkCommandBuffer cmd,
VkExtent2D renderExtent, VkExtent2D renderExtent,
const RenderResources& resources, const RenderResources& resources,
const Camera& camera,
const GPUBuffer& sceneDataBuffer, const GPUBuffer& sceneDataBuffer,
const std::vector<MeshDrawCommand>& drawCommands, const IndirectDrawList& drawList,
const std::vector<std::size_t>& sortedDrawCommands); const IndirectDrawList::View& view);
void draw( void draw(
VkCommandBuffer cmd, VkCommandBuffer cmd,
VkExtent2D renderExtent, VkExtent2D renderExtent,
const RenderResources& resources, const RenderResources& resources,
const Camera& camera,
const GPUBuffer& sceneDataBuffer, const GPUBuffer& sceneDataBuffer,
const std::vector<MeshDrawCommand>& drawCommands, const IndirectDrawList& drawList,
const std::vector<std::size_t>& sortedDrawCommands); const IndirectDrawList::View& view);
void cleanup(VkDevice device); void cleanup(VkDevice device);
@@ -57,11 +54,8 @@ public:
private: private:
struct PushConstants { struct PushConstants {
glm::mat4 transform;
VkDeviceAddress sceneDataBuffer; VkDeviceAddress sceneDataBuffer;
VkDeviceAddress vertexBuffer; VkDeviceAddress instanceBuffer;
std::uint32_t materialId;
std::uint32_t padding;
}; };
void drawMeshes( void drawMeshes(
@@ -69,10 +63,9 @@ private:
const Pipeline& pipeline, const Pipeline& pipeline,
VkExtent2D renderExtent, VkExtent2D renderExtent,
const RenderResources& resources, const RenderResources& resources,
const Camera& camera,
const GPUBuffer& sceneDataBuffer, const GPUBuffer& sceneDataBuffer,
const std::vector<MeshDrawCommand>& drawCommands, const IndirectDrawList& drawList,
const std::vector<std::size_t>& sortedDrawCommands); const IndirectDrawList::View& view);
private: private:
VkPipelineLayout m_pipelineLayout{VK_NULL_HANDLE}; VkPipelineLayout m_pipelineLayout{VK_NULL_HANDLE};
@@ -11,7 +11,7 @@
#include <destrum/Graphics/Light.h> #include <destrum/Graphics/Light.h>
#include <destrum/Graphics/Pipeline.h> #include <destrum/Graphics/Pipeline.h>
#include <destrum/Graphics/ids.h> #include <destrum/Graphics/ids.h>
#include <destrum/Graphics/MeshDrawCommand.h> #include <destrum/Graphics/IndirectDrawList.h>
class GfxDevice; class GfxDevice;
struct GPUImage; struct GPUImage;
@@ -32,15 +32,15 @@ public:
void drawDirectionalShadow( void drawDirectionalShadow(
VkCommandBuffer cmd, VkCommandBuffer cmd,
const RenderResources& resources, const RenderResources& resources,
const std::vector<MeshDrawCommand>& drawCommands, const IndirectDrawList& drawList,
const std::vector<std::size_t>& sortedDrawCommands, const IndirectDrawList::View& view,
const glm::mat4& lightViewProj); const glm::mat4& lightViewProj);
void drawSpotShadows( void drawSpotShadows(
VkCommandBuffer cmd, VkCommandBuffer cmd,
const RenderResources& resources, const RenderResources& resources,
const std::vector<MeshDrawCommand>& drawCommands, const IndirectDrawList& drawList,
const std::vector<std::size_t>& sortedDrawCommands, const std::vector<IndirectDrawList::View>& views,
const std::vector<glm::mat4>& lightViewProjs); const std::vector<glm::mat4>& lightViewProjs);
[[nodiscard]] glm::mat4 calculateDirectionalViewProj(const glm::vec3& lightDirection, const glm::vec3& center) const; [[nodiscard]] glm::mat4 calculateDirectionalViewProj(const glm::vec3& lightDirection, const glm::vec3& center) const;
@@ -57,8 +57,8 @@ public:
private: private:
struct PushConstants struct PushConstants
{ {
glm::mat4 modelToLightClip; glm::mat4 lightViewProj;
VkDeviceAddress vertexBuffer; VkDeviceAddress instanceBuffer;
}; };
void beginDepthRendering(VkCommandBuffer cmd, const RenderResources& resources, const GPUImage& depthImage); void beginDepthRendering(VkCommandBuffer cmd, const RenderResources& resources, const GPUImage& depthImage);
@@ -67,8 +67,8 @@ private:
void drawShadowCasters( void drawShadowCasters(
VkCommandBuffer cmd, VkCommandBuffer cmd,
const RenderResources& resources, const RenderResources& resources,
const std::vector<MeshDrawCommand>& drawCommands, const IndirectDrawList& drawList,
const std::vector<std::size_t>& sortedDrawCommands, const IndirectDrawList::View& view,
const glm::mat4& lightViewProj); const glm::mat4& lightViewProj);
VkPipelineLayout m_pipelineLayout{VK_NULL_HANDLE}; VkPipelineLayout m_pipelineLayout{VK_NULL_HANDLE};
@@ -5,6 +5,7 @@
#include <glm/vec3.hpp> #include <glm/vec3.hpp>
#include <destrum/Graphics/Camera.h> #include <destrum/Graphics/Camera.h>
#include <destrum/Graphics/IndirectDrawList.h>
#include <destrum/Graphics/Light.h> #include <destrum/Graphics/Light.h>
#include <destrum/Graphics/Pipelines/LightCullingPipeline.h> #include <destrum/Graphics/Pipelines/LightCullingPipeline.h>
#include <destrum/Graphics/Pipelines/LineRenderingPass.h> #include <destrum/Graphics/Pipelines/LineRenderingPass.h>
@@ -29,6 +30,15 @@
class GameRenderer class GameRenderer
{ {
public: 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 struct SceneData
{ {
const Camera& camera; const Camera& camera;
@@ -106,6 +116,8 @@ public:
return resources; return resources;
} }
[[nodiscard]] const DrawStats& getDrawStats() const { return lastDrawStats; }
void draw2D( void draw2D(
VkCommandBuffer cmd, VkCommandBuffer cmd,
GfxDevice& gfxDevice, GfxDevice& gfxDevice,
@@ -125,6 +137,8 @@ private:
std::vector<MeshDrawCommand> meshDrawCommands; std::vector<MeshDrawCommand> meshDrawCommands;
std::vector<std::size_t> sortedMeshDrawCommands; std::vector<std::size_t> sortedMeshDrawCommands;
IndirectDrawList drawList;
DrawStats lastDrawStats;
VkFormat drawImageFormat{VK_FORMAT_R16G16B16A16_SFLOAT}; VkFormat drawImageFormat{VK_FORMAT_R16G16B16A16_SFLOAT};
VkFormat depthImageFormat{VK_FORMAT_D32_SFLOAT}; VkFormat depthImageFormat{VK_FORMAT_D32_SFLOAT};
@@ -20,10 +20,11 @@
struct GPUMesh { struct GPUMesh {
GPUBuffer vertexBuffer; GPUBuffer vertexBuffer;
GPUBuffer indexBuffer;
std::uint32_t numVertices{0}; std::uint32_t numVertices{0};
std::uint32_t numIndices{0}; std::uint32_t numIndices{0};
// offset into MeshCache::getIndexBuffer(), every mesh shares that one index buffer
std::uint32_t firstIndex{0};
// AABB // AABB
glm::vec3 minPos; glm::vec3 minPos;
+7
View File
@@ -208,6 +208,13 @@ if (!isRunning) break;
if (ImGui::Begin("Debug", &m_DebugWindows.stats)) if (ImGui::Begin("Debug", &m_DebugWindows.stats))
{ {
ImGui::Text("FPS: %.2f", avgFPS); 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(); ImGui::End();
} }
+175
View File
@@ -0,0 +1,175 @@
#include <destrum/Graphics/IndirectDrawList.h>
#include <algorithm>
#include <cassert>
#include <cstring>
#include <volk.h>
#include <tracy/Tracy.hpp>
#include <destrum/Graphics/GfxDevice.h>
#include <destrum/Graphics/Resources/Mesh.h>
#include <destrum/Graphics/Util.h>
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<GPUMesh>& meshes,
const std::vector<MeshDrawCommand>& drawCommands,
const std::vector<std::size_t>& sortedDrawCommands,
const Frustum& frustum,
Filter filter)
{
ZoneScopedN("IndirectDrawList::addView");
View view{
.firstCommand = static_cast<std::uint32_t>(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<std::uint32_t>(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<VkDeviceSize>(view.firstCommand) * sizeof(VkDrawIndexedIndirectCommand),
view.commandCount,
sizeof(VkDrawIndexedIndirectCommand));
}
VkDeviceAddress IndirectDrawList::getInstanceBufferAddress() const
{
return frames[currentFrame].instances.address;
}
+79 -18
View File
@@ -49,15 +49,73 @@ MeshID MeshCache::addMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh)
return id; 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<std::size_t>(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<VkDeviceSize>(indexCount) * sizeof(std::uint32_t),
};
vkCmdCopyBuffer(cmd, indexBuffer.buffer, newBuffer.buffer, 1, &copy);
});
} 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 { try {
// create index buffer const auto numIndices = static_cast<std::uint32_t>(cpuMesh.indices.size());
ensureIndexCapacity(gfxDevice, indexCount + numIndices);
gpuMesh.firstIndex = indexCount;
const auto indexBufferSize = cpuMesh.indices.size() * sizeof(std::uint32_t); const auto indexBufferSize = cpuMesh.indices.size() * sizeof(std::uint32_t);
gpuMesh.indexBuffer = gfxDevice.createBuffer( const auto indexBufferOffset = static_cast<VkDeviceSize>(gpuMesh.firstIndex) * sizeof(std::uint32_t);
indexBufferSize,
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE);
// create vertex buffer // create vertex buffer
const auto vertexBufferSize = cpuMesh.vertices.size() * sizeof(CPUMesh::Vertex); 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<char*>(vertexIndexData) + vertexBufferSize, cpuMesh.indices.data(), indexBufferSize); memcpy(static_cast<char*>(vertexIndexData) + vertexBufferSize, cpuMesh.indices.data(), indexBufferSize);
gfxDevice.getMemoryManager().flushAllocation(vertexIndexStaging); 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) { gfxDevice.immediateSubmit([&](VkCommandBuffer cmd) {
vkutil::bufferHostWriteToTransferReadBarrier( vkutil::bufferHostWriteToTransferReadBarrier(
cmd, cmd,
@@ -97,17 +157,17 @@ void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh
const auto indexCopy = VkBufferCopy{ const auto indexCopy = VkBufferCopy{
.srcOffset = vertexBufferSize, .srcOffset = vertexBufferSize,
.dstOffset = 0, .dstOffset = indexBufferOffset,
.size = indexBufferSize, .size = indexBufferSize,
}; };
vkCmdCopyBuffer(cmd, vertexIndexStaging.buffer, gpuMesh.indexBuffer.buffer, 1, &indexCopy); vkCmdCopyBuffer(cmd, vertexIndexStaging.buffer, indexBuffer.buffer, 1, &indexCopy);
const std::array<VkBufferMemoryBarrier2, 2> destinationBarriers{{ const std::array<VkBufferMemoryBarrier2, 2> destinationBarriers{{
{ {
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2, .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT, .srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT,
.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_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, .dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = 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, .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT, .srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT,
.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT, .srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT,
.dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_INPUT_BIT, .dstStageMask = VK_PIPELINE_STAGE_2_INDEX_INPUT_BIT | VK_PIPELINE_STAGE_2_COPY_BIT,
.dstAccessMask = VK_ACCESS_2_INDEX_READ_BIT, .dstAccessMask = VK_ACCESS_2_INDEX_READ_BIT | VK_ACCESS_2_TRANSFER_READ_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = gpuMesh.indexBuffer.buffer, .buffer = indexBuffer.buffer,
.offset = 0, .offset = indexBufferOffset,
.size = VK_WHOLE_SIZE, .size = indexBufferSize,
} }
}}; }};
const VkDependencyInfo destinationDependency{ const VkDependencyInfo destinationDependency{
@@ -141,11 +201,10 @@ void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh
} }
gfxDevice.destroyBufferNow(vertexIndexStaging); gfxDevice.destroyBufferNow(vertexIndexStaging);
indexCount += numIndices;
const auto vtxBufferName = cpuMesh.name + " (vtx)"; 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.vertexBuffer.buffer, vtxBufferName.c_str());
vkutil::addDebugLabel(gfxDevice.getDevice(), gpuMesh.indexBuffer.buffer, idxBufferName.c_str());
if (gpuMesh.hasSkeleton) { if (gpuMesh.hasSkeleton) {
// create skinning data buffer // create skinning data buffer
@@ -214,7 +273,6 @@ void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh
} catch (...) { } catch (...) {
gfxDevice.destroyBufferNow(gpuMesh.skinningDataBuffer); gfxDevice.destroyBufferNow(gpuMesh.skinningDataBuffer);
gfxDevice.destroyBufferNow(gpuMesh.vertexBuffer); gfxDevice.destroyBufferNow(gpuMesh.vertexBuffer);
gfxDevice.destroyBufferNow(gpuMesh.indexBuffer);
gpuMesh = {}; gpuMesh = {};
throw; throw;
} }
@@ -246,8 +304,11 @@ std::optional<MeshID> MeshCache::findMeshByKey(std::string_view key) const
void MeshCache::cleanup(GfxDevice& gfxDevice) void MeshCache::cleanup(GfxDevice& gfxDevice)
{ {
gfxDevice.destroyBuffer(indexBuffer);
indexCount = 0;
indexCapacity = 0;
for (auto& mesh : meshes) { for (auto& mesh : meshes) {
gfxDevice.destroyBuffer(mesh.indexBuffer);
gfxDevice.destroyBuffer(mesh.vertexBuffer); gfxDevice.destroyBuffer(mesh.vertexBuffer);
gfxDevice.destroyBuffer(mesh.skinningDataBuffer); gfxDevice.destroyBuffer(mesh.skinningDataBuffer);
} }
+34 -96
View File
@@ -8,7 +8,6 @@
#include <destrum/Graphics/GfxDevice.h> #include <destrum/Graphics/GfxDevice.h>
#include <destrum/Graphics/RenderResources.h> #include <destrum/Graphics/RenderResources.h>
#include <destrum/Graphics/Caches/MeshCache.h> #include <destrum/Graphics/Caches/MeshCache.h>
#include <destrum/Graphics/Frustum.h>
#include <destrum/Graphics/Util.h> #include <destrum/Graphics/Util.h>
#include "volk.h" #include "volk.h"
@@ -119,10 +118,9 @@ void MeshPipeline::drawDepthPrepass(
VkCommandBuffer cmd, VkCommandBuffer cmd,
VkExtent2D renderExtent, VkExtent2D renderExtent,
const RenderResources& resources, const RenderResources& resources,
const Camera& camera,
const GPUBuffer& sceneDataBuffer, const GPUBuffer& sceneDataBuffer,
const std::vector<MeshDrawCommand>& drawCommands, const IndirectDrawList& drawList,
const std::vector<std::size_t>& sortedDrawCommands) const IndirectDrawList::View& view)
{ {
if (!m_depthPrepassPipeline) if (!m_depthPrepassPipeline)
{ {
@@ -134,20 +132,18 @@ void MeshPipeline::drawDepthPrepass(
*m_depthPrepassPipeline, *m_depthPrepassPipeline,
renderExtent, renderExtent,
resources, resources,
camera,
sceneDataBuffer, sceneDataBuffer,
drawCommands, drawList,
sortedDrawCommands); view);
} }
void MeshPipeline::draw( void MeshPipeline::draw(
VkCommandBuffer cmd, VkCommandBuffer cmd,
VkExtent2D renderExtent, VkExtent2D renderExtent,
const RenderResources& resources, const RenderResources& resources,
const Camera& camera,
const GPUBuffer& sceneDataBuffer, const GPUBuffer& sceneDataBuffer,
const std::vector<MeshDrawCommand>& drawCommands, const IndirectDrawList& drawList,
const std::vector<std::size_t>& sortedDrawCommands) const IndirectDrawList::View& view)
{ {
if (!m_pipeline) if (!m_pipeline)
{ {
@@ -159,10 +155,9 @@ void MeshPipeline::draw(
*m_pipeline, *m_pipeline,
renderExtent, renderExtent,
resources, resources,
camera,
sceneDataBuffer, sceneDataBuffer,
drawCommands, drawList,
sortedDrawCommands); view);
} }
void MeshPipeline::drawMeshes( void MeshPipeline::drawMeshes(
@@ -170,13 +165,10 @@ void MeshPipeline::drawMeshes(
const Pipeline& pipeline, const Pipeline& pipeline,
VkExtent2D renderExtent, VkExtent2D renderExtent,
const RenderResources& resources, const RenderResources& resources,
const Camera& camera,
const GPUBuffer& sceneDataBuffer, const GPUBuffer& sceneDataBuffer,
const std::vector<MeshDrawCommand>& drawCommands, const IndirectDrawList& drawList,
const std::vector<std::size_t>& sortedDrawCommands) const IndirectDrawList::View& view)
{ {
const MeshCache& meshCache = resources.meshes();
pipeline.bind(cmd); pipeline.bind(cmd);
resources.bindBindlessDescSet(cmd, m_pipelineLayout); resources.bindBindlessDescSet(cmd, m_pipelineLayout);
@@ -204,86 +196,32 @@ void MeshPipeline::drawMeshes(
m_renderWireframe ? VK_POLYGON_MODE_LINE : VK_POLYGON_MODE_FILL m_renderWireframe ? VK_POLYGON_MODE_LINE : VK_POLYGON_MODE_FILL
); );
MeshID prevMeshId = NULL_MESH_ID; // The same for every draw, per-draw data (transform, material, vertex buffer)
int actualDrawCalls = 0; // lives in the instance buffer and is looked up with gl_InstanceIndex
const auto pushConstants = PushConstants{
const auto frustum = edge::createFrustumFromCamera(camera); .sceneDataBuffer = sceneDataBuffer.address,
.instanceBuffer = drawList.getInstanceBufferAddress(),
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);
}; };
if (!sortedDrawCommands.empty()) vkCmdPushConstants(
{ cmd,
for (std::size_t drawIndex : sortedDrawCommands) m_pipelineLayout,
{ VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
assert(drawIndex < drawCommands.size()); 0,
drawOne(drawCommands[drawIndex]); sizeof(PushConstants),
} &pushConstants
} );
else
{ vkutil::cmdBeginLabelFormatted(
for (const MeshDrawCommand& dc : drawCommands) cmd,
{ vkutil::labelColors::draw,
drawOne(dc); "%u indirect draws, %u instances",
} view.commandCount,
} view.instanceCount);
drawList.draw(cmd, resources.meshes().getIndexBuffer(), view);
vkutil::cmdEndLabel(cmd);
} }
void MeshPipeline::cleanup(VkDevice device) void MeshPipeline::cleanup(VkDevice device)
@@ -12,7 +12,6 @@
#include <destrum/Graphics/GfxDevice.h> #include <destrum/Graphics/GfxDevice.h>
#include <destrum/Graphics/RenderResources.h> #include <destrum/Graphics/RenderResources.h>
#include <destrum/Graphics/Caches/MeshCache.h> #include <destrum/Graphics/Caches/MeshCache.h>
#include <destrum/Graphics/Frustum.h>
#include <destrum/Graphics/Util.h> #include <destrum/Graphics/Util.h>
#include "volk.h" #include "volk.h"
@@ -206,8 +205,8 @@ float ShadowMapPipeline::getNormalOffset() const
void ShadowMapPipeline::drawDirectionalShadow( void ShadowMapPipeline::drawDirectionalShadow(
VkCommandBuffer cmd, VkCommandBuffer cmd,
const RenderResources& resources, const RenderResources& resources,
const std::vector<MeshDrawCommand>& drawCommands, const IndirectDrawList& drawList,
const std::vector<std::size_t>& sortedDrawCommands, const IndirectDrawList::View& view,
const glm::mat4& lightViewProj) const glm::mat4& lightViewProj)
{ {
if (!m_pipeline || shadowMapId == NULL_IMAGE_ID) if (!m_pipeline || shadowMapId == NULL_IMAGE_ID)
@@ -219,15 +218,15 @@ void ShadowMapPipeline::drawDirectionalShadow(
beginDepthRendering(cmd, resources, shadowMap); beginDepthRendering(cmd, resources, shadowMap);
setRenderRegion(cmd, VkRect2D{.offset = {}, .extent = shadowMap.getExtent2D()}); setRenderRegion(cmd, VkRect2D{.offset = {}, .extent = shadowMap.getExtent2D()});
drawShadowCasters(cmd, resources, drawCommands, sortedDrawCommands, lightViewProj); drawShadowCasters(cmd, resources, drawList, view, lightViewProj);
endDepthRendering(cmd, shadowMap); endDepthRendering(cmd, shadowMap);
} }
void ShadowMapPipeline::drawSpotShadows( void ShadowMapPipeline::drawSpotShadows(
VkCommandBuffer cmd, VkCommandBuffer cmd,
const RenderResources& resources, const RenderResources& resources,
const std::vector<MeshDrawCommand>& drawCommands, const IndirectDrawList& drawList,
const std::vector<std::size_t>& sortedDrawCommands, const std::vector<IndirectDrawList::View>& views,
const std::vector<glm::mat4>& lightViewProjs) const std::vector<glm::mat4>& lightViewProjs)
{ {
if (!m_pipeline || spotShadowAtlasId == NULL_IMAGE_ID || lightViewProjs.empty()) if (!m_pipeline || spotShadowAtlasId == NULL_IMAGE_ID || lightViewProjs.empty())
@@ -236,6 +235,7 @@ void ShadowMapPipeline::drawSpotShadows(
} }
assert(lightViewProjs.size() <= MAX_SPOT_SHADOWS); assert(lightViewProjs.size() <= MAX_SPOT_SHADOWS);
assert(views.size() == lightViewProjs.size());
const auto& spotShadowAtlas = resources.getImage(spotShadowAtlasId); const auto& spotShadowAtlas = resources.getImage(spotShadowAtlasId);
beginDepthRendering(cmd, resources, spotShadowAtlas); beginDepthRendering(cmd, resources, spotShadowAtlas);
@@ -244,7 +244,7 @@ void ShadowMapPipeline::drawSpotShadows(
vkutil::cmdBeginLabelFormatted(cmd, vkutil::labelColors::shadow, "Spot shadow %u", shadowIndex); vkutil::cmdBeginLabelFormatted(cmd, vkutil::labelColors::shadow, "Spot shadow %u", shadowIndex);
setRenderRegion(cmd, spotShadowTileRegion(shadowIndex)); setRenderRegion(cmd, spotShadowTileRegion(shadowIndex));
drawShadowCasters(cmd, resources, drawCommands, sortedDrawCommands, lightViewProjs[shadowIndex]); drawShadowCasters(cmd, resources, drawList, views[shadowIndex], lightViewProjs[shadowIndex]);
vkutil::cmdEndLabel(cmd); vkutil::cmdEndLabel(cmd);
} }
@@ -306,73 +306,31 @@ void ShadowMapPipeline::setRenderRegion(VkCommandBuffer cmd, const VkRect2D& reg
void ShadowMapPipeline::drawShadowCasters( void ShadowMapPipeline::drawShadowCasters(
VkCommandBuffer cmd, VkCommandBuffer cmd,
const RenderResources& resources, const RenderResources& resources,
const std::vector<MeshDrawCommand>& drawCommands, const IndirectDrawList& drawList,
const std::vector<std::size_t>& sortedDrawCommands, const IndirectDrawList::View& view,
const glm::mat4& lightViewProj) const glm::mat4& lightViewProj)
{ {
const MeshCache& meshCache = resources.meshes(); const auto pushConstants = PushConstants{
const Frustum lightFrustum = edge::createFrustumFromViewProj(lightViewProj); .lightViewProj = lightViewProj,
MeshID previousMeshId = NULL_MESH_ID; .instanceBuffer = drawList.getInstanceBufferAddress(),
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);
}; };
if (!sortedDrawCommands.empty()) vkCmdPushConstants(
{ cmd,
for (std::size_t drawIndex : sortedDrawCommands) m_pipelineLayout,
{ VK_SHADER_STAGE_VERTEX_BIT,
assert(drawIndex < drawCommands.size()); 0,
drawCaster(drawCommands[drawIndex]); sizeof(PushConstants),
} &pushConstants);
}
else vkutil::cmdBeginLabelFormatted(
{ cmd,
for (const MeshDrawCommand& dc : drawCommands) vkutil::labelColors::draw,
{ "%u indirect draws, %u instances",
drawCaster(dc); view.commandCount,
} view.instanceCount);
}
drawList.draw(cmd, resources.meshes().getIndexBuffer(), view);
vkutil::cmdEndLabel(cmd);
} }
+61 -21
View File
@@ -79,22 +79,14 @@ void GameRenderer::endDrawing()
sortedMeshDrawCommands.resize(meshDrawCommands.size()); sortedMeshDrawCommands.resize(meshDrawCommands.size());
std::iota(sortedMeshDrawCommands.begin(), sortedMeshDrawCommands.end(), 0); 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( std::stable_sort(
sortedMeshDrawCommands.begin(), sortedMeshDrawCommands.begin(),
sortedMeshDrawCommands.end(), sortedMeshDrawCommands.end(),
[this](std::size_t left, std::size_t right) [this](std::size_t left, std::size_t right)
{ {
const auto& lhs = meshDrawCommands[left]; return meshDrawCommands[left].meshId < meshDrawCommands[right].meshId;
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);
}); });
} }
@@ -147,6 +139,55 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
spotShadowViewProjs.push_back(spotShadow.lightViewProj); 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<IndirectDrawList::View> 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<std::uint32_t>(meshDrawCommands.size()),
.indirectCommands = static_cast<std::uint32_t>(drawList.getCommands().size()),
.instances = static_cast<std::uint32_t>(drawList.getInstances().size()),
.mainViewCommands = mainView.commandCount,
.mainViewInstances = mainView.instanceCount,
};
}
// TODO: there has to be a better way to do this // TODO: there has to be a better way to do this
const auto gpuSceneData = GPUSceneData{ const auto gpuSceneData = GPUSceneData{
.view = sceneData.camera.GetViewMatrix(), .view = sceneData.camera.GetViewMatrix(),
@@ -225,8 +266,8 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
shadowMapPipeline->drawDirectionalShadow( shadowMapPipeline->drawDirectionalShadow(
cmd, cmd,
*resources, *resources,
meshDrawCommands, drawList,
sortedMeshDrawCommands, directionalShadowView,
lightViewProj); lightViewProj);
} }
@@ -238,8 +279,8 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
shadowMapPipeline->drawSpotShadows( shadowMapPipeline->drawSpotShadows(
cmd, cmd,
*resources, *resources,
meshDrawCommands, drawList,
sortedMeshDrawCommands, spotShadowViews,
spotShadowViewProjs); spotShadowViewProjs);
} }
@@ -260,10 +301,9 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
cmd, cmd,
drawImage.getExtent2D(), drawImage.getExtent2D(),
*resources, *resources,
camera,
sceneDataBuffer.getBuffer(), sceneDataBuffer.getBuffer(),
meshDrawCommands, drawList,
sortedMeshDrawCommands); mainView);
vkCmdEndRendering(cmd); vkCmdEndRendering(cmd);
} }
@@ -319,10 +359,9 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
cmd, cmd,
drawImage.getExtent2D(), drawImage.getExtent2D(),
*resources, *resources,
camera,
sceneDataBuffer.getBuffer(), sceneDataBuffer.getBuffer(),
meshDrawCommands, drawList,
sortedMeshDrawCommands); mainView);
} }
{ {
@@ -400,6 +439,7 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
meshPipeline->cleanup(device); meshPipeline->cleanup(device);
sceneDataBuffer.cleanup(gfxDevice); sceneDataBuffer.cleanup(gfxDevice);
drawList.cleanup(gfxDevice);
// if (drawImageId != NULL_IMAGE_ID) // if (drawImageId != NULL_IMAGE_ID)
// gfxDevice.destroyImage(); // gfxDevice.destroyImage();