Integrate render target handling in frame graphs (#411)

* Graphics: Integrate RenderTarget in FrameGraph

- This handles the blit to texture/swapchain in the FrameGraph and fixes RenderTextureBlit
- Dummy attachments were added to the FrameGraph class to handle link without texture (used to setup a dependency between two passes with no texture)
- FramePass now supports custom access/layout/usage for inputs

* Graphics/RenderTarget: Allow to set any RenderTarget as output
This commit is contained in:
Jérôme Leclercq 2023-11-28 21:00:57 +01:00 committed by GitHub
parent f57fc3c1d5
commit 32d227628c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 490 additions and 340 deletions

View File

@ -28,6 +28,7 @@
#include <NazaraUtils/MemoryPool.hpp> #include <NazaraUtils/MemoryPool.hpp>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <span>
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
@ -80,12 +81,14 @@ namespace Nz
ForwardFramePipeline& operator=(ForwardFramePipeline&&) = delete; ForwardFramePipeline& operator=(ForwardFramePipeline&&) = delete;
private: private:
struct ViewerData;
BakedFrameGraph BuildFrameGraph(); BakedFrameGraph BuildFrameGraph();
void RegisterMaterialInstance(MaterialInstance* materialPass); void RegisterMaterialInstance(MaterialInstance* materialPass);
void UnregisterMaterialInstance(MaterialInstance* material); void UnregisterMaterialInstance(MaterialInstance* material);
struct ViewerData; static std::size_t BuildMergePass(FrameGraph& frameGraph, std::span<ViewerData*> targetViewers);
struct LightData struct LightData
{ {
@ -119,7 +122,6 @@ namespace Nz
struct RenderTargetData struct RenderTargetData
{ {
std::size_t finalAttachment;
std::vector<const ViewerData*> viewers; std::vector<const ViewerData*> viewers;
}; };

View File

@ -15,6 +15,7 @@
#include <Nazara/Graphics/FramePassAttachment.hpp> #include <Nazara/Graphics/FramePassAttachment.hpp>
#include <Nazara/Renderer/Enums.hpp> #include <Nazara/Renderer/Enums.hpp>
#include <Nazara/Renderer/RenderPass.hpp> #include <Nazara/Renderer/RenderPass.hpp>
#include <limits>
#include <optional> #include <optional>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
@ -26,6 +27,8 @@ namespace Nz
{ {
class NAZARA_GRAPHICS_API FrameGraph class NAZARA_GRAPHICS_API FrameGraph
{ {
friend class BakedFrameGraph;
public: public:
FrameGraph() = default; FrameGraph() = default;
FrameGraph(const FrameGraph&) = delete; FrameGraph(const FrameGraph&) = delete;
@ -38,6 +41,7 @@ namespace Nz
inline std::size_t AddAttachmentCube(FramePassAttachment attachment); inline std::size_t AddAttachmentCube(FramePassAttachment attachment);
inline std::size_t AddAttachmentCubeFace(std::size_t attachmentId, CubemapFace face); inline std::size_t AddAttachmentCubeFace(std::size_t attachmentId, CubemapFace face);
inline std::size_t AddAttachmentProxy(std::string name, std::size_t attachmentId); inline std::size_t AddAttachmentProxy(std::string name, std::size_t attachmentId);
inline std::size_t AddDummyAttachment();
inline FramePass& AddPass(std::string name); inline FramePass& AddPass(std::string name);
inline void AddOutput(std::size_t attachmentIndex); inline void AddOutput(std::size_t attachmentIndex);
@ -88,6 +92,10 @@ namespace Nz
TextureLayout layout; TextureLayout layout;
}; };
struct DummyAttachment
{
};
struct PassBarriers struct PassBarriers
{ {
std::vector<Barrier> invalidationBarriers; std::vector<Barrier> invalidationBarriers;
@ -138,7 +146,10 @@ namespace Nz
void ReorderPasses(); void ReorderPasses();
void TraverseGraph(std::size_t passIndex); void TraverseGraph(std::size_t passIndex);
using AttachmentType = std::variant<FramePassAttachment, AttachmentProxy, AttachmentArray, AttachmentCube, AttachmentLayer>; using AttachmentType = std::variant<FramePassAttachment, AttachmentProxy, AttachmentArray, AttachmentCube, AttachmentLayer, DummyAttachment>;
static constexpr std::size_t InvalidAttachmentIndex = std::numeric_limits<std::size_t>::max();
static constexpr std::size_t InvalidTextureIndex = std::numeric_limits<std::size_t>::max();
std::vector<std::size_t> m_graphOutputs; std::vector<std::size_t> m_graphOutputs;
std::vector<FramePass> m_framePasses; std::vector<FramePass> m_framePasses;

View File

@ -78,6 +78,14 @@ namespace Nz
return id; return id;
} }
inline std::size_t FrameGraph::AddDummyAttachment()
{
std::size_t id = m_attachments.size();
m_attachments.emplace_back(DummyAttachment{});
return id;
}
inline FramePass& FrameGraph::AddPass(std::string name) inline FramePass& FrameGraph::AddPass(std::string name)
{ {
std::size_t id = m_framePasses.size(); std::size_t id = m_framePasses.size();

View File

@ -73,7 +73,9 @@ namespace Nz
inline void SetDepthStencilInput(std::size_t attachmentId); inline void SetDepthStencilInput(std::size_t attachmentId);
inline void SetDepthStencilOutput(std::size_t attachmentId); inline void SetDepthStencilOutput(std::size_t attachmentId);
inline void SetExecutionCallback(ExecutionCallback callback); inline void SetExecutionCallback(ExecutionCallback callback);
inline void SetInputLayout(std::size_t inputIndex, TextureLayout layout); inline void SetInputAccess(std::size_t inputIndex, TextureLayout layout, PipelineStageFlags stageFlags, MemoryAccessFlags accessFlags);
inline void SetInputAssumedLayout(std::size_t inputIndex, TextureLayout layout);
inline void SetInputUsage(std::size_t inputIndex, TextureUsageFlags usageFlags);
inline void SetReadInput(std::size_t inputIndex, bool doesRead); inline void SetReadInput(std::size_t inputIndex, bool doesRead);
FramePass& operator=(const FramePass&) = delete; FramePass& operator=(const FramePass&) = delete;
@ -90,7 +92,11 @@ namespace Nz
struct Input struct Input
{ {
std::optional<TextureLayout> assumedLayout; std::optional<TextureLayout> assumedLayout;
std::optional<TextureUsageFlags> textureUsageFlags;
std::size_t attachmentId; std::size_t attachmentId;
MemoryAccessFlags accessFlags = MemoryAccess::ShaderRead;
PipelineStageFlags stageFlags = PipelineStage::FragmentShader;
TextureLayout layout = TextureLayout::ColorInput;
bool doesRead = true; bool doesRead = true;
}; };

View File

@ -127,12 +127,26 @@ namespace Nz
m_executionCallback = std::move(callback); m_executionCallback = std::move(callback);
} }
inline void FramePass::SetInputLayout(std::size_t inputIndex, TextureLayout layout) inline void FramePass::SetInputAccess(std::size_t inputIndex, TextureLayout layout, PipelineStageFlags stageFlags, MemoryAccessFlags accessFlags)
{
assert(inputIndex < m_inputs.size());
m_inputs[inputIndex].accessFlags = accessFlags;
m_inputs[inputIndex].layout = layout;
m_inputs[inputIndex].stageFlags = stageFlags;
}
inline void FramePass::SetInputAssumedLayout(std::size_t inputIndex, TextureLayout layout)
{ {
assert(inputIndex < m_inputs.size()); assert(inputIndex < m_inputs.size());
m_inputs[inputIndex].assumedLayout = layout; m_inputs[inputIndex].assumedLayout = layout;
} }
inline void FramePass::SetInputUsage(std::size_t inputIndex, TextureUsageFlags usageFlags)
{
assert(inputIndex < m_inputs.size());
m_inputs[inputIndex].textureUsageFlags = usageFlags;
}
inline void FramePass::SetReadInput(std::size_t inputIndex, bool doesRead) inline void FramePass::SetReadInput(std::size_t inputIndex, bool doesRead)
{ {
assert(inputIndex < m_inputs.size()); assert(inputIndex < m_inputs.size());

View File

@ -19,22 +19,33 @@ namespace Nz
class Framebuffer; class Framebuffer;
class FrameGraph; class FrameGraph;
class RenderPass; class RenderPass;
class Texture;
class RenderResources; class RenderResources;
class Texture;
class NAZARA_GRAPHICS_API RenderTarget class NAZARA_GRAPHICS_API RenderTarget
{ {
public: public:
RenderTarget() = default; inline RenderTarget(Int32 renderOrder = 0);
virtual ~RenderTarget(); virtual ~RenderTarget();
virtual void OnBuildGraph(FrameGraph& frameGraph, std::size_t attachmentIndex) const = 0; inline Int32 GetRenderOrder() const;
virtual void OnRenderEnd(RenderResources& resources, const BakedFrameGraph& frameGraph, std::size_t finalAttachment) const = 0;
virtual const Vector2ui& GetSize() const = 0; virtual const Vector2ui& GetSize() const = 0;
inline bool IsFrameGraphOutput() const;
virtual std::size_t OnBuildGraph(FrameGraph& frameGraph, std::size_t attachmentIndex) const = 0;
inline void SetFrameGraphOutput(bool output = true);
inline void UpdateRenderOrder(Int32 renderOrder);
NazaraSignal(OnRenderTargetRelease, const RenderTarget* /*renderTarget*/); NazaraSignal(OnRenderTargetRelease, const RenderTarget* /*renderTarget*/);
NazaraSignal(OnRenderTargetRenderOrderChange, const RenderTarget* /*renderTarget*/, Int32 /*newOrder*/);
NazaraSignal(OnRenderTargetSizeChange, const RenderTarget* /*renderTarget*/, const Vector2ui& /*newSize*/); NazaraSignal(OnRenderTargetSizeChange, const RenderTarget* /*renderTarget*/, const Vector2ui& /*newSize*/);
private:
Int32 m_renderOrder;
bool m_frameGraphOutput;
}; };
} }

View File

@ -6,6 +6,32 @@
namespace Nz namespace Nz
{ {
inline RenderTarget::RenderTarget(Int32 renderOrder) :
m_renderOrder(renderOrder),
m_frameGraphOutput(false)
{
}
inline Int32 RenderTarget::GetRenderOrder() const
{
return m_renderOrder;
}
inline bool RenderTarget::IsFrameGraphOutput() const
{
return m_frameGraphOutput;
}
inline void RenderTarget::SetFrameGraphOutput(bool output)
{
m_frameGraphOutput = output;
}
inline void RenderTarget::UpdateRenderOrder(Int32 renderOrder)
{
OnRenderTargetRenderOrderChange(this, renderOrder);
m_renderOrder = renderOrder;
}
} }
#include <Nazara/Graphics/DebugOff.hpp> #include <Nazara/Graphics/DebugOff.hpp>

View File

@ -24,8 +24,7 @@ namespace Nz
RenderTexture(RenderTexture&&) = delete; RenderTexture(RenderTexture&&) = delete;
~RenderTexture() = default; ~RenderTexture() = default;
void OnBuildGraph(FrameGraph& graph, std::size_t attachmentIndex) const override; std::size_t OnBuildGraph(FrameGraph& graph, std::size_t attachmentIndex) const override;
void OnRenderEnd(RenderResources& resources, const BakedFrameGraph& frameGraph, std::size_t finalAttachment) const override;
const Vector2ui& GetSize() const override; const Vector2ui& GetSize() const override;

View File

@ -26,8 +26,7 @@ namespace Nz
RenderTextureBlit(RenderTextureBlit&&) = delete; RenderTextureBlit(RenderTextureBlit&&) = delete;
~RenderTextureBlit() = default; ~RenderTextureBlit() = default;
void OnBuildGraph(FrameGraph& graph, std::size_t attachmentIndex) const override; std::size_t OnBuildGraph(FrameGraph& graph, std::size_t attachmentIndex) const override;
void OnRenderEnd(RenderResources& resources, const BakedFrameGraph& frameGraph, std::size_t finalAttachment) const override;
const Vector2ui& GetSize() const override; const Vector2ui& GetSize() const override;

View File

@ -25,14 +25,15 @@ namespace Nz
RenderWindow(RenderWindow&&) = delete; RenderWindow(RenderWindow&&) = delete;
~RenderWindow() = default; ~RenderWindow() = default;
void OnBuildGraph(FrameGraph& graph, std::size_t attachmentIndex) const override; std::size_t OnBuildGraph(FrameGraph& graph, std::size_t attachmentIndex) const override;
void OnRenderEnd(RenderResources& renderResources, const BakedFrameGraph& frameGraph, std::size_t attachmentId) const override;
const Vector2ui& GetSize() const override; const Vector2ui& GetSize() const override;
RenderWindow& operator=(const RenderWindow&) = delete; RenderWindow& operator=(const RenderWindow&) = delete;
RenderWindow& operator=(RenderWindow&&) = delete; RenderWindow& operator=(RenderWindow&&) = delete;
static constexpr Int32 DefaultRenderOrder = 1000;
private: private:
void SetSwapchain(Swapchain* swapchain); void SetSwapchain(Swapchain* swapchain);

View File

@ -7,6 +7,7 @@
namespace Nz namespace Nz
{ {
inline RenderWindow::RenderWindow(Swapchain& swapchain) : inline RenderWindow::RenderWindow(Swapchain& swapchain) :
RenderTarget(DefaultRenderOrder),
m_swapchain(&swapchain), m_swapchain(&swapchain),
m_windowSwapchain(nullptr) m_windowSwapchain(nullptr)
{ {

View File

@ -3,9 +3,9 @@
// For conditions of distribution and use, see copyright notice in Config.hpp // For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Graphics/BakedFrameGraph.hpp> #include <Nazara/Graphics/BakedFrameGraph.hpp>
#include <Nazara/Graphics/FrameGraph.hpp>
#include <Nazara/Graphics/Graphics.hpp> #include <Nazara/Graphics/Graphics.hpp>
#include <Nazara/Renderer/CommandBufferBuilder.hpp> #include <Nazara/Renderer/CommandBufferBuilder.hpp>
#include <Nazara/Renderer/RenderFrame.hpp>
#include <Nazara/Graphics/Debug.hpp> #include <Nazara/Graphics/Debug.hpp>
namespace Nz namespace Nz
@ -60,7 +60,8 @@ namespace Nz
builder.TextureBarrier(textureTransition.srcStageMask, textureTransition.dstStageMask, textureTransition.srcAccessMask, textureTransition.dstAccessMask, textureTransition.oldLayout, textureTransition.newLayout, *texture); builder.TextureBarrier(textureTransition.srcStageMask, textureTransition.dstStageMask, textureTransition.srcAccessMask, textureTransition.dstAccessMask, textureTransition.oldLayout, textureTransition.newLayout, *texture);
} }
builder.BeginRenderPass(*passData.framebuffer, *passData.renderPass, passData.renderRect, passData.outputClearValues.data(), passData.outputClearValues.size()); if (passData.framebuffer)
builder.BeginRenderPass(*passData.framebuffer, *passData.renderPass, passData.renderRect, passData.outputClearValues.data(), passData.outputClearValues.size());
if (!passData.name.empty()) if (!passData.name.empty())
builder.BeginDebugRegion(passData.name, Color::Green()); builder.BeginDebugRegion(passData.name, Color::Green());
@ -85,7 +86,8 @@ namespace Nz
if (!passData.name.empty()) if (!passData.name.empty())
builder.EndDebugRegion(); builder.EndDebugRegion();
builder.EndRenderPass(); if (passData.framebuffer)
builder.EndRenderPass();
}); });
passData.forceCommandBufferRegeneration = false; passData.forceCommandBufferRegeneration = false;
@ -102,7 +104,7 @@ namespace Nz
const std::shared_ptr<Texture>& BakedFrameGraph::GetAttachmentTexture(std::size_t attachmentIndex) const const std::shared_ptr<Texture>& BakedFrameGraph::GetAttachmentTexture(std::size_t attachmentIndex) const
{ {
auto it = m_attachmentToTextureMapping.find(attachmentIndex); auto it = m_attachmentToTextureMapping.find(attachmentIndex);
if (it == m_attachmentToTextureMapping.end()) if (it == m_attachmentToTextureMapping.end() || it->second == FrameGraph::InvalidTextureIndex)
{ {
static std::shared_ptr<Texture> dummy; static std::shared_ptr<Texture> dummy;
return dummy; return dummy;
@ -116,7 +118,7 @@ namespace Nz
const std::shared_ptr<RenderPass>& BakedFrameGraph::GetRenderPass(std::size_t passIndex) const const std::shared_ptr<RenderPass>& BakedFrameGraph::GetRenderPass(std::size_t passIndex) const
{ {
auto it = m_attachmentToTextureMapping.find(passIndex); auto it = m_attachmentToTextureMapping.find(passIndex);
if (it == m_attachmentToTextureMapping.end()) if (it == m_attachmentToTextureMapping.end() || it->second == FrameGraph::InvalidTextureIndex)
{ {
static std::shared_ptr<RenderPass> dummy; static std::shared_ptr<RenderPass> dummy;
return dummy; return dummy;
@ -224,24 +226,29 @@ namespace Nz
{ {
textures.clear(); textures.clear();
unsigned int framebufferWidth = std::numeric_limits<unsigned int>::max(); if (!passData.outputTextureIndices.empty())
unsigned int framebufferHeight = std::numeric_limits<unsigned int>::max();
for (std::size_t textureId : passData.outputTextureIndices)
{ {
auto& textureData = m_textures[textureId]; unsigned int framebufferWidth = std::numeric_limits<unsigned int>::max();
textures.push_back(textureData.texture); unsigned int framebufferHeight = std::numeric_limits<unsigned int>::max();
for (std::size_t textureId : passData.outputTextureIndices)
{
auto& textureData = m_textures[textureId];
textures.push_back(textureData.texture);
auto [width, height] = ComputeTextureSize(textureData); auto [width, height] = ComputeTextureSize(textureData);
framebufferWidth = std::min(framebufferWidth, width); framebufferWidth = std::min(framebufferWidth, width);
framebufferHeight = std::min(framebufferHeight, height); framebufferHeight = std::min(framebufferHeight, height);
}
passData.renderRect = Recti(0, 0, int(framebufferWidth), int(framebufferHeight));
passData.framebuffer = renderDevice->InstantiateFramebuffer(framebufferWidth, framebufferHeight, passData.renderPass, textures);
if (!passData.name.empty())
passData.framebuffer->UpdateDebugName(passData.name);
} }
else
passData.renderRect = Recti(0, 0, int(framebufferWidth), int(framebufferHeight)); passData.renderRect = Recti(0, 0, -1, -1);
passData.framebuffer = renderDevice->InstantiateFramebuffer(framebufferWidth, framebufferHeight, passData.renderPass, textures);
if (!passData.name.empty())
passData.framebuffer->UpdateDebugName(passData.name);
passData.forceCommandBufferRegeneration = true; passData.forceCommandBufferRegeneration = true;
} }

View File

@ -217,7 +217,7 @@ namespace Nz
PerViewerData& viewerData = *Retrieve(m_viewerData, viewer); PerViewerData& viewerData = *Retrieve(m_viewerData, viewer);
std::size_t arrayInputIndex = pass.AddInput(viewerData.textureArrayAttachmentIndex); std::size_t arrayInputIndex = pass.AddInput(viewerData.textureArrayAttachmentIndex);
pass.SetInputLayout(arrayInputIndex, TextureLayout::ColorInput); pass.SetInputAssumedLayout(arrayInputIndex, TextureLayout::ColorInput);
for (CascadeData& cascade : viewerData.cascades) for (CascadeData& cascade : viewerData.cascades)
pass.AddInput(cascade.attachmentIndex); pass.AddInput(cascade.attachmentIndex);

View File

@ -383,21 +383,26 @@ namespace Nz
} }
frameGraphInvalidated |= m_bakedFrameGraph.Resize(renderResources, viewerSizes); frameGraphInvalidated |= m_bakedFrameGraph.Resize(renderResources, viewerSizes);
if (frameGraphInvalidated)
{
for (ViewerData& viewerData : m_viewerPool)
{
if (viewerData.blitShaderBinding)
renderResources.PushForRelease(std::move(viewerData.blitShaderBinding));
}
}
// Find active lights (i.e. visible in any frustum) // Find active lights (i.e. visible in any frustum)
m_activeLights.Clear(); m_activeLights.Clear();
for (auto& viewerData : m_viewerPool) for (ViewerData* viewerData : m_orderedViewers)
{ {
if (viewerData.pendingDestruction) UInt32 renderMask = viewerData->viewer->GetRenderMask();
continue;
UInt32 renderMask = viewerData.viewer->GetRenderMask();
// Extract frustum from viewproj matrix // Extract frustum from viewproj matrix
const Matrix4f& viewProjMatrix = viewerData.viewer->GetViewerInstance().GetViewProjMatrix(); const Matrix4f& viewProjMatrix = viewerData->viewer->GetViewerInstance().GetViewProjMatrix();
viewerData.frame.frustum = Frustumf::Extract(viewProjMatrix); viewerData->frame.frustum = Frustumf::Extract(viewProjMatrix);
viewerData.frame.visibleLights.Clear(); viewerData->frame.visibleLights.Clear();
for (auto it = m_lightPool.begin(); it != m_lightPool.end(); ++it) for (auto it = m_lightPool.begin(); it != m_lightPool.end(); ++it)
{ {
const LightData& lightData = *it; const LightData& lightData = *it;
@ -407,7 +412,7 @@ namespace Nz
continue; continue;
m_activeLights.UnboundedSet(lightIndex); m_activeLights.UnboundedSet(lightIndex);
viewerData.frame.visibleLights.UnboundedSet(lightIndex); viewerData->frame.visibleLights.UnboundedSet(lightIndex);
} }
} }
@ -422,61 +427,34 @@ namespace Nz
} }
// Viewer handling (second pass) // Viewer handling (second pass)
for (auto& viewerData : m_viewerPool) for (ViewerData* viewerData : m_orderedViewers)
{ {
if (viewerData.pendingDestruction) UInt32 renderMask = viewerData->viewer->GetRenderMask();
continue;
UInt32 renderMask = viewerData.viewer->GetRenderMask();
// Per-viewer shadow map handling // Per-viewer shadow map handling
for (std::size_t lightIndex : viewerData.frame.visibleLights.IterBits()) for (std::size_t lightIndex : viewerData->frame.visibleLights.IterBits())
{ {
LightData* lightData = m_lightPool.RetrieveFromIndex(lightIndex); LightData* lightData = m_lightPool.RetrieveFromIndex(lightIndex);
if (lightData->shadowData && lightData->shadowData->IsPerViewer() && (renderMask & lightData->renderMask) != 0) if (lightData->shadowData && lightData->shadowData->IsPerViewer() && (renderMask & lightData->renderMask) != 0)
lightData->shadowData->PrepareRendering(renderResources, viewerData.viewer); lightData->shadowData->PrepareRendering(renderResources, viewerData->viewer);
} }
// Frustum culling // Frustum culling
std::size_t visibilityHash = 5; std::size_t visibilityHash = 5;
const auto& visibleRenderables = FrustumCull(viewerData.frame.frustum, renderMask, visibilityHash); const auto& visibleRenderables = FrustumCull(viewerData->frame.frustum, renderMask, visibilityHash);
FramePipelinePass::FrameData passData = { FramePipelinePass::FrameData passData = {
&viewerData.frame.visibleLights, &viewerData->frame.visibleLights,
viewerData.frame.frustum, viewerData->frame.frustum,
renderResources, renderResources,
visibleRenderables, visibleRenderables,
visibilityHash visibilityHash
}; };
for (auto& passPtr : viewerData.passes) for (auto& passPtr : viewerData->passes)
passPtr->Prepare(passData); passPtr->Prepare(passData);
} }
if (frameGraphInvalidated)
{
const std::shared_ptr<TextureSampler>& sampler = graphics->GetSamplerCache().Get({});
for (auto& viewerData : m_viewerPool)
{
if (viewerData.pendingDestruction)
continue;
if (viewerData.blitShaderBinding)
renderResources.PushForRelease(std::move(viewerData.blitShaderBinding));
viewerData.blitShaderBinding = graphics->GetBlitPipelineLayout()->AllocateShaderBinding(0);
viewerData.blitShaderBinding->Update({
{
0,
ShaderBinding::SampledTextureBinding {
m_bakedFrameGraph.GetAttachmentTexture(viewerData.finalColorAttachment).get(),
sampler.get()
}
}
});
}
}
// Update UBOs and materials // Update UBOs and materials
renderResources.Execute([&](CommandBufferBuilder& builder) renderResources.Execute([&](CommandBufferBuilder& builder)
{ {
@ -498,10 +476,6 @@ namespace Nz
m_bakedFrameGraph.Execute(renderResources); m_bakedFrameGraph.Execute(renderResources);
m_rebuildFrameGraph = false; m_rebuildFrameGraph = false;
// Final blit (TODO: Make part of frame graph?)
for (auto&& [renderTargetPtr, renderTargetData] : m_renderTargets)
renderTargetPtr->OnRenderEnd(renderResources, m_bakedFrameGraph, renderTargetData.finalAttachment);
// reset at the end instead of the beginning so debug draw can be used before calling this method // reset at the end instead of the beginning so debug draw can be used before calling this method
DebugDrawer& debugDrawer = GetDebugDrawer(); DebugDrawer& debugDrawer = GetDebugDrawer();
debugDrawer.Reset(renderResources); debugDrawer.Reset(renderResources);
@ -648,6 +622,7 @@ namespace Nz
{ {
FrameGraph frameGraph; FrameGraph frameGraph;
// Register viewer-independent passes
for (std::size_t i : m_shadowCastingLights.IterBits()) for (std::size_t i : m_shadowCastingLights.IterBits())
{ {
LightData* lightData = m_lightPool.RetrieveFromIndex(i); LightData* lightData = m_lightPool.RetrieveFromIndex(i);
@ -655,149 +630,177 @@ namespace Nz
lightData->shadowData->RegisterToFrameGraph(frameGraph, nullptr); lightData->shadowData->RegisterToFrameGraph(frameGraph, nullptr);
} }
using ViewerPair = std::pair<const RenderTarget*, ViewerData*>; // Group every viewer by their render order and RenderTarget
m_orderedViewers.clear();
StackArray<ViewerPair> viewers = NazaraStackArray(ViewerPair, m_viewerPool.size());
auto viewerIt = viewers.begin();
for (auto& viewerData : m_viewerPool) for (auto& viewerData : m_viewerPool)
{ {
if (viewerData.pendingDestruction) if (viewerData.pendingDestruction)
continue; continue;
const RenderTarget& renderTarget = viewerData.viewer->GetRenderTarget(); m_orderedViewers.push_back(&viewerData);
*viewerIt++ = std::make_pair(&renderTarget, &viewerData);
} }
std::sort(viewers.begin(), viewers.end(), [](const ViewerPair& lhs, const ViewerPair& rhs) if (m_orderedViewers.empty())
return frameGraph.Bake();
std::sort(m_orderedViewers.begin(), m_orderedViewers.end(), [](ViewerData* lhs, ViewerData* rhs)
{ {
return lhs.second->renderOrder < rhs.second->renderOrder; // Order by RenderTarget render order and then by viewer render order
Int32 leftTargetRenderOrder1 = lhs->viewer->GetRenderTarget().GetRenderOrder();
Int32 rightTargetRenderOrder1 = rhs->viewer->GetRenderTarget().GetRenderOrder();
if (leftTargetRenderOrder1 == rightTargetRenderOrder1)
return leftTargetRenderOrder1 < rightTargetRenderOrder1;
else
return lhs->renderOrder < rhs->renderOrder;
}); });
StackVector<std::size_t> dependenciesColorAttachments = NazaraStackVector(std::size_t, viewers.size()); StackVector<std::size_t> dependenciesColorAttachments = NazaraStackVector(std::size_t, m_orderedViewers.size());
std::size_t dependenciesColorAttachmentCount = 0;
Int32 lastRenderOrder = m_orderedViewers.front()->viewer->GetRenderTarget().GetRenderOrder();
m_orderedViewers.clear(); std::size_t viewerIndex = 0;
m_renderTargets.clear(); auto HandleRenderTarget = [&](const RenderTarget& renderTarget, std::span<ViewerData*> viewers)
unsigned int viewerIndex = 0;
for (auto it = viewers.begin(), prevIt = it; it != viewers.end(); ++it)
{ {
auto&& [renderTarget, viewerData] = *it; if (renderTarget.GetRenderOrder() > lastRenderOrder)
UInt32 renderMask = viewerData->viewer->GetRenderMask();
for (std::size_t i : m_shadowCastingLights.IterBits())
{ {
LightData* lightData = m_lightPool.RetrieveFromIndex(i); dependenciesColorAttachmentCount = dependenciesColorAttachments.size();
if (lightData->shadowData->IsPerViewer() && (renderMask & lightData->renderMask) != 0) lastRenderOrder = renderTarget.GetRenderOrder();
lightData->shadowData->RegisterToFrameGraph(frameGraph, viewerData->viewer);
} }
// Keep track of previous dependencies attachments (from viewers having a smaller render order) assert(!viewers.empty());
Int32 renderOrder = viewerData->renderOrder;
for (auto it2 = prevIt; prevIt != it; ++prevIt)
{
ViewerData* prevViewerData = prevIt->second;
Int32 prevRenderOrder = prevViewerData->renderOrder;
if (prevRenderOrder >= renderOrder)
break;
dependenciesColorAttachments.push_back(prevViewerData->finalColorAttachment); for (ViewerData* viewerData : viewers)
prevIt = it2;
}
auto framePassCallback = [&, viewerData = viewerData](std::size_t /*passIndex*/, FramePass& framePass, FramePipelinePassFlags flags)
{ {
// Inject previous final attachments as inputs for all passes, to force framegraph to order viewers passes relative to each other UInt32 renderMask = viewerData->viewer->GetRenderMask();
// TODO: Allow the user to define which pass of viewer A uses viewer B rendering for (std::size_t i : m_shadowCastingLights.IterBits())
for (std::size_t finalAttachment : dependenciesColorAttachments)
{ {
std::size_t inputIndex = framePass.AddInput(finalAttachment); LightData* lightData = m_lightPool.RetrieveFromIndex(i);
if (lightData->shadowData->IsPerViewer() && (renderMask & lightData->renderMask) != 0)
// Disable ReadInput to prevent the framegraph from transitionning the texture layout (for now it's handled externally) lightData->shadowData->RegisterToFrameGraph(frameGraph, viewerData->viewer);
// (however if we manage to get rid of the texture blit from RenderTexture by making the framegraph use the external texture directly, this would be necessary)
framePass.SetReadInput(inputIndex, false);
} }
if (flags.Test(FramePipelinePassFlag::LightShadowing)) auto framePassCallback = [&, viewerData = viewerData](std::size_t /*passIndex*/, FramePass& framePass, FramePipelinePassFlags flags)
{ {
for (std::size_t i : m_shadowCastingLights.IterBits()) // Inject previous final attachments as inputs for all passes, to force framegraph to order viewers passes relative to each other
// TODO: Allow the user to define which pass of viewer A uses viewer B rendering
for (std::size_t i = 0; i < dependenciesColorAttachmentCount; ++i)
framePass.AddInput(dependenciesColorAttachments[i]);
if (flags.Test(FramePipelinePassFlag::LightShadowing))
{ {
LightData* lightData = m_lightPool.RetrieveFromIndex(i); for (std::size_t i : m_shadowCastingLights.IterBits())
if ((renderMask & lightData->renderMask) != 0)
lightData->shadowData->RegisterPassInputs(framePass, (lightData->shadowData->IsPerViewer()) ? viewerData->viewer : nullptr);
}
}
};
viewerData->finalColorAttachment = viewerData->viewer->RegisterPasses(viewerData->passes, frameGraph, viewerIndex++, framePassCallback);
// Group viewers by render targets
auto& renderTargetData = m_renderTargets[renderTarget];
renderTargetData.viewers.push_back(viewerData);
m_orderedViewers.push_back(viewerData);
}
for (auto&& [renderTarget, renderTargetData] : m_renderTargets)
{
const auto& targetViewers = renderTargetData.viewers;
if (targetViewers.size() > 1)
{
// Multiple viewers on the same targets, merge them
FramePass& mergePass = frameGraph.AddPass("Merge pass");
renderTargetData.finalAttachment = frameGraph.AddAttachment({
"Viewer output",
PixelFormat::RGBA8
});
for (const ViewerData* viewerData : targetViewers)
mergePass.AddInput(viewerData->finalColorAttachment);
mergePass.AddOutput(renderTargetData.finalAttachment);
mergePass.SetClearColor(0, Color::Black());
mergePass.SetCommandCallback([&targetViewers](CommandBufferBuilder& builder, const FramePassEnvironment& /*env*/)
{
Graphics* graphics = Graphics::Instance();
builder.BindRenderPipeline(*graphics->GetBlitPipeline(false));
bool first = true;
for (const ViewerData* viewerData : targetViewers)
{
Recti renderRect = viewerData->viewer->GetViewport();
builder.SetScissor(renderRect);
builder.SetViewport(renderRect);
const ShaderBindingPtr& blitShaderBinding = viewerData->blitShaderBinding;
builder.BindRenderShaderBinding(0, *blitShaderBinding);
builder.Draw(3);
if (first)
{ {
builder.BindRenderPipeline(*graphics->GetBlitPipeline(true)); LightData* lightData = m_lightPool.RetrieveFromIndex(i);
first = false; if ((renderMask & lightData->renderMask) != 0)
lightData->shadowData->RegisterPassInputs(framePass, (lightData->shadowData->IsPerViewer()) ? viewerData->viewer : nullptr);
} }
} }
}); };
renderTarget->OnBuildGraph(frameGraph, renderTargetData.finalAttachment); viewerData->finalColorAttachment = viewerData->viewer->RegisterPasses(viewerData->passes, frameGraph, viewerIndex++, framePassCallback);
} }
else if (targetViewers.size() == 1)
std::size_t finalAttachment;
if (viewers.size() > 1)
{
// Multiple viewers on the same targets, merge them
finalAttachment = renderTarget.OnBuildGraph(frameGraph, BuildMergePass(frameGraph, viewers));
}
else
{ {
// Single viewer on that target // Single viewer on that target
const auto& viewer = *targetViewers.front(); const auto& viewer = *viewers.front();
finalAttachment = renderTarget.OnBuildGraph(frameGraph, viewer.finalColorAttachment);
}
renderTargetData.finalAttachment = viewer.finalColorAttachment; if (!renderTarget.IsFrameGraphOutput())
renderTarget->OnBuildGraph(frameGraph, renderTargetData.finalAttachment); {
// Keep track of previous dependencies attachments
dependenciesColorAttachments.push_back(finalAttachment);
}
else
frameGraph.AddOutput(finalAttachment);
};
const RenderTarget* currentTarget = &m_orderedViewers.front()->viewer->GetRenderTarget();
std::size_t currentTargetIndex = 0;
for (std::size_t i = 1; i < m_orderedViewers.size(); ++i)
{
const RenderTarget* target = &m_orderedViewers[i]->viewer->GetRenderTarget();
if (currentTarget != target)
{
HandleRenderTarget(*currentTarget, std::span(&m_orderedViewers[currentTargetIndex], &m_orderedViewers[i]));
currentTarget = target;
currentTargetIndex = i;
} }
} }
HandleRenderTarget(*currentTarget, std::span(m_orderedViewers.data() + currentTargetIndex, m_orderedViewers.data() + m_orderedViewers.size()));
return frameGraph.Bake(); return frameGraph.Bake();
} }
std::size_t ForwardFramePipeline::BuildMergePass(FrameGraph& frameGraph, std::span<ViewerData*> targetViewers)
{
FramePass& mergePass = frameGraph.AddPass("Merge pass");
std::size_t mergedAttachment = frameGraph.AddAttachment({
"Merged output",
PixelFormat::RGBA8
});
for (const ViewerData* viewerData : targetViewers)
mergePass.AddInput(viewerData->finalColorAttachment);
mergePass.AddOutput(mergedAttachment);
mergePass.SetClearColor(0, Color::Black());
mergePass.SetCommandCallback([&targetViewers](CommandBufferBuilder& builder, const FramePassEnvironment& env)
{
Graphics* graphics = Graphics::Instance();
builder.BindRenderPipeline(*graphics->GetBlitPipeline(false));
bool first = true;
for (ViewerData* viewerData : targetViewers)
{
Recti renderRect = viewerData->viewer->GetViewport();
builder.SetScissor(renderRect);
builder.SetViewport(renderRect);
if (!viewerData->blitShaderBinding)
{
const std::shared_ptr<TextureSampler>& sampler = graphics->GetSamplerCache().Get({});
viewerData->blitShaderBinding = graphics->GetBlitPipelineLayout()->AllocateShaderBinding(0);
viewerData->blitShaderBinding->Update({
{
0,
ShaderBinding::SampledTextureBinding {
env.frameGraph.GetAttachmentTexture(viewerData->finalColorAttachment).get(),
sampler.get()
}
}
});
}
const ShaderBindingPtr& blitShaderBinding = viewerData->blitShaderBinding;
builder.BindRenderShaderBinding(0, *blitShaderBinding);
builder.Draw(3);
if (first)
{
builder.BindRenderPipeline(*graphics->GetBlitPipeline(true));
first = false;
}
}
});
return mergedAttachment;
}
void ForwardFramePipeline::RegisterMaterialInstance(MaterialInstance* materialInstance) void ForwardFramePipeline::RegisterMaterialInstance(MaterialInstance* materialInstance)
{ {
auto it = m_materialInstances.find(materialInstance); auto it = m_materialInstances.find(materialInstance);

View File

@ -29,7 +29,7 @@ namespace Nz
BakedFrameGraph FrameGraph::Bake() BakedFrameGraph FrameGraph::Bake()
{ {
if (m_graphOutputs.empty()) if (m_graphOutputs.empty())
throw std::runtime_error("no backbuffer output has been set"); throw std::runtime_error("no graph output has been set");
m_pending.attachmentReadList.clear(); m_pending.attachmentReadList.clear();
m_pending.attachmentToTextures.clear(); m_pending.attachmentToTextures.clear();
@ -89,7 +89,8 @@ namespace Nz
for (std::size_t i = 0; i < colorOutputs.size(); ++i) for (std::size_t i = 0; i < colorOutputs.size(); ++i)
{ {
const auto& output = colorOutputs[i]; const auto& output = colorOutputs[i];
bakedPass.outputTextureIndices.push_back(Retrieve(m_pending.attachmentToTextures, output.attachmentId)); if (std::size_t textureIndex = Retrieve(m_pending.attachmentToTextures, output.attachmentId); textureIndex != InvalidTextureIndex)
bakedPass.outputTextureIndices.push_back(textureIndex);
if (output.clearColor) if (output.clearColor)
{ {
@ -111,9 +112,15 @@ namespace Nz
std::size_t attachmentId; std::size_t attachmentId;
if (attachmentId = framePass.GetDepthStencilOutput(); attachmentId != FramePass::InvalidAttachmentId) if (attachmentId = framePass.GetDepthStencilOutput(); attachmentId != FramePass::InvalidAttachmentId)
bakedPass.outputTextureIndices.push_back(Retrieve(m_pending.attachmentToTextures, attachmentId)); {
if (std::size_t textureIndex = Retrieve(m_pending.attachmentToTextures, attachmentId); textureIndex != InvalidTextureIndex)
bakedPass.outputTextureIndices.push_back(textureIndex);
}
else if (attachmentId = framePass.GetDepthStencilInput(); attachmentId != FramePass::InvalidAttachmentId) else if (attachmentId = framePass.GetDepthStencilInput(); attachmentId != FramePass::InvalidAttachmentId)
bakedPass.outputTextureIndices.push_back(Retrieve(m_pending.attachmentToTextures, attachmentId)); //< FIXME? {
if (std::size_t textureIndex = Retrieve(m_pending.attachmentToTextures, attachmentId); textureIndex != InvalidTextureIndex)
bakedPass.outputTextureIndices.push_back(textureIndex);
}
} }
} }
@ -199,25 +206,31 @@ namespace Nz
for (const auto& input : framePass.GetInputs()) for (const auto& input : framePass.GetInputs())
{ {
std::size_t textureId = RegisterTexture(input.attachmentId); std::size_t textureId = RegisterTexture(input.attachmentId);
if (textureId != InvalidTextureIndex)
FrameGraphTextureData& attachmentData = m_pending.textures[textureId]; {
attachmentData.usage |= TextureUsage::ShaderSampling; FrameGraphTextureData& attachmentData = m_pending.textures[textureId];
attachmentData.usage |= input.textureUsageFlags.value_or(TextureUsage::ShaderSampling);
}
} }
for (const auto& output : framePass.GetOutputs()) for (const auto& output : framePass.GetOutputs())
{ {
std::size_t textureId = RegisterTexture(output.attachmentId); std::size_t textureId = RegisterTexture(output.attachmentId);
if (textureId != InvalidTextureIndex)
FrameGraphTextureData& attachmentData = m_pending.textures[textureId]; {
attachmentData.usage |= TextureUsage::ColorAttachment; FrameGraphTextureData& attachmentData = m_pending.textures[textureId];
attachmentData.usage |= TextureUsage::ColorAttachment;
}
} }
if (std::size_t depthStencilInput = framePass.GetDepthStencilInput(); depthStencilInput != FramePass::InvalidAttachmentId) if (std::size_t depthStencilInput = framePass.GetDepthStencilInput(); depthStencilInput != FramePass::InvalidAttachmentId)
{ {
std::size_t textureId = RegisterTexture(depthStencilInput); std::size_t textureId = RegisterTexture(depthStencilInput);
if (textureId != InvalidTextureIndex)
FrameGraphTextureData& attachmentData = m_pending.textures[textureId]; {
attachmentData.usage |= TextureUsage::DepthStencilAttachment; FrameGraphTextureData& attachmentData = m_pending.textures[textureId];
attachmentData.usage |= TextureUsage::DepthStencilAttachment;
}
if (std::size_t depthStencilOutput = framePass.GetDepthStencilOutput(); depthStencilOutput != FramePass::InvalidAttachmentId) if (std::size_t depthStencilOutput = framePass.GetDepthStencilOutput(); depthStencilOutput != FramePass::InvalidAttachmentId)
{ {
@ -296,8 +309,11 @@ namespace Nz
auto it = m_pending.attachmentToTextures.find(output); auto it = m_pending.attachmentToTextures.find(output);
assert(it != m_pending.attachmentToTextures.end()); assert(it != m_pending.attachmentToTextures.end());
auto& finalTexture = m_pending.textures[it->second]; if (std::size_t textureIndex = it->second; textureIndex != InvalidTextureIndex)
finalTexture.usage |= TextureUsage::ShaderSampling | TextureUsage::TransferSource; {
auto& finalTexture = m_pending.textures[textureIndex];
finalTexture.usage |= TextureUsage::ShaderSampling | TextureUsage::TransferSource;
}
} }
// Apply texture view usage to their parents // Apply texture view usage to their parents
@ -316,13 +332,15 @@ namespace Nz
assert(m_pending.barrierList.empty()); assert(m_pending.barrierList.empty());
m_pending.barrierList.reserve(m_pending.passList.size()); m_pending.barrierList.reserve(m_pending.passList.size());
auto GetBarrier = [&](std::vector<Barrier>& barriers, std::size_t attachmentId) -> Barrier& auto GetBarrier = [&](std::vector<Barrier>& barriers, std::size_t attachmentId) -> Barrier*
{ {
std::size_t textureId = Retrieve(m_pending.attachmentToTextures, ResolveAttachmentIndex(attachmentId)); std::size_t textureId = Retrieve(m_pending.attachmentToTextures, ResolveAttachmentIndex(attachmentId));
if (textureId == InvalidTextureIndex)
return nullptr;
auto it = std::find_if(barriers.begin(), barriers.end(), [&](const Barrier& barrier) { return barrier.textureId == textureId; }); auto it = std::find_if(barriers.begin(), barriers.end(), [&](const Barrier& barrier) { return barrier.textureId == textureId; });
if (it != barriers.end()) if (it != barriers.end())
return *it; return &*it;
else else
{ {
// Insert a new barrier // Insert a new barrier
@ -330,7 +348,7 @@ namespace Nz
barrier.textureId = textureId; barrier.textureId = textureId;
barrier.layout = TextureLayout::Undefined; barrier.layout = TextureLayout::Undefined;
return barrier; return &barrier;
} }
}; };
@ -340,29 +358,35 @@ namespace Nz
auto& barriers = m_pending.barrierList.emplace_back(); auto& barriers = m_pending.barrierList.emplace_back();
auto GetInvalidationBarrier = [&](std::size_t attachmentId) -> Barrier& { return GetBarrier(barriers.invalidationBarriers, attachmentId); }; auto GetInvalidationBarrier = [&](std::size_t attachmentId) -> Barrier* { return GetBarrier(barriers.invalidationBarriers, attachmentId); };
auto GetFlushBarrier = [&](std::size_t attachmentId) -> Barrier& { return GetBarrier(barriers.flushBarriers, attachmentId); }; auto GetFlushBarrier = [&](std::size_t attachmentId) -> Barrier* { return GetBarrier(barriers.flushBarriers, attachmentId); };
for (const auto& input : framePass.GetInputs()) for (const auto& input : framePass.GetInputs())
{ {
auto& barrier = GetInvalidationBarrier(input.attachmentId); Barrier* barrier = GetInvalidationBarrier(input.attachmentId);
if (barrier.layout != TextureLayout::Undefined) if (!barrier)
continue;
if (barrier->layout != TextureLayout::Undefined)
throw std::runtime_error("layout mismatch"); throw std::runtime_error("layout mismatch");
barrier.access |= MemoryAccess::ShaderRead; barrier->access |= input.accessFlags;
barrier.stages |= PipelineStage::FragmentShader; barrier->stages |= input.stageFlags;
barrier.layout = TextureLayout::ColorInput; barrier->layout = input.layout;
} }
for (const auto& output : framePass.GetOutputs()) for (const auto& output : framePass.GetOutputs())
{ {
auto& barrier = GetFlushBarrier(output.attachmentId); Barrier* barrier = GetFlushBarrier(output.attachmentId);
if (barrier.layout != TextureLayout::Undefined) if (!barrier)
continue;
if (barrier->layout != TextureLayout::Undefined)
throw std::runtime_error("layout mismatch"); throw std::runtime_error("layout mismatch");
barrier.access |= MemoryAccess::ColorWrite; barrier->access |= MemoryAccess::ColorWrite;
barrier.stages |= PipelineStage::ColorOutput; barrier->stages |= PipelineStage::ColorOutput;
barrier.layout = TextureLayout::ColorOutput; barrier->layout = TextureLayout::ColorOutput;
} }
std::size_t dsInputAttachment = framePass.GetDepthStencilInput(); std::size_t dsInputAttachment = framePass.GetDepthStencilInput();
@ -371,40 +395,48 @@ namespace Nz
if (dsInputAttachment != FramePass::InvalidAttachmentId && dsOutputAttachement != FramePass::InvalidAttachmentId) if (dsInputAttachment != FramePass::InvalidAttachmentId && dsOutputAttachement != FramePass::InvalidAttachmentId)
{ {
// DS input/output // DS input/output
auto& invalidationBarrier = GetInvalidationBarrier(dsInputAttachment); if (Barrier* invalidationBarrier = GetInvalidationBarrier(dsInputAttachment))
if (invalidationBarrier.layout != TextureLayout::Undefined) {
throw std::runtime_error("layout mismatch"); if (invalidationBarrier->layout != TextureLayout::Undefined)
throw std::runtime_error("layout mismatch");
invalidationBarrier.layout = TextureLayout::DepthStencilReadWrite; invalidationBarrier->layout = TextureLayout::DepthStencilReadWrite;
invalidationBarrier.access = MemoryAccess::DepthStencilRead | MemoryAccess::DepthStencilWrite; invalidationBarrier->access = MemoryAccess::DepthStencilRead | MemoryAccess::DepthStencilWrite;
invalidationBarrier.stages = PipelineStage::FragmentTestsEarly | PipelineStage::FragmentTestsLate; invalidationBarrier->stages = PipelineStage::FragmentTestsEarly | PipelineStage::FragmentTestsLate;
}
auto& flushBarrier = GetFlushBarrier(dsOutputAttachement); if (Barrier* flushBarrier = GetFlushBarrier(dsOutputAttachement))
flushBarrier.layout = TextureLayout::DepthStencilReadWrite; {
flushBarrier.access = MemoryAccess::DepthStencilWrite; flushBarrier->layout = TextureLayout::DepthStencilReadWrite;
flushBarrier.stages = PipelineStage::FragmentTestsLate; flushBarrier->access = MemoryAccess::DepthStencilWrite;
flushBarrier->stages = PipelineStage::FragmentTestsLate;
}
} }
else if (dsInputAttachment != FramePass::InvalidAttachmentId) else if (dsInputAttachment != FramePass::InvalidAttachmentId)
{ {
// DS input-only // DS input-only
auto& invalidationBarrier = GetInvalidationBarrier(dsInputAttachment); if (Barrier* invalidationBarrier = GetInvalidationBarrier(dsInputAttachment))
if (invalidationBarrier.layout != TextureLayout::Undefined) {
throw std::runtime_error("layout mismatch"); if (invalidationBarrier->layout != TextureLayout::Undefined)
throw std::runtime_error("layout mismatch");
invalidationBarrier.layout = TextureLayout::DepthStencilReadWrite; invalidationBarrier->layout = TextureLayout::DepthStencilReadWrite;
invalidationBarrier.access = MemoryAccess::DepthStencilRead; invalidationBarrier->access = MemoryAccess::DepthStencilRead;
invalidationBarrier.stages = PipelineStage::FragmentTestsEarly | PipelineStage::FragmentTestsLate; invalidationBarrier->stages = PipelineStage::FragmentTestsEarly | PipelineStage::FragmentTestsLate;
}
} }
else if (dsOutputAttachement != FramePass::InvalidAttachmentId) else if (dsOutputAttachement != FramePass::InvalidAttachmentId)
{ {
// DS output-only // DS output-only
auto& flushBarrier = GetFlushBarrier(dsOutputAttachement); if (Barrier* flushBarrier = GetFlushBarrier(dsOutputAttachement))
if (flushBarrier.layout != TextureLayout::Undefined) {
throw std::runtime_error("layout mismatch"); if (flushBarrier->layout != TextureLayout::Undefined)
throw std::runtime_error("layout mismatch");
flushBarrier.layout = TextureLayout::DepthStencilReadWrite; flushBarrier->layout = TextureLayout::DepthStencilReadWrite;
flushBarrier.access = MemoryAccess::DepthStencilWrite; flushBarrier->access = MemoryAccess::DepthStencilWrite;
flushBarrier.stages = PipelineStage::FragmentTestsLate; flushBarrier->stages = PipelineStage::FragmentTestsLate;
}
} }
} }
} }
@ -729,12 +761,14 @@ namespace Nz
auto RegisterColorInputRead = [&](const FramePass::Input& input) auto RegisterColorInputRead = [&](const FramePass::Input& input)
{ {
std::size_t textureId = Retrieve(m_pending.attachmentToTextures, input.attachmentId); std::size_t textureId = Retrieve(m_pending.attachmentToTextures, input.attachmentId);
if (textureId == InvalidTextureIndex)
return;
TextureLayout& textureLayout = textureLayouts[textureId]; TextureLayout& textureLayout = textureLayouts[textureId];
if (!input.assumedLayout) if (!input.assumedLayout)
{ {
assert(textureLayouts[textureId] != TextureLayout::Undefined); assert(textureLayouts[textureId] != TextureLayout::Undefined);
textureLayout = TextureLayout::ColorInput; textureLayout = input.layout;
} }
else else
textureLayout = *input.assumedLayout; textureLayout = *input.assumedLayout;
@ -743,6 +777,8 @@ namespace Nz
auto RegisterColorOutput = [&](const FramePass::Output& output, bool shouldLoad) auto RegisterColorOutput = [&](const FramePass::Output& output, bool shouldLoad)
{ {
std::size_t textureId = Retrieve(m_pending.attachmentToTextures, output.attachmentId); std::size_t textureId = Retrieve(m_pending.attachmentToTextures, output.attachmentId);
if (textureId == InvalidTextureIndex)
return InvalidAttachmentIndex;
TextureLayout initialLayout = textureLayouts[textureId]; TextureLayout initialLayout = textureLayouts[textureId];
textureLayouts[textureId] = TextureLayout::ColorOutput; textureLayouts[textureId] = TextureLayout::ColorOutput;
@ -770,19 +806,21 @@ namespace Nz
return attachmentIndex; return attachmentIndex;
}; };
auto RegisterDepthStencil = [&](std::size_t attachmentId, TextureLayout textureLayout, bool* first) -> RenderPass::Attachment& auto RegisterDepthStencil = [&](std::size_t attachmentId, TextureLayout textureLayout, bool* first) -> RenderPass::Attachment*
{ {
if (depthStencilAttachmentIndex) if (depthStencilAttachmentIndex)
{ {
assert(depthStencilAttachmentId == attachmentId); assert(depthStencilAttachmentId == attachmentId);
*first = false; *first = false;
return renderPassAttachments[depthStencilAttachmentIndex.value()]; return &renderPassAttachments[depthStencilAttachmentIndex.value()];
} }
*first = true; *first = true;
std::size_t textureId = Retrieve(m_pending.attachmentToTextures, attachmentId); std::size_t textureId = Retrieve(m_pending.attachmentToTextures, attachmentId);
if (textureId == InvalidTextureIndex)
return nullptr;
TextureLayout initialLayout = textureLayouts[textureId]; TextureLayout initialLayout = textureLayouts[textureId];
textureLayouts[textureId] = textureLayout; textureLayouts[textureId] = textureLayout;
@ -796,7 +834,7 @@ namespace Nz
depthStencilAttachment.format = m_pending.textures[textureId].format; depthStencilAttachment.format = m_pending.textures[textureId].format;
depthStencilAttachment.initialLayout = initialLayout; depthStencilAttachment.initialLayout = initialLayout;
return depthStencilAttachment; return &depthStencilAttachment;
}; };
std::size_t physicalPassIndex = 0; std::size_t physicalPassIndex = 0;
@ -834,11 +872,13 @@ namespace Nz
shouldLoad = true; shouldLoad = true;
std::size_t attachmentIndex = RegisterColorOutput(output, shouldLoad); std::size_t attachmentIndex = RegisterColorOutput(output, shouldLoad);
if (attachmentIndex != InvalidAttachmentIndex)
colorAttachments.push_back({ {
attachmentIndex, colorAttachments.push_back({
TextureLayout::ColorOutput attachmentIndex,
}); TextureLayout::ColorOutput
});
}
} }
} }
@ -855,73 +895,79 @@ namespace Nz
{ {
// DS input/output // DS input/output
bool first; bool first;
auto& dsAttachment = RegisterDepthStencil(dsInputAttachment, TextureLayout::DepthStencilReadWrite, &first); RenderPass::Attachment* dsAttachment = RegisterDepthStencil(dsInputAttachment, TextureLayout::DepthStencilReadWrite, &first);
if (dsAttachment)
if (first)
{ {
dsAttachment.loadOp = AttachmentLoadOp::Load; if (first)
dsAttachment.storeOp = AttachmentStoreOp::Store; {
} dsAttachment->loadOp = AttachmentLoadOp::Load;
dsAttachment->storeOp = AttachmentStoreOp::Store;
}
depthStencilAttachment = RenderPass::AttachmentReference{ depthStencilAttachment = RenderPass::AttachmentReference{
depthStencilAttachmentIndex.value(), depthStencilAttachmentIndex.value(),
TextureLayout::DepthStencilReadWrite TextureLayout::DepthStencilReadWrite
}; };
}
} }
else if (dsInputAttachment != FramePass::InvalidAttachmentId) else if (dsInputAttachment != FramePass::InvalidAttachmentId)
{ {
// DS input-only // DS input-only
bool first; bool first;
auto& dsAttachment = RegisterDepthStencil(dsInputAttachment, TextureLayout::DepthStencilReadOnly, &first); RenderPass::Attachment* dsAttachment = RegisterDepthStencil(dsInputAttachment, TextureLayout::DepthStencilReadOnly, &first);
if (dsAttachment)
if (first)
{ {
bool canDiscard = true; if (first)
// Check if a future pass reads from the DS buffer or if we can discard it after this pass
if (auto readIt = m_pending.attachmentReadList.find(dsInputAttachment); readIt != m_pending.attachmentReadList.end())
{ {
for (std::size_t passIndex : readIt->second) bool canDiscard = true;
{
auto it = m_pending.passIdToPhysicalPassIndex.find(passIndex);
if (it == m_pending.passIdToPhysicalPassIndex.end())
continue; //< pass may have been discarded
std::size_t readPhysicalPassIndex = it->second; // Check if a future pass reads from the DS buffer or if we can discard it after this pass
if (readPhysicalPassIndex > physicalPassIndex) //< Read in a future pass? if (auto readIt = m_pending.attachmentReadList.find(dsInputAttachment); readIt != m_pending.attachmentReadList.end())
{
for (std::size_t passIndex : readIt->second)
{ {
// Yes, store it auto it = m_pending.passIdToPhysicalPassIndex.find(passIndex);
canDiscard = false; if (it == m_pending.passIdToPhysicalPassIndex.end())
break; continue; //< pass may have been discarded
std::size_t readPhysicalPassIndex = it->second;
if (readPhysicalPassIndex > physicalPassIndex) //< Read in a future pass?
{
// Yes, store it
canDiscard = false;
break;
}
} }
} }
dsAttachment->storeOp = (canDiscard) ? AttachmentStoreOp::Discard : AttachmentStoreOp::Store;
} }
dsAttachment.storeOp = (canDiscard) ? AttachmentStoreOp::Discard : AttachmentStoreOp::Store; depthStencilAttachment = RenderPass::AttachmentReference{
depthStencilAttachmentIndex.value(),
TextureLayout::DepthStencilReadOnly
};
} }
depthStencilAttachment = RenderPass::AttachmentReference{
depthStencilAttachmentIndex.value(),
TextureLayout::DepthStencilReadOnly
};
} }
else if (dsOutputAttachement != FramePass::InvalidAttachmentId) else if (dsOutputAttachement != FramePass::InvalidAttachmentId)
{ {
// DS output-only // DS output-only
bool first; bool first;
auto& dsAttachment = RegisterDepthStencil(dsOutputAttachement, TextureLayout::DepthStencilReadWrite, &first); RenderPass::Attachment* dsAttachment = RegisterDepthStencil(dsOutputAttachement, TextureLayout::DepthStencilReadWrite, &first);
if (dsAttachment)
if (first)
{ {
dsAttachment.initialLayout = TextureLayout::Undefined; //< Don't care about initial layout if (first)
dsAttachment.loadOp = (framePass.GetDepthStencilClear()) ? AttachmentLoadOp::Clear : AttachmentLoadOp::Discard; {
dsAttachment.storeOp = AttachmentStoreOp::Store; dsAttachment->initialLayout = TextureLayout::Undefined; //< Don't care about initial layout
} dsAttachment->loadOp = (framePass.GetDepthStencilClear()) ? AttachmentLoadOp::Clear : AttachmentLoadOp::Discard;
dsAttachment->storeOp = AttachmentStoreOp::Store;
}
depthStencilAttachment = RenderPass::AttachmentReference{ depthStencilAttachment = RenderPass::AttachmentReference{
depthStencilAttachmentIndex.value(), depthStencilAttachmentIndex.value(),
TextureLayout::DepthStencilReadWrite TextureLayout::DepthStencilReadWrite
}; };
}
} }
subpassesDesc.push_back({ subpassesDesc.push_back({
@ -943,7 +989,10 @@ namespace Nz
BuildPhysicalPassDependencies(colorAttachmentCount, depthStencilAttachmentIndex.has_value(), renderPassAttachments, subpassesDesc, subpassesDeps); BuildPhysicalPassDependencies(colorAttachmentCount, depthStencilAttachmentIndex.has_value(), renderPassAttachments, subpassesDesc, subpassesDeps);
m_pending.renderPasses.push_back(renderPassCache.Get(renderPassAttachments, subpassesDesc, subpassesDeps)); if (!renderPassAttachments.empty())
m_pending.renderPasses.push_back(renderPassCache.Get(renderPassAttachments, subpassesDesc, subpassesDeps));
else
m_pending.renderPasses.push_back(nullptr);
physicalPassIndex++; physicalPassIndex++;
} }
@ -1223,6 +1272,11 @@ namespace Nz
return textureId; return textureId;
} }
else if constexpr (std::is_same_v<T, DummyAttachment>)
{
m_pending.attachmentToTextures.emplace(attachmentIndex, InvalidTextureIndex);
return InvalidTextureIndex;
}
else else
static_assert(AlwaysFalse<T>::value, "non-exhaustive visitor"); static_assert(AlwaysFalse<T>::value, "non-exhaustive visitor");
}, m_attachments[attachmentIndex]); }, m_attachments[attachmentIndex]);

View File

@ -135,7 +135,7 @@ namespace Nz
assert(viewer == nullptr); assert(viewer == nullptr);
std::size_t cubeInputIndex = pass.AddInput(m_cubeAttachmentIndex); std::size_t cubeInputIndex = pass.AddInput(m_cubeAttachmentIndex);
pass.SetInputLayout(cubeInputIndex, TextureLayout::ColorInput); pass.SetInputAssumedLayout(cubeInputIndex, TextureLayout::ColorInput);
for (DirectionData& direction : m_directions) for (DirectionData& direction : m_directions)
pass.AddInput(direction.attachmentIndex); pass.AddInput(direction.attachmentIndex);

View File

@ -15,13 +15,10 @@ namespace Nz
{ {
} }
void RenderTexture::OnBuildGraph(FrameGraph& graph, std::size_t attachmentIndex) const std::size_t RenderTexture::OnBuildGraph(FrameGraph& graph, std::size_t attachmentIndex) const
{ {
graph.BindExternalTexture(attachmentIndex, m_targetTexture); graph.BindExternalTexture(attachmentIndex, m_targetTexture);
} return attachmentIndex;
void RenderTexture::OnRenderEnd(RenderResources& /*renderFrame*/, const BakedFrameGraph& /*frameGraph*/, std::size_t /*finalAttachment*/) const
{
} }
const Vector2ui& RenderTexture::GetSize() const const Vector2ui& RenderTexture::GetSize() const

View File

@ -12,33 +12,33 @@
namespace Nz namespace Nz
{ {
void RenderTextureBlit::OnBuildGraph(FrameGraph& graph, std::size_t attachmentIndex) const std::size_t RenderTextureBlit::OnBuildGraph(FrameGraph& graph, std::size_t attachmentIndex) const
{ {
graph.AddOutput(attachmentIndex); std::size_t linkAttachment = graph.AddDummyAttachment();
}
void RenderTextureBlit::OnRenderEnd(RenderResources& resources, const BakedFrameGraph& frameGraph, std::size_t finalAttachment) const FramePass& blitPass = graph.AddPass("Blit to texture");
{ blitPass.AddInput(attachmentIndex);
const std::shared_ptr<Texture>& sourceTexture = frameGraph.GetAttachmentTexture(finalAttachment); blitPass.SetInputAccess(0, TextureLayout::TransferSource, PipelineStage::Transfer, MemoryAccess::MemoryRead);
blitPass.SetInputUsage(0, TextureUsage::TransferSource);
Vector2ui sourceTextureSize = Vector2ui(sourceTexture->GetSize()); blitPass.AddOutput(linkAttachment);
Vector2ui targetTextureSize = Vector2ui(m_targetTexture->GetSize());
resources.Execute([&](CommandBufferBuilder& builder) blitPass.SetCommandCallback([this, attachmentIndex](CommandBufferBuilder& builder, const FramePassEnvironment& env)
{ {
builder.BeginDebugRegion("Blit to texture", Color::Blue()); const std::shared_ptr<Texture>& sourceTexture = env.frameGraph.GetAttachmentTexture(attachmentIndex);
{
builder.TextureBarrier(PipelineStage::ColorOutput, PipelineStage::Transfer, MemoryAccess::ColorWrite, MemoryAccess::TransferRead, TextureLayout::ColorOutput, TextureLayout::TransferSource, *sourceTexture);
builder.TextureBarrier(PipelineStage::TopOfPipe, PipelineStage::Transfer, {}, MemoryAccess::TransferWrite, TextureLayout::Undefined, TextureLayout::TransferDestination, *m_targetTexture);
Boxui fromBox(0, 0, 0, sourceTextureSize.x, sourceTextureSize.y, 1); Vector2ui sourceTextureSize = Vector2ui(sourceTexture->GetSize());
Boxui toBox(0, 0, 0, targetTextureSize.x, targetTextureSize.y, 1); Vector2ui targetTextureSize = Vector2ui(m_targetTexture->GetSize());
builder.BlitTexture(*sourceTexture, fromBox, TextureLayout::TransferSource, *m_targetTexture, toBox, TextureLayout::TransferDestination, m_samplerFilter); Boxui fromBox(0, 0, 0, sourceTextureSize.x, sourceTextureSize.y, 1);
builder.TextureBarrier(PipelineStage::Transfer, m_targetPipelineStage, MemoryAccess::TransferWrite, m_targetMemoryFlags, TextureLayout::TransferDestination, m_targetLayout, *m_targetTexture); Boxui toBox(0, 0, 0, targetTextureSize.x, targetTextureSize.y, 1);
}
builder.EndDebugRegion(); builder.TextureBarrier(PipelineStage::TopOfPipe, PipelineStage::Transfer, {}, MemoryAccess::TransferWrite, TextureLayout::Undefined, TextureLayout::TransferDestination, *m_targetTexture);
}, QueueType::Graphics); builder.BlitTexture(*sourceTexture, fromBox, TextureLayout::TransferSource, *m_targetTexture, toBox, TextureLayout::TransferDestination, m_samplerFilter);
builder.TextureBarrier(PipelineStage::Transfer, m_targetPipelineStage, MemoryAccess::TransferWrite, m_targetMemoryFlags, TextureLayout::TransferDestination, m_targetLayout, *m_targetTexture);
});
return linkAttachment;
} }
const Vector2ui& RenderTextureBlit::GetSize() const const Vector2ui& RenderTextureBlit::GetSize() const

View File

@ -15,6 +15,7 @@
namespace Nz namespace Nz
{ {
RenderWindow::RenderWindow(WindowSwapchain& swapchain) : RenderWindow::RenderWindow(WindowSwapchain& swapchain) :
RenderTarget(DefaultRenderOrder),
m_swapchain(nullptr), m_swapchain(nullptr),
m_windowSwapchain(&swapchain) m_windowSwapchain(&swapchain)
{ {
@ -28,30 +29,40 @@ namespace Nz
SetSwapchain(nullptr); SetSwapchain(nullptr);
}); });
SetFrameGraphOutput(true);
SetSwapchain(m_windowSwapchain->GetSwapchain()); SetSwapchain(m_windowSwapchain->GetSwapchain());
} }
void RenderWindow::OnBuildGraph(FrameGraph& graph, std::size_t attachmentIndex) const std::size_t RenderWindow::OnBuildGraph(FrameGraph& graph, std::size_t attachmentIndex) const
{ {
graph.AddOutput(attachmentIndex); // TODO: Replace the blit to swapchain by a graph.BindExternalSwapchain?
} std::size_t linkAttachment = graph.AddDummyAttachment();
FramePass& blitPass = graph.AddPass("Blit to swapchain");
blitPass.AddInput(attachmentIndex);
blitPass.SetInputAccess(0, TextureLayout::TransferSource, PipelineStage::Transfer, MemoryAccess::MemoryRead);
blitPass.SetInputUsage(0, TextureUsage::TransferSource);
void RenderWindow::OnRenderEnd(RenderResources& renderResources, const BakedFrameGraph& frameGraph, std::size_t finalAttachment) const blitPass.AddOutput(linkAttachment);
{
const std::shared_ptr<Texture>& texture = frameGraph.GetAttachmentTexture(finalAttachment);
Vector2ui textureSize = Vector2ui(texture->GetSize()); // Force regeneration of RenderWindow execution callback (since image index changes every frame)
Boxui blitRegion(0, 0, 0, textureSize.x, textureSize.y, 1); // TODO: Maybe handle this in a better way (temporary command buffer? multiple commands buffers selected by frame index?)
blitPass.SetExecutionCallback([]
renderResources.Execute([&](CommandBufferBuilder& builder)
{ {
builder.BeginDebugRegion("Blit to swapchain", Color::Blue()); return FramePassExecution::UpdateAndExecute;
{ });
builder.TextureBarrier(PipelineStage::ColorOutput, PipelineStage::Transfer, MemoryAccess::ColorWrite, MemoryAccess::TransferRead, TextureLayout::ColorOutput, TextureLayout::TransferSource, *texture);
builder.BlitTextureToSwapchain(*texture, blitRegion, TextureLayout::TransferSource, *m_swapchain, renderResources.GetImageIndex()); blitPass.SetCommandCallback([this, attachmentIndex](CommandBufferBuilder& builder, const FramePassEnvironment& env)
} {
builder.EndDebugRegion(); const std::shared_ptr<Texture>& sourceTexture = env.frameGraph.GetAttachmentTexture(attachmentIndex);
}, QueueType::Graphics);
Vector2ui textureSize = Vector2ui(sourceTexture->GetSize());
Boxui blitRegion(0, 0, 0, textureSize.x, textureSize.y, 1);
builder.BlitTextureToSwapchain(*sourceTexture, blitRegion, TextureLayout::TransferSource, *m_swapchain, env.renderResources.GetImageIndex());
});
return linkAttachment;
} }
const Vector2ui& RenderWindow::GetSize() const const Vector2ui& RenderWindow::GetSize() const