Graphics: Add SpriteChainRenderer

This commit is contained in:
Jérôme Leclercq
2021-09-05 15:50:17 +02:00
parent a18d505ae2
commit 938d965e06
18 changed files with 705 additions and 14 deletions

View File

@@ -8,4 +8,14 @@
namespace Nz
{
ElementRenderer::~ElementRenderer() = default;
void ElementRenderer::Prepare(ElementRendererData& /*rendererData*/, RenderFrame& /*currentFrame*/, const Pointer<const RenderElement>* /*elements*/, std::size_t /*elementCount*/)
{
}
void ElementRenderer::Reset(ElementRendererData& /*rendererData*/, RenderFrame& /*currentFrame*/)
{
}
ElementRendererData::~ElementRendererData() = default;
}

View File

@@ -9,6 +9,7 @@
#include <Nazara/Graphics/InstancedRenderable.hpp>
#include <Nazara/Graphics/Material.hpp>
#include <Nazara/Graphics/RenderElement.hpp>
#include <Nazara/Graphics/SpriteChainRenderer.hpp>
#include <Nazara/Graphics/SubmeshRenderer.hpp>
#include <Nazara/Graphics/ViewerInstance.hpp>
#include <Nazara/Graphics/WorldInstance.hpp>
@@ -30,7 +31,8 @@ namespace Nz
m_depthPassIndex = passRegistry.GetPassIndex("DepthPass");
m_forwardPassIndex = passRegistry.GetPassIndex("ForwardPass");
m_elementRenderers.resize(1);
m_elementRenderers.resize(BasicRenderElementCount);
m_elementRenderers[UnderlyingCast(BasicRenderElement::SpriteChain)] = std::make_unique<SpriteChainRenderer>(*Graphics::Instance()->GetRenderDevice());
m_elementRenderers[UnderlyingCast(BasicRenderElement::Submesh)] = std::make_unique<SubmeshRenderer>();
}
@@ -113,6 +115,8 @@ namespace Nz
void ForwardFramePipeline::Render(RenderFrame& renderFrame)
{
m_currentRenderFrame = &renderFrame;
Graphics* graphics = Graphics::Instance();
renderFrame.PushForRelease(std::move(m_removedWorldInstances));
@@ -167,6 +171,7 @@ namespace Nz
};
// Render queues handling
bool prepare = false;
for (auto&& [viewer, data] : m_viewers)
{
auto& viewerData = data;
@@ -208,6 +213,7 @@ namespace Nz
{
viewerData.rebuildDepthPrepass = true;
viewerData.rebuildForwardPass = true;
prepare = true;
viewerData.visibilityHash = visibilityHash;
}
@@ -256,6 +262,48 @@ namespace Nz
});
}
if (prepare)
{
for (std::size_t i = 0; i < m_elementRenderers.size(); ++i)
{
auto& elementRendererPtr = m_elementRenderers[i];
for (auto&& [_, viewerData] : m_viewers)
{
if (i >= viewerData.elementRendererData.size() || !viewerData.elementRendererData[i])
{
if (i >= viewerData.elementRendererData.size())
viewerData.elementRendererData.resize(i + 1);
viewerData.elementRendererData[i] = elementRendererPtr->InstanciateData();
}
if (elementRendererPtr)
elementRendererPtr->Reset(*viewerData.elementRendererData[i], renderFrame);
}
}
for (auto&& [_, viewerData] : m_viewers)
{
auto& rendererData = viewerData.elementRendererData;
ProcessRenderQueue(viewerData.depthPrepassRenderQueue, [&](std::size_t elementType, const Pointer<const RenderElement>* elements, std::size_t elementCount)
{
ElementRenderer& elementRenderer = *m_elementRenderers[elementType];
elementRenderer.Prepare(*rendererData[elementType], renderFrame, elements, elementCount);
});
ProcessRenderQueue(viewerData.forwardRenderQueue, [&](std::size_t elementType, const Pointer<const RenderElement>* elements, std::size_t elementCount)
{
ElementRenderer& elementRenderer = *m_elementRenderers[elementType];
elementRenderer.Prepare(*rendererData[elementType], renderFrame, elements, elementCount);
});
viewerData.rebuildForwardPass = true;
viewerData.rebuildDepthPrepass = true;
}
}
if (m_bakedFrameGraph.Resize(renderFrame))
{
const std::shared_ptr<TextureSampler>& sampler = graphics->GetSamplerCache().Get({});
@@ -406,7 +454,11 @@ namespace Nz
builder.BindShaderBinding(Graphics::ViewerBindingSet, viewer->GetViewerInstance().GetShaderBinding());
ProcessRenderQueue(builder, viewerData.depthPrepassRenderQueue);
ProcessRenderQueue(viewerData.depthPrepassRenderQueue, [&](std::size_t elementType, const Pointer<const RenderElement>* elements, std::size_t elementCount)
{
ElementRenderer& elementRenderer = *m_elementRenderers[elementType];
elementRenderer.Render(*viewerData.elementRendererData[elementType], builder, elements, elementCount);
});
});
FramePass& forwardPass = frameGraph.AddPass("Forward pass");
@@ -433,8 +485,12 @@ namespace Nz
builder.SetViewport(viewport);
builder.BindShaderBinding(Graphics::ViewerBindingSet, viewer->GetViewerInstance().GetShaderBinding());
ProcessRenderQueue(builder, viewerData.forwardRenderQueue);
ProcessRenderQueue(viewerData.forwardRenderQueue, [&](std::size_t elementType, const Pointer<const RenderElement>* elements, std::size_t elementCount)
{
ElementRenderer& elementRenderer = *m_elementRenderers[elementType];
elementRenderer.Render(*viewerData.elementRendererData[elementType], builder, elements, elementCount);
});
});
}
@@ -462,7 +518,8 @@ namespace Nz
it->second.usedCount++;
}
void ForwardFramePipeline::ProcessRenderQueue(CommandBufferBuilder& builder, const RenderQueue<RenderElement*>& renderQueue)
template<typename F>
void ForwardFramePipeline::ProcessRenderQueue(const RenderQueue<RenderElement*>& renderQueue, F&& callback)
{
if (renderQueue.empty())
return;
@@ -485,8 +542,7 @@ namespace Nz
if (elementType >= m_elementRenderers.size() || !m_elementRenderers[elementType])
continue;
ElementRenderer& elementRenderer = *m_elementRenderers[elementType];
elementRenderer.Render(builder, first, count);
callback(elementType, first, count);
}
}

View File

@@ -1,10 +0,0 @@
// Copyright (C) 2017 Jérôme Leclercq
// This file is part of the "Nazara Engine - Graphics module"
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Graphics/RenderSubmesh.hpp>
#include <Nazara/Graphics/Debug.hpp>
namespace Nz
{
}

View File

@@ -0,0 +1,56 @@
// Copyright (C) 2017 Jérôme Leclercq
// This file is part of the "Nazara Engine - Graphics module"
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Graphics/Sprite.hpp>
#include <Nazara/Graphics/Material.hpp>
#include <Nazara/Graphics/RenderSpriteChain.hpp>
#include <Nazara/Graphics/WorldInstance.hpp>
#include <Nazara/Graphics/Debug.hpp>
namespace Nz
{
Sprite::Sprite(std::shared_ptr<Material> material) :
InstancedRenderable(Nz::Boxf(-1000.f, -1000.f, -1000.f, 2000.f, 2000.f, 2000.f)),
m_material(std::move(material))
{
m_vertices[0] = VertexStruct_XYZ_Color_UV{ Vector3f(0.f, 0.f, 0.f), Nz::Color::White, Vector2f(0.f, 0.f) };
m_vertices[1] = VertexStruct_XYZ_Color_UV{ Vector3f(1.f, 0.f, 0.f), Nz::Color::White, Vector2f(1.f, 0.f) };
m_vertices[2] = VertexStruct_XYZ_Color_UV{ Vector3f(0.f, 1.f, 0.f), Nz::Color::White, Vector2f(0.f, 1.f) };
m_vertices[3] = VertexStruct_XYZ_Color_UV{ Vector3f(1.f, 1.f, 0.f), Nz::Color::White, Vector2f(1.f, 1.f) };
}
void Sprite::BuildElement(std::size_t passIndex, const WorldInstance& worldInstance, std::vector<std::unique_ptr<RenderElement>>& elements) const
{
MaterialPass* materialPass = m_material->GetPass(passIndex);
if (!materialPass)
return;
const std::shared_ptr<VertexDeclaration>& vertexDeclaration = VertexDeclaration::Get(VertexLayout::XYZ_Color_UV);
std::vector<RenderPipelineInfo::VertexBufferData> vertexBufferData = {
{
{
0,
vertexDeclaration
}
}
};
const auto& renderPipeline = materialPass->GetPipeline()->GetRenderPipeline(vertexBufferData);
elements.emplace_back(std::make_unique<RenderSpriteChain>(0, renderPipeline, vertexDeclaration, 1, m_vertices.data(), materialPass->GetShaderBinding(), worldInstance.GetShaderBinding()));
}
const std::shared_ptr<Material>& Sprite::GetMaterial(std::size_t i) const
{
assert(i == 0);
NazaraUnused(i);
return m_material;
}
std::size_t Sprite::GetMaterialCount() const
{
return 1;
}
}

View File

@@ -0,0 +1,258 @@
// Copyright (C) 2017 Jérôme Leclercq
// This file is part of the "Nazara Engine - Graphics module"
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Graphics/SpriteChainRenderer.hpp>
#include <Nazara/Graphics/Graphics.hpp>
#include <Nazara/Graphics/RenderSpriteChain.hpp>
#include <Nazara/Renderer/CommandBufferBuilder.hpp>
#include <Nazara/Renderer/RenderFrame.hpp>
#include <Nazara/Renderer/UploadPool.hpp>
#include <utility>
#include <Nazara/Graphics/Debug.hpp>
namespace Nz
{
SpriteChainRenderer::SpriteChainRenderer(RenderDevice& device, std::size_t maxVertexBufferSize) :
m_device(device),
m_maxVertexBufferSize(maxVertexBufferSize),
m_maxVertexCount(m_maxVertexBufferSize / (2 * sizeof(float))) // Treat vec2 as the minimum declaration possible
{
std::size_t maxQuadCount = m_maxVertexCount / 4;
std::size_t indexCount = 6 * maxQuadCount;
m_indexBuffer = m_device.InstantiateBuffer(BufferType::Index);
if (!m_indexBuffer->Initialize(indexCount * sizeof(UInt16), BufferUsage::DeviceLocal))
throw std::runtime_error("failed to initialize index buffer");
// Generate indices for quad (0, 1, 2, 2, 1, 3, ...)
std::vector<UInt16> indices(indexCount);
UInt16* indexPtr = indices.data();
for (std::size_t i = 0; i < maxQuadCount; ++i)
{
*indexPtr++ = i * 4 + 0;
*indexPtr++ = i * 4 + 1;
*indexPtr++ = i * 4 + 2;
*indexPtr++ = i * 4 + 2;
*indexPtr++ = i * 4 + 1;
*indexPtr++ = i * 4 + 3;
}
m_indexBuffer->Fill(indices.data(), 0, indexCount * sizeof(UInt16));
}
std::unique_ptr<ElementRendererData> SpriteChainRenderer::InstanciateData()
{
return std::make_unique<SpriteChainRendererData>();
}
void SpriteChainRenderer::Prepare(ElementRendererData& rendererData, RenderFrame& currentFrame, const Pointer<const RenderElement>* elements, std::size_t elementCount)
{
auto& data = static_cast<SpriteChainRendererData&>(rendererData);
std::vector<std::pair<UploadPool::Allocation*, AbstractBuffer*>> pendingCopies;
std::size_t firstQuadIndex = 0;
SpriteChainRendererData::DrawCall* currentDrawCall = nullptr;
UploadPool::Allocation* currentAllocation = nullptr;
UInt8* currentAllocationMemPtr = nullptr;
const VertexDeclaration* currentVertexDeclaration = nullptr;
AbstractBuffer* currentVertexBuffer = nullptr;
const RenderPipeline* currentPipeline = nullptr;
const ShaderBinding* currentInstanceBinding = nullptr;
const ShaderBinding* currentMaterialBinding = nullptr;
auto FlushDrawCall = [&]()
{
currentDrawCall = nullptr;
};
auto Flush = [&]()
{
// changing vertex buffer always mean we have to switch draw calls
FlushDrawCall();
if (currentAllocation)
{
pendingCopies.emplace_back(currentAllocation, currentVertexBuffer);
firstQuadIndex = 0;
currentAllocation = nullptr;
currentVertexBuffer = nullptr;
}
};
std::size_t oldDrawCallCount = data.drawCalls.size();
for (std::size_t i = 0; i < elementCount; ++i)
{
assert(elements[i]->GetElementType() == UnderlyingCast(BasicRenderElement::SpriteChain));
const RenderSpriteChain& spriteChain = static_cast<const RenderSpriteChain&>(*elements[i]);
const VertexDeclaration* vertexDeclaration = spriteChain.GetVertexDeclaration();
std::size_t stride = vertexDeclaration->GetStride();
if (currentVertexDeclaration != vertexDeclaration)
{
// TODO: It's be possible to use another vertex declaration with the same vertex buffer but currently very complicated
// Wait until buffer rewrite
Flush();
currentVertexDeclaration = vertexDeclaration;
}
if (currentPipeline != spriteChain.GetRenderPipeline())
{
FlushDrawCall();
currentPipeline = spriteChain.GetRenderPipeline();
}
if (currentMaterialBinding != &spriteChain.GetMaterialBinding())
{
FlushDrawCall();
currentMaterialBinding = &spriteChain.GetMaterialBinding();
}
if (currentInstanceBinding != &spriteChain.GetInstanceBinding())
{
// TODO: Flushing draw calls on instance binding means we can have e.g. 1000 sprites rendered using a draw call for each one
// which is far from being efficient, using some bindless could help (or at least instancing?)
FlushDrawCall();
currentInstanceBinding = &spriteChain.GetInstanceBinding();
}
std::size_t remainingQuads = spriteChain.GetSpriteCount();
while (remainingQuads > 0)
{
if (!currentAllocation)
{
currentAllocation = &currentFrame.GetUploadPool().Allocate(m_maxVertexBufferSize);
currentAllocationMemPtr = static_cast<UInt8*>(currentAllocation->mappedPtr);
std::shared_ptr<AbstractBuffer> vertexBuffer = m_device.InstantiateBuffer(BufferType::Vertex);
vertexBuffer->Initialize(m_maxVertexBufferSize, BufferUsage::DeviceLocal);
currentVertexBuffer = vertexBuffer.get();
data.vertexBuffers.emplace_back(std::move(vertexBuffer));
}
if (!currentDrawCall)
{
data.drawCalls.push_back(SpriteChainRendererData::DrawCall{
currentVertexBuffer,
currentPipeline,
currentInstanceBinding,
currentMaterialBinding,
6 * firstQuadIndex,
0,
});
currentDrawCall = &data.drawCalls.back();
}
std::size_t remainingSpace = m_maxVertexBufferSize - (currentAllocationMemPtr - currentAllocation->mappedPtr);
std::size_t maxQuads = remainingSpace / (4 * stride);
if (maxQuads == 0)
{
Flush();
continue;
}
std::size_t copiedQuadCount = std::min(maxQuads, remainingQuads);
std::size_t copiedSize = 4 * copiedQuadCount * stride;
std::memcpy(currentAllocationMemPtr, spriteChain.GetSpriteData(), copiedSize);
currentAllocationMemPtr += copiedSize;
firstQuadIndex += copiedQuadCount;
currentDrawCall->quadCount += copiedQuadCount;
remainingQuads -= copiedQuadCount;
// If there's still data to copy, it means buffer is full, flush it
if (remainingQuads > 0)
Flush();
}
}
//TODO: Add Finish()/PrepareEnd() call to allow to reuse buffers/draw calls for multiple Prepare calls
Flush();
const RenderSpriteChain* firstSpriteChain = static_cast<const RenderSpriteChain*>(elements[0]);
std::size_t drawCallCount = data.drawCalls.size() - oldDrawCallCount;
data.drawCallPerElement[firstSpriteChain] = SpriteChainRendererData::DrawCallIndices{ oldDrawCallCount, drawCallCount };
if (!pendingCopies.empty())
{
currentFrame.Execute([&](CommandBufferBuilder& builder)
{
for (auto&& [allocation, buffer] : pendingCopies)
builder.CopyBuffer(*allocation, buffer);
builder.PostTransferBarrier();
}, Nz::QueueType::Transfer);
}
}
void SpriteChainRenderer::Render(ElementRendererData& rendererData, CommandBufferBuilder& commandBuffer, const Pointer<const RenderElement>* elements, std::size_t elementCount)
{
auto& data = static_cast<SpriteChainRendererData&>(rendererData);
commandBuffer.BindIndexBuffer(*m_indexBuffer);
const AbstractBuffer* currentVertexBuffer = nullptr;
const RenderPipeline* currentPipeline = nullptr;
const ShaderBinding* currentInstanceBinding = nullptr;
const ShaderBinding* currentMaterialBinding = nullptr;
const RenderSpriteChain* firstSpriteChain = static_cast<const RenderSpriteChain*>(elements[0]);
auto it = data.drawCallPerElement.find(firstSpriteChain);
assert(it != data.drawCallPerElement.end());
const auto& indices = it->second;
for (std::size_t i = 0; i < indices.count; ++i)
{
const auto& drawCall = data.drawCalls[indices.start + i];
if (currentVertexBuffer != drawCall.vertexBuffer)
{
commandBuffer.BindVertexBuffer(0, *drawCall.vertexBuffer);
currentVertexBuffer = drawCall.vertexBuffer;
}
if (currentPipeline != drawCall.renderPipeline)
{
commandBuffer.BindPipeline(*drawCall.renderPipeline);
currentPipeline = drawCall.renderPipeline;
}
if (currentMaterialBinding != drawCall.materialBinding)
{
commandBuffer.BindShaderBinding(Graphics::MaterialBindingSet, *drawCall.materialBinding);
currentMaterialBinding = drawCall.materialBinding;
}
if (currentInstanceBinding != drawCall.instanceBinding)
{
commandBuffer.BindShaderBinding(Graphics::WorldBindingSet, *drawCall.instanceBinding);
currentInstanceBinding = drawCall.instanceBinding;
}
commandBuffer.DrawIndexed(drawCall.quadCount * 6, 1U, drawCall.firstIndex);
}
}
void SpriteChainRenderer::Reset(ElementRendererData& rendererData, RenderFrame& currentFrame)
{
auto& data = static_cast<SpriteChainRendererData&>(rendererData);
// TODO: Reuse vertex buffers
for (auto& vertexBufferPtr : data.vertexBuffers)
currentFrame.PushForRelease(std::move(vertexBufferPtr));
data.drawCalls.clear();
data.vertexBuffers.clear();
}
}

View File

@@ -10,7 +10,12 @@
namespace Nz
{
void SubmeshRenderer::Render(CommandBufferBuilder& commandBuffer, const Pointer<const RenderElement>* elements, std::size_t elementCount)
std::unique_ptr<ElementRendererData> SubmeshRenderer::InstanciateData()
{
return {};
}
void SubmeshRenderer::Render(ElementRendererData& /*rendererData*/, CommandBufferBuilder& commandBuffer, const Pointer<const RenderElement>* elements, std::size_t elementCount)
{
const AbstractBuffer* currentIndexBuffer = nullptr;
const AbstractBuffer* currentVertexBuffer = nullptr;