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

View File

@ -15,6 +15,7 @@
#include <Nazara/Graphics/FramePassAttachment.hpp>
#include <Nazara/Renderer/Enums.hpp>
#include <Nazara/Renderer/RenderPass.hpp>
#include <limits>
#include <optional>
#include <string>
#include <unordered_map>
@ -26,6 +27,8 @@ namespace Nz
{
class NAZARA_GRAPHICS_API FrameGraph
{
friend class BakedFrameGraph;
public:
FrameGraph() = default;
FrameGraph(const FrameGraph&) = delete;
@ -38,6 +41,7 @@ namespace Nz
inline std::size_t AddAttachmentCube(FramePassAttachment attachment);
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 AddDummyAttachment();
inline FramePass& AddPass(std::string name);
inline void AddOutput(std::size_t attachmentIndex);
@ -88,6 +92,10 @@ namespace Nz
TextureLayout layout;
};
struct DummyAttachment
{
};
struct PassBarriers
{
std::vector<Barrier> invalidationBarriers;
@ -138,7 +146,10 @@ namespace Nz
void ReorderPasses();
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<FramePass> m_framePasses;

View File

@ -78,6 +78,14 @@ namespace Nz
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)
{
std::size_t id = m_framePasses.size();

View File

@ -73,7 +73,9 @@ namespace Nz
inline void SetDepthStencilInput(std::size_t attachmentId);
inline void SetDepthStencilOutput(std::size_t attachmentId);
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);
FramePass& operator=(const FramePass&) = delete;
@ -90,7 +92,11 @@ namespace Nz
struct Input
{
std::optional<TextureLayout> assumedLayout;
std::optional<TextureUsageFlags> textureUsageFlags;
std::size_t attachmentId;
MemoryAccessFlags accessFlags = MemoryAccess::ShaderRead;
PipelineStageFlags stageFlags = PipelineStage::FragmentShader;
TextureLayout layout = TextureLayout::ColorInput;
bool doesRead = true;
};

View File

@ -127,12 +127,26 @@ namespace Nz
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());
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)
{
assert(inputIndex < m_inputs.size());

View File

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

View File

@ -6,6 +6,32 @@
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>

View File

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

View File

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

View File

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

View File

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

View File

@ -3,9 +3,9 @@
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Graphics/BakedFrameGraph.hpp>
#include <Nazara/Graphics/FrameGraph.hpp>
#include <Nazara/Graphics/Graphics.hpp>
#include <Nazara/Renderer/CommandBufferBuilder.hpp>
#include <Nazara/Renderer/RenderFrame.hpp>
#include <Nazara/Graphics/Debug.hpp>
namespace Nz
@ -60,6 +60,7 @@ namespace Nz
builder.TextureBarrier(textureTransition.srcStageMask, textureTransition.dstStageMask, textureTransition.srcAccessMask, textureTransition.dstAccessMask, textureTransition.oldLayout, textureTransition.newLayout, *texture);
}
if (passData.framebuffer)
builder.BeginRenderPass(*passData.framebuffer, *passData.renderPass, passData.renderRect, passData.outputClearValues.data(), passData.outputClearValues.size());
if (!passData.name.empty())
@ -85,6 +86,7 @@ namespace Nz
if (!passData.name.empty())
builder.EndDebugRegion();
if (passData.framebuffer)
builder.EndRenderPass();
});
@ -102,7 +104,7 @@ namespace Nz
const std::shared_ptr<Texture>& BakedFrameGraph::GetAttachmentTexture(std::size_t attachmentIndex) const
{
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;
return dummy;
@ -116,7 +118,7 @@ namespace Nz
const std::shared_ptr<RenderPass>& BakedFrameGraph::GetRenderPass(std::size_t passIndex) const
{
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;
return dummy;
@ -224,6 +226,8 @@ namespace Nz
{
textures.clear();
if (!passData.outputTextureIndices.empty())
{
unsigned int framebufferWidth = std::numeric_limits<unsigned int>::max();
unsigned int framebufferHeight = std::numeric_limits<unsigned int>::max();
for (std::size_t textureId : passData.outputTextureIndices)
@ -242,6 +246,9 @@ namespace Nz
passData.framebuffer = renderDevice->InstantiateFramebuffer(framebufferWidth, framebufferHeight, passData.renderPass, textures);
if (!passData.name.empty())
passData.framebuffer->UpdateDebugName(passData.name);
}
else
passData.renderRect = Recti(0, 0, -1, -1);
passData.forceCommandBufferRegeneration = true;
}

View File

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

View File

@ -383,21 +383,26 @@ namespace Nz
}
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)
m_activeLights.Clear();
for (auto& viewerData : m_viewerPool)
for (ViewerData* viewerData : m_orderedViewers)
{
if (viewerData.pendingDestruction)
continue;
UInt32 renderMask = viewerData.viewer->GetRenderMask();
UInt32 renderMask = viewerData->viewer->GetRenderMask();
// Extract frustum from viewproj matrix
const Matrix4f& viewProjMatrix = viewerData.viewer->GetViewerInstance().GetViewProjMatrix();
viewerData.frame.frustum = Frustumf::Extract(viewProjMatrix);
const Matrix4f& viewProjMatrix = viewerData->viewer->GetViewerInstance().GetViewProjMatrix();
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)
{
const LightData& lightData = *it;
@ -407,7 +412,7 @@ namespace Nz
continue;
m_activeLights.UnboundedSet(lightIndex);
viewerData.frame.visibleLights.UnboundedSet(lightIndex);
viewerData->frame.visibleLights.UnboundedSet(lightIndex);
}
}
@ -422,61 +427,34 @@ namespace Nz
}
// Viewer handling (second pass)
for (auto& viewerData : m_viewerPool)
for (ViewerData* viewerData : m_orderedViewers)
{
if (viewerData.pendingDestruction)
continue;
UInt32 renderMask = viewerData.viewer->GetRenderMask();
UInt32 renderMask = viewerData->viewer->GetRenderMask();
// 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);
if (lightData->shadowData && lightData->shadowData->IsPerViewer() && (renderMask & lightData->renderMask) != 0)
lightData->shadowData->PrepareRendering(renderResources, viewerData.viewer);
lightData->shadowData->PrepareRendering(renderResources, viewerData->viewer);
}
// Frustum culling
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 = {
&viewerData.frame.visibleLights,
viewerData.frame.frustum,
&viewerData->frame.visibleLights,
viewerData->frame.frustum,
renderResources,
visibleRenderables,
visibilityHash
};
for (auto& passPtr : viewerData.passes)
for (auto& passPtr : viewerData->passes)
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
renderResources.Execute([&](CommandBufferBuilder& builder)
{
@ -498,10 +476,6 @@ namespace Nz
m_bakedFrameGraph.Execute(renderResources);
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
DebugDrawer& debugDrawer = GetDebugDrawer();
debugDrawer.Reset(renderResources);
@ -648,6 +622,7 @@ namespace Nz
{
FrameGraph frameGraph;
// Register viewer-independent passes
for (std::size_t i : m_shadowCastingLights.IterBits())
{
LightData* lightData = m_lightPool.RetrieveFromIndex(i);
@ -655,34 +630,48 @@ namespace Nz
lightData->shadowData->RegisterToFrameGraph(frameGraph, nullptr);
}
using ViewerPair = std::pair<const RenderTarget*, ViewerData*>;
StackArray<ViewerPair> viewers = NazaraStackArray(ViewerPair, m_viewerPool.size());
auto viewerIt = viewers.begin();
// Group every viewer by their render order and RenderTarget
m_orderedViewers.clear();
for (auto& viewerData : m_viewerPool)
{
if (viewerData.pendingDestruction)
continue;
const RenderTarget& renderTarget = viewerData.viewer->GetRenderTarget();
*viewerIt++ = std::make_pair(&renderTarget, &viewerData);
m_orderedViewers.push_back(&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();
m_renderTargets.clear();
unsigned int viewerIndex = 0;
for (auto it = viewers.begin(), prevIt = it; it != viewers.end(); ++it)
std::size_t viewerIndex = 0;
auto HandleRenderTarget = [&](const RenderTarget& renderTarget, std::span<ViewerData*> viewers)
{
auto&& [renderTarget, viewerData] = *it;
if (renderTarget.GetRenderOrder() > lastRenderOrder)
{
dependenciesColorAttachmentCount = dependenciesColorAttachments.size();
lastRenderOrder = renderTarget.GetRenderOrder();
}
assert(!viewers.empty());
for (ViewerData* viewerData : viewers)
{
UInt32 renderMask = viewerData->viewer->GetRenderMask();
for (std::size_t i : m_shadowCastingLights.IterBits())
{
@ -691,31 +680,12 @@ namespace Nz
lightData->shadowData->RegisterToFrameGraph(frameGraph, viewerData->viewer);
}
// Keep track of previous dependencies attachments (from viewers having a smaller render order)
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);
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
// TODO: Allow the user to define which pass of viewer A uses viewer B rendering
for (std::size_t finalAttachment : dependenciesColorAttachments)
{
std::size_t inputIndex = framePass.AddInput(finalAttachment);
// Disable ReadInput to prevent the framegraph from transitionning the texture layout (for now it's handled externally)
// (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);
}
for (std::size_t i = 0; i < dependenciesColorAttachmentCount; ++i)
framePass.AddInput(dependenciesColorAttachments[i]);
if (flags.Test(FramePipelinePassFlag::LightShadowing))
{
@ -729,47 +699,92 @@ namespace Nz
};
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)
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
const auto& viewer = *viewers.front();
finalAttachment = renderTarget.OnBuildGraph(frameGraph, viewer.finalColorAttachment);
}
if (!renderTarget.IsFrameGraphOutput())
{
// 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();
}
std::size_t ForwardFramePipeline::BuildMergePass(FrameGraph& frameGraph, std::span<ViewerData*> targetViewers)
{
FramePass& mergePass = frameGraph.AddPass("Merge pass");
renderTargetData.finalAttachment = frameGraph.AddAttachment({
"Viewer output",
std::size_t mergedAttachment = frameGraph.AddAttachment({
"Merged output",
PixelFormat::RGBA8
});
for (const ViewerData* viewerData : targetViewers)
mergePass.AddInput(viewerData->finalColorAttachment);
mergePass.AddOutput(renderTargetData.finalAttachment);
mergePass.AddOutput(mergedAttachment);
mergePass.SetClearColor(0, Color::Black());
mergePass.SetCommandCallback([&targetViewers](CommandBufferBuilder& builder, const FramePassEnvironment& /*env*/)
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)
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);
@ -783,19 +798,7 @@ namespace Nz
}
});
renderTarget->OnBuildGraph(frameGraph, renderTargetData.finalAttachment);
}
else if (targetViewers.size() == 1)
{
// Single viewer on that target
const auto& viewer = *targetViewers.front();
renderTargetData.finalAttachment = viewer.finalColorAttachment;
renderTarget->OnBuildGraph(frameGraph, renderTargetData.finalAttachment);
}
}
return frameGraph.Bake();
return mergedAttachment;
}
void ForwardFramePipeline::RegisterMaterialInstance(MaterialInstance* materialInstance)

View File

@ -29,7 +29,7 @@ namespace Nz
BakedFrameGraph FrameGraph::Bake()
{
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.attachmentToTextures.clear();
@ -89,7 +89,8 @@ namespace Nz
for (std::size_t i = 0; i < colorOutputs.size(); ++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)
{
@ -111,9 +112,15 @@ namespace Nz
std::size_t attachmentId;
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)
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())
{
std::size_t textureId = RegisterTexture(input.attachmentId);
if (textureId != InvalidTextureIndex)
{
FrameGraphTextureData& attachmentData = m_pending.textures[textureId];
attachmentData.usage |= TextureUsage::ShaderSampling;
attachmentData.usage |= input.textureUsageFlags.value_or(TextureUsage::ShaderSampling);
}
}
for (const auto& output : framePass.GetOutputs())
{
std::size_t textureId = RegisterTexture(output.attachmentId);
if (textureId != InvalidTextureIndex)
{
FrameGraphTextureData& attachmentData = m_pending.textures[textureId];
attachmentData.usage |= TextureUsage::ColorAttachment;
}
}
if (std::size_t depthStencilInput = framePass.GetDepthStencilInput(); depthStencilInput != FramePass::InvalidAttachmentId)
{
std::size_t textureId = RegisterTexture(depthStencilInput);
if (textureId != InvalidTextureIndex)
{
FrameGraphTextureData& attachmentData = m_pending.textures[textureId];
attachmentData.usage |= TextureUsage::DepthStencilAttachment;
}
if (std::size_t depthStencilOutput = framePass.GetDepthStencilOutput(); depthStencilOutput != FramePass::InvalidAttachmentId)
{
@ -296,9 +309,12 @@ namespace Nz
auto it = m_pending.attachmentToTextures.find(output);
assert(it != m_pending.attachmentToTextures.end());
auto& finalTexture = m_pending.textures[it->second];
if (std::size_t textureIndex = it->second; textureIndex != InvalidTextureIndex)
{
auto& finalTexture = m_pending.textures[textureIndex];
finalTexture.usage |= TextureUsage::ShaderSampling | TextureUsage::TransferSource;
}
}
// Apply texture view usage to their parents
for (auto& textureData : m_pending.textures)
@ -316,13 +332,15 @@ namespace Nz
assert(m_pending.barrierList.empty());
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));
if (textureId == InvalidTextureIndex)
return nullptr;
auto it = std::find_if(barriers.begin(), barriers.end(), [&](const Barrier& barrier) { return barrier.textureId == textureId; });
if (it != barriers.end())
return *it;
return &*it;
else
{
// Insert a new barrier
@ -330,7 +348,7 @@ namespace Nz
barrier.textureId = textureId;
barrier.layout = TextureLayout::Undefined;
return barrier;
return &barrier;
}
};
@ -340,29 +358,35 @@ namespace Nz
auto& barriers = m_pending.barrierList.emplace_back();
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 GetInvalidationBarrier = [&](std::size_t attachmentId) -> Barrier* { return GetBarrier(barriers.invalidationBarriers, attachmentId); };
auto GetFlushBarrier = [&](std::size_t attachmentId) -> Barrier* { return GetBarrier(barriers.flushBarriers, attachmentId); };
for (const auto& input : framePass.GetInputs())
{
auto& barrier = GetInvalidationBarrier(input.attachmentId);
if (barrier.layout != TextureLayout::Undefined)
Barrier* barrier = GetInvalidationBarrier(input.attachmentId);
if (!barrier)
continue;
if (barrier->layout != TextureLayout::Undefined)
throw std::runtime_error("layout mismatch");
barrier.access |= MemoryAccess::ShaderRead;
barrier.stages |= PipelineStage::FragmentShader;
barrier.layout = TextureLayout::ColorInput;
barrier->access |= input.accessFlags;
barrier->stages |= input.stageFlags;
barrier->layout = input.layout;
}
for (const auto& output : framePass.GetOutputs())
{
auto& barrier = GetFlushBarrier(output.attachmentId);
if (barrier.layout != TextureLayout::Undefined)
Barrier* barrier = GetFlushBarrier(output.attachmentId);
if (!barrier)
continue;
if (barrier->layout != TextureLayout::Undefined)
throw std::runtime_error("layout mismatch");
barrier.access |= MemoryAccess::ColorWrite;
barrier.stages |= PipelineStage::ColorOutput;
barrier.layout = TextureLayout::ColorOutput;
barrier->access |= MemoryAccess::ColorWrite;
barrier->stages |= PipelineStage::ColorOutput;
barrier->layout = TextureLayout::ColorOutput;
}
std::size_t dsInputAttachment = framePass.GetDepthStencilInput();
@ -371,40 +395,48 @@ namespace Nz
if (dsInputAttachment != FramePass::InvalidAttachmentId && dsOutputAttachement != FramePass::InvalidAttachmentId)
{
// DS input/output
auto& invalidationBarrier = GetInvalidationBarrier(dsInputAttachment);
if (invalidationBarrier.layout != TextureLayout::Undefined)
if (Barrier* invalidationBarrier = GetInvalidationBarrier(dsInputAttachment))
{
if (invalidationBarrier->layout != TextureLayout::Undefined)
throw std::runtime_error("layout mismatch");
invalidationBarrier.layout = TextureLayout::DepthStencilReadWrite;
invalidationBarrier.access = MemoryAccess::DepthStencilRead | MemoryAccess::DepthStencilWrite;
invalidationBarrier.stages = PipelineStage::FragmentTestsEarly | PipelineStage::FragmentTestsLate;
invalidationBarrier->layout = TextureLayout::DepthStencilReadWrite;
invalidationBarrier->access = MemoryAccess::DepthStencilRead | MemoryAccess::DepthStencilWrite;
invalidationBarrier->stages = PipelineStage::FragmentTestsEarly | PipelineStage::FragmentTestsLate;
}
auto& flushBarrier = GetFlushBarrier(dsOutputAttachement);
flushBarrier.layout = TextureLayout::DepthStencilReadWrite;
flushBarrier.access = MemoryAccess::DepthStencilWrite;
flushBarrier.stages = PipelineStage::FragmentTestsLate;
if (Barrier* flushBarrier = GetFlushBarrier(dsOutputAttachement))
{
flushBarrier->layout = TextureLayout::DepthStencilReadWrite;
flushBarrier->access = MemoryAccess::DepthStencilWrite;
flushBarrier->stages = PipelineStage::FragmentTestsLate;
}
}
else if (dsInputAttachment != FramePass::InvalidAttachmentId)
{
// DS input-only
auto& invalidationBarrier = GetInvalidationBarrier(dsInputAttachment);
if (invalidationBarrier.layout != TextureLayout::Undefined)
if (Barrier* invalidationBarrier = GetInvalidationBarrier(dsInputAttachment))
{
if (invalidationBarrier->layout != TextureLayout::Undefined)
throw std::runtime_error("layout mismatch");
invalidationBarrier.layout = TextureLayout::DepthStencilReadWrite;
invalidationBarrier.access = MemoryAccess::DepthStencilRead;
invalidationBarrier.stages = PipelineStage::FragmentTestsEarly | PipelineStage::FragmentTestsLate;
invalidationBarrier->layout = TextureLayout::DepthStencilReadWrite;
invalidationBarrier->access = MemoryAccess::DepthStencilRead;
invalidationBarrier->stages = PipelineStage::FragmentTestsEarly | PipelineStage::FragmentTestsLate;
}
}
else if (dsOutputAttachement != FramePass::InvalidAttachmentId)
{
// DS output-only
auto& flushBarrier = GetFlushBarrier(dsOutputAttachement);
if (flushBarrier.layout != TextureLayout::Undefined)
if (Barrier* flushBarrier = GetFlushBarrier(dsOutputAttachement))
{
if (flushBarrier->layout != TextureLayout::Undefined)
throw std::runtime_error("layout mismatch");
flushBarrier.layout = TextureLayout::DepthStencilReadWrite;
flushBarrier.access = MemoryAccess::DepthStencilWrite;
flushBarrier.stages = PipelineStage::FragmentTestsLate;
flushBarrier->layout = TextureLayout::DepthStencilReadWrite;
flushBarrier->access = MemoryAccess::DepthStencilWrite;
flushBarrier->stages = PipelineStage::FragmentTestsLate;
}
}
}
}
@ -729,12 +761,14 @@ namespace Nz
auto RegisterColorInputRead = [&](const FramePass::Input& input)
{
std::size_t textureId = Retrieve(m_pending.attachmentToTextures, input.attachmentId);
if (textureId == InvalidTextureIndex)
return;
TextureLayout& textureLayout = textureLayouts[textureId];
if (!input.assumedLayout)
{
assert(textureLayouts[textureId] != TextureLayout::Undefined);
textureLayout = TextureLayout::ColorInput;
textureLayout = input.layout;
}
else
textureLayout = *input.assumedLayout;
@ -743,6 +777,8 @@ namespace Nz
auto RegisterColorOutput = [&](const FramePass::Output& output, bool shouldLoad)
{
std::size_t textureId = Retrieve(m_pending.attachmentToTextures, output.attachmentId);
if (textureId == InvalidTextureIndex)
return InvalidAttachmentIndex;
TextureLayout initialLayout = textureLayouts[textureId];
textureLayouts[textureId] = TextureLayout::ColorOutput;
@ -770,19 +806,21 @@ namespace Nz
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)
{
assert(depthStencilAttachmentId == attachmentId);
*first = false;
return renderPassAttachments[depthStencilAttachmentIndex.value()];
return &renderPassAttachments[depthStencilAttachmentIndex.value()];
}
*first = true;
std::size_t textureId = Retrieve(m_pending.attachmentToTextures, attachmentId);
if (textureId == InvalidTextureIndex)
return nullptr;
TextureLayout initialLayout = textureLayouts[textureId];
textureLayouts[textureId] = textureLayout;
@ -796,7 +834,7 @@ namespace Nz
depthStencilAttachment.format = m_pending.textures[textureId].format;
depthStencilAttachment.initialLayout = initialLayout;
return depthStencilAttachment;
return &depthStencilAttachment;
};
std::size_t physicalPassIndex = 0;
@ -834,13 +872,15 @@ namespace Nz
shouldLoad = true;
std::size_t attachmentIndex = RegisterColorOutput(output, shouldLoad);
if (attachmentIndex != InvalidAttachmentIndex)
{
colorAttachments.push_back({
attachmentIndex,
TextureLayout::ColorOutput
});
}
}
}
std::size_t colorAttachmentCount = renderPassAttachments.size();
@ -855,12 +895,13 @@ namespace Nz
{
// DS input/output
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;
dsAttachment.storeOp = AttachmentStoreOp::Store;
dsAttachment->loadOp = AttachmentLoadOp::Load;
dsAttachment->storeOp = AttachmentStoreOp::Store;
}
depthStencilAttachment = RenderPass::AttachmentReference{
@ -868,12 +909,14 @@ namespace Nz
TextureLayout::DepthStencilReadWrite
};
}
}
else if (dsInputAttachment != FramePass::InvalidAttachmentId)
{
// DS input-only
bool first;
auto& dsAttachment = RegisterDepthStencil(dsInputAttachment, TextureLayout::DepthStencilReadOnly, &first);
RenderPass::Attachment* dsAttachment = RegisterDepthStencil(dsInputAttachment, TextureLayout::DepthStencilReadOnly, &first);
if (dsAttachment)
{
if (first)
{
bool canDiscard = true;
@ -897,7 +940,7 @@ namespace Nz
}
}
dsAttachment.storeOp = (canDiscard) ? AttachmentStoreOp::Discard : AttachmentStoreOp::Store;
dsAttachment->storeOp = (canDiscard) ? AttachmentStoreOp::Discard : AttachmentStoreOp::Store;
}
depthStencilAttachment = RenderPass::AttachmentReference{
@ -905,17 +948,19 @@ namespace Nz
TextureLayout::DepthStencilReadOnly
};
}
}
else if (dsOutputAttachement != FramePass::InvalidAttachmentId)
{
// DS output-only
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
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{
@ -923,6 +968,7 @@ namespace Nz
TextureLayout::DepthStencilReadWrite
};
}
}
subpassesDesc.push_back({
std::move(colorAttachments),
@ -943,7 +989,10 @@ namespace Nz
BuildPhysicalPassDependencies(colorAttachmentCount, depthStencilAttachmentIndex.has_value(), renderPassAttachments, subpassesDesc, subpassesDeps);
if (!renderPassAttachments.empty())
m_pending.renderPasses.push_back(renderPassCache.Get(renderPassAttachments, subpassesDesc, subpassesDeps));
else
m_pending.renderPasses.push_back(nullptr);
physicalPassIndex++;
}
@ -1223,6 +1272,11 @@ namespace Nz
return textureId;
}
else if constexpr (std::is_same_v<T, DummyAttachment>)
{
m_pending.attachmentToTextures.emplace(attachmentIndex, InvalidTextureIndex);
return InvalidTextureIndex;
}
else
static_assert(AlwaysFalse<T>::value, "non-exhaustive visitor");
}, m_attachments[attachmentIndex]);

View File

@ -135,7 +135,7 @@ namespace Nz
assert(viewer == nullptr);
std::size_t cubeInputIndex = pass.AddInput(m_cubeAttachmentIndex);
pass.SetInputLayout(cubeInputIndex, TextureLayout::ColorInput);
pass.SetInputAssumedLayout(cubeInputIndex, TextureLayout::ColorInput);
for (DirectionData& direction : m_directions)
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);
}
void RenderTexture::OnRenderEnd(RenderResources& /*renderFrame*/, const BakedFrameGraph& /*frameGraph*/, std::size_t /*finalAttachment*/) const
{
return attachmentIndex;
}
const Vector2ui& RenderTexture::GetSize() const

View File

@ -12,33 +12,33 @@
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);
blitPass.SetInputAccess(0, TextureLayout::TransferSource, PipelineStage::Transfer, MemoryAccess::MemoryRead);
blitPass.SetInputUsage(0, TextureUsage::TransferSource);
blitPass.AddOutput(linkAttachment);
blitPass.SetCommandCallback([this, attachmentIndex](CommandBufferBuilder& builder, const FramePassEnvironment& env)
{
const std::shared_ptr<Texture>& sourceTexture = frameGraph.GetAttachmentTexture(finalAttachment);
const std::shared_ptr<Texture>& sourceTexture = env.frameGraph.GetAttachmentTexture(attachmentIndex);
Vector2ui sourceTextureSize = Vector2ui(sourceTexture->GetSize());
Vector2ui targetTextureSize = Vector2ui(m_targetTexture->GetSize());
resources.Execute([&](CommandBufferBuilder& builder)
{
builder.BeginDebugRegion("Blit to texture", Color::Blue());
{
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);
Boxui toBox(0, 0, 0, targetTextureSize.x, targetTextureSize.y, 1);
builder.TextureBarrier(PipelineStage::TopOfPipe, PipelineStage::Transfer, {}, MemoryAccess::TransferWrite, TextureLayout::Undefined, TextureLayout::TransferDestination, *m_targetTexture);
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);
}
builder.EndDebugRegion();
}, QueueType::Graphics);
});
return linkAttachment;
}
const Vector2ui& RenderTextureBlit::GetSize() const

View File

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