feat: draw meshes with instanced multi-draw indirect
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<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, ©);
|
||||
});
|
||||
} 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<std::uint32_t>(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<VkDeviceSize>(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<char*>(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<VkBufferMemoryBarrier2, 2> 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<MeshID> 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);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <destrum/Graphics/GfxDevice.h>
|
||||
#include <destrum/Graphics/RenderResources.h>
|
||||
#include <destrum/Graphics/Caches/MeshCache.h>
|
||||
#include <destrum/Graphics/Frustum.h>
|
||||
#include <destrum/Graphics/Util.h>
|
||||
|
||||
#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<MeshDrawCommand>& drawCommands,
|
||||
const std::vector<std::size_t>& 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<MeshDrawCommand>& drawCommands,
|
||||
const std::vector<std::size_t>& 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<MeshDrawCommand>& drawCommands,
|
||||
const std::vector<std::size_t>& 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)
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <destrum/Graphics/GfxDevice.h>
|
||||
#include <destrum/Graphics/RenderResources.h>
|
||||
#include <destrum/Graphics/Caches/MeshCache.h>
|
||||
#include <destrum/Graphics/Frustum.h>
|
||||
#include <destrum/Graphics/Util.h>
|
||||
|
||||
#include "volk.h"
|
||||
@@ -206,8 +205,8 @@ float ShadowMapPipeline::getNormalOffset() const
|
||||
void ShadowMapPipeline::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)
|
||||
{
|
||||
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<MeshDrawCommand>& drawCommands,
|
||||
const std::vector<std::size_t>& sortedDrawCommands,
|
||||
const IndirectDrawList& drawList,
|
||||
const std::vector<IndirectDrawList::View>& views,
|
||||
const std::vector<glm::mat4>& 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<MeshDrawCommand>& drawCommands,
|
||||
const std::vector<std::size_t>& 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);
|
||||
}
|
||||
|
||||
@@ -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<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
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user