Add FrameGraph (WIP)

This commit is contained in:
Jérôme Leclercq
2021-05-02 13:58:35 +02:00
parent 377129586b
commit 55c2dd8485
29 changed files with 1749 additions and 36 deletions

View File

@@ -0,0 +1,160 @@
// Copyright (C) 2017 Jérôme Leclercq
// This file is part of the "Nazara Engine - Graphics module"
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Graphics/BakedFrameGraph.hpp>
#include <Nazara/Graphics/Graphics.hpp>
#include <Nazara/Renderer/CommandBufferBuilder.hpp>
#include <Nazara/Renderer/RenderFrame.hpp>
#include <Nazara/Graphics/Debug.hpp>
namespace Nz
{
BakedFrameGraph::BakedFrameGraph(std::vector<PassData> passes, std::vector<TextureData> textures, AttachmentIdToTextureId attachmentIdToTextureMapping, PassIdToPhysicalPassIndex passIdToPhysicalPassMapping) :
m_passes(std::move(passes)),
m_textures(std::move(textures)),
m_attachmentToTextureMapping(std::move(attachmentIdToTextureMapping)),
m_passIdToPhysicalPassMapping(std::move(passIdToPhysicalPassMapping))
{
RenderDevice& renderDevice = Graphics::Instance()->GetRenderDevice();
m_commandPool = renderDevice.InstantiateCommandPool(QueueType::Graphics);
}
void BakedFrameGraph::Execute(RenderFrame& renderFrame)
{
for (auto& passData : m_passes)
{
bool regenerateCommandBuffer = (passData.commandBuffer == nullptr);
if (passData.executionCallback)
{
switch (passData.executionCallback())
{
case FramePassExecution::Execute:
break;
case FramePassExecution::Skip:
passData.commandBuffer.reset();
continue; //< Skip the pass
case FramePassExecution::UpdateAndExecute:
regenerateCommandBuffer = true;
break;
}
}
if (!regenerateCommandBuffer)
continue;
passData.commandBuffer.reset(); //< Release command buffer resources before reallocating it
passData.commandBuffer = m_commandPool->BuildCommandBuffer([&](CommandBufferBuilder& builder)
{
for (auto& textureTransition : passData.transitions)
{
const std::shared_ptr<Texture>& texture = m_textures[textureTransition.textureId].texture;
builder.TextureBarrier(textureTransition.srcStageMask, textureTransition.dstStageMask, textureTransition.srcAccessMask, textureTransition.dstAccessMask, textureTransition.oldLayout, textureTransition.newLayout, *texture);
}
builder.BeginRenderPass(*passData.framebuffer, *passData.renderPass, passData.renderRect);
bool first = true;
for (auto& subpass : passData.subpasses)
{
if (!first)
builder.NextSubpass();
first = false;
subpass.commandCallback(builder);
}
builder.EndRenderPass();
});
}
//TODO: Submit all commands buffer at once
for (auto& passData : m_passes)
{
if (passData.commandBuffer)
renderFrame.SubmitCommandBuffer(passData.commandBuffer.get(), QueueType::Graphics);
}
}
const std::shared_ptr<Texture>& BakedFrameGraph::GetAttachmentTexture(std::size_t attachmentIndex) const
{
auto it = m_attachmentToTextureMapping.find(attachmentIndex);
if (it == m_attachmentToTextureMapping.end())
{
static std::shared_ptr<Texture> dummy;
return dummy;
}
std::size_t textureIndex = it->second;
assert(textureIndex < m_textures.size());
return m_textures[textureIndex].texture;
}
const std::shared_ptr<RenderPass>& BakedFrameGraph::GetRenderPass(std::size_t passIndex) const
{
auto it = m_attachmentToTextureMapping.find(passIndex);
if (it == m_attachmentToTextureMapping.end())
{
static std::shared_ptr<RenderPass> dummy;
return dummy;
}
std::size_t physicalPassIndex = it->second;
assert(physicalPassIndex < m_passes.size());
return m_passes[physicalPassIndex].renderPass;
}
void BakedFrameGraph::Resize(unsigned int width, unsigned int height)
{
RenderDevice& renderDevice = Graphics::Instance()->GetRenderDevice();
// Delete previous textures to make some room in VRAM
for (auto& passData : m_passes)
{
passData.commandBuffer.reset();
passData.framebuffer.reset();
}
for (auto& textureData : m_textures)
textureData.texture.reset();
for (auto& textureData : m_textures)
{
TextureInfo textureCreationParams;
textureCreationParams.type = ImageType_2D;
textureCreationParams.width = textureData.width * width / 100'000;
textureCreationParams.height = textureData.height * height / 100'000;
textureCreationParams.usageFlags = textureData.usage;
textureCreationParams.pixelFormat = textureData.format;
textureData.texture = renderDevice.InstantiateTexture(textureCreationParams);
}
std::vector<std::shared_ptr<Texture>> textures;
for (auto& passData : m_passes)
{
textures.clear();
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)
{
auto& textureData = m_textures[textureId];
textures.push_back(textureData.texture);
framebufferWidth = std::min(framebufferWidth, textureData.width);
framebufferHeight = std::min(framebufferHeight, textureData.height);
}
framebufferWidth = framebufferWidth * width / 100'000;
framebufferHeight = framebufferHeight * height / 100'000;
passData.renderRect.Set(0, 0, int(framebufferWidth), int(framebufferHeight));
passData.framebuffer = renderDevice.InstantiateFramebuffer(framebufferWidth, framebufferHeight, passData.renderPass, textures);
}
}
}

View File

@@ -0,0 +1,913 @@
// Copyright (C) 2017 Jérôme Leclercq
// This file is part of the "Nazara Engine - Graphics module"
// For conditions of distribution and use, see copyright notice in Config.hpp
// This class was written with a lot of help from themaister articles and Granite source code, check them out!
// https://themaister.net/blog/2017/08/15/render-graphs-and-vulkan-a-deep-dive/
#include <Nazara/Graphics/FrameGraph.hpp>
#include <Nazara/Graphics/Graphics.hpp>
#include <Nazara/Core/StackArray.hpp>
#include <stdexcept>
#include <unordered_set>
#include <Nazara/Graphics/Debug.hpp>
namespace Nz
{
namespace
{
template<typename T> const T& Retrieve(const std::unordered_map<std::size_t, T>& map, std::size_t id)
{
auto it = map.find(id);
assert(it != map.end());
return it->second;
}
template<typename T> void UniquePushBack(std::vector<T>& vec, const T& value)
{
auto it = std::find(vec.begin(), vec.end(), value);
if (it == vec.end())
vec.push_back(value);
}
}
BakedFrameGraph FrameGraph::Bake()
{
if (!m_backbufferOutput.has_value())
throw std::runtime_error("no backbuffer output has been set");
m_pending.attachmentReadList.clear();
m_pending.attachmentToTextures.clear();
m_pending.attachmentWriteList.clear();
m_pending.barrierList.clear();
m_pending.passIdToPhysicalPassIndex.clear();
m_pending.passList.clear();
m_pending.physicalPasses.clear();
m_pending.renderPasses.clear();
m_pending.textures.clear();
m_pending.backbufferResourceIndex = m_backbufferOutput.value();
BuildReadWriteList();
auto it = m_pending.attachmentWriteList.find(m_pending.backbufferResourceIndex);
if (it == m_pending.attachmentWriteList.end())
throw std::runtime_error("no pass writes to backbuffer");
const std::vector<std::size_t>& backbufferPasses = it->second;
for (std::size_t passIndex : backbufferPasses)
TraverseGraph(passIndex);
std::reverse(m_pending.passList.begin(), m_pending.passList.end());
RemoveDuplicatePasses();
ReorderPasses();
AssignPhysicalTextures();
AssignPhysicalPasses();
BuildPhysicalPasses();
//BuildBarriers();
//BuildPhysicalBarriers();
std::vector<BakedFrameGraph::PassData> bakedPasses;
bakedPasses.reserve(m_pending.physicalPasses.size());
std::size_t renderPassIndex = 0;
for (auto& physicalPass : m_pending.physicalPasses)
{
auto& bakedPass = bakedPasses.emplace_back();
bakedPass.renderPass = std::move(m_pending.renderPasses[renderPassIndex++]);
bakedPass.transitions = std::move(physicalPass.textureTransitions);
for (auto& subpass : physicalPass.passes)
{
const FramePass& framePass = m_framePasses[subpass.passIndex];
bakedPass.executionCallback = framePass.GetExecutionCallback(); //< FIXME
auto& bakedSubpass = bakedPass.subpasses.emplace_back();
bakedSubpass.commandCallback = framePass.GetCommandCallback();
for (const auto& output : framePass.GetOutputs())
bakedPass.outputTextureIndices.push_back(Retrieve(m_pending.attachmentToTextures, output.attachmentId));
if (std::size_t attachmentId = framePass.GetDepthStencilOutput(); attachmentId != FramePass::InvalidAttachmentId)
bakedPass.outputTextureIndices.push_back(Retrieve(m_pending.attachmentToTextures, attachmentId));
else if (std::size_t attachmentId = framePass.GetDepthStencilInput(); attachmentId != FramePass::InvalidAttachmentId)
bakedPass.outputTextureIndices.push_back(Retrieve(m_pending.attachmentToTextures, attachmentId)); //< FIXME?
}
}
std::vector<BakedFrameGraph::TextureData> bakedTextures;
bakedTextures.reserve(m_pending.textures.size());
for (auto& texture : m_pending.textures)
{
auto& bakedTexture = bakedTextures.emplace_back();
bakedTexture.format = texture.format;
bakedTexture.height = texture.height;
bakedTexture.usage = texture.usage;
bakedTexture.width = texture.width;
}
return BakedFrameGraph(std::move(bakedPasses), std::move(bakedTextures), std::move(m_pending.attachmentToTextures), std::move(m_pending.passIdToPhysicalPassIndex));
}
void FrameGraph::AssignPhysicalPasses()
{
auto ShouldMerge = [&](const FramePass& prevPass, const FramePass& nextPass)
{
//TODO
return false;
};
for (std::size_t passIndex = 0; passIndex < m_pending.passList.size();)
{
std::size_t mergeEnd = passIndex + 1;
for (; mergeEnd < m_pending.passList.size(); ++mergeEnd)
{
bool merge = true;
for (std::size_t mergeStart = passIndex; mergeStart < mergeEnd; ++mergeStart)
{
if (!ShouldMerge(m_framePasses[m_pending.passList[mergeStart]], m_framePasses[m_pending.passList[mergeEnd]]))
{
merge = false;
break;
}
}
if (!merge)
break;
}
std::size_t physPassIndex = m_pending.physicalPasses.size();
PhysicalPassData& currentPass = m_pending.physicalPasses.emplace_back();
auto begin = m_pending.passList.begin() + passIndex;
auto end = m_pending.passList.begin() + mergeEnd;
for (; begin < end; ++begin)
{
auto& subpass = currentPass.passes.emplace_back();
subpass.passIndex = *begin;
m_pending.passIdToPhysicalPassIndex.emplace(subpass.passIndex, physPassIndex);
}
passIndex = mergeEnd;
}
}
void FrameGraph::AssignPhysicalTextures()
{
auto RegisterTexture = [&](std::size_t attachmentIndex)
{
if (auto it = m_pending.attachmentToTextures.find(attachmentIndex); it == m_pending.attachmentToTextures.end())
{
std::size_t textureId = m_pending.textures.size();
m_pending.attachmentToTextures.emplace(attachmentIndex, textureId);
TextureData& data = m_pending.textures.emplace_back();
data.format = m_attachments[attachmentIndex].format;
data.width = 100'000;
data.height = 100'000;
return textureId;
}
else
return it->second;
};
for (std::size_t passIndex : m_pending.passList)
{
const FramePass& framePass = m_framePasses[passIndex];
for (const auto& input : framePass.GetInputs())
{
std::size_t textureId = RegisterTexture(input.attachmentId);
TextureData& attachmentData = m_pending.textures[textureId];
attachmentData.usage |= TextureUsage::ShaderSampling;
}
for (const auto& output : framePass.GetOutputs())
{
std::size_t textureId = RegisterTexture(output.attachmentId);
TextureData& 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);
TextureData& attachmentData = m_pending.textures[textureId];
attachmentData.usage |= TextureUsage::DepthStencilAttachment;
if (std::size_t depthStencilOutput = framePass.GetDepthStencilOutput(); depthStencilOutput != FramePass::InvalidAttachmentId)
{
if (auto it = m_pending.attachmentToTextures.find(depthStencilOutput); it == m_pending.attachmentToTextures.end())
m_pending.attachmentToTextures.emplace(depthStencilOutput, textureId);
else
throw std::runtime_error("depth-stencil output already assigned");
}
}
if (std::size_t depthStencilOutput = framePass.GetDepthStencilOutput(); depthStencilOutput != FramePass::InvalidAttachmentId)
{
std::size_t textureId = RegisterTexture(depthStencilOutput);
TextureData& attachmentData = m_pending.textures[textureId];
attachmentData.usage |= TextureUsage::DepthStencilAttachment;
}
}
// Add TextureUsage::Sampled to backbuffer output
auto it = m_pending.attachmentToTextures.find(m_pending.backbufferResourceIndex);
assert(it != m_pending.attachmentToTextures.end());
auto& backbufferTexture = m_pending.textures[it->second];
backbufferTexture.usage |= TextureUsage::ShaderSampling;
}
void FrameGraph::BuildBarriers()
{
assert(m_pending.barrierList.empty());
m_pending.barrierList.reserve(m_pending.passList.size());
auto GetBarrier = [&](std::vector<Barrier>& barriers, std::size_t attachmentId) -> Barrier&
{
std::size_t textureId = Retrieve(m_pending.attachmentToTextures, attachmentId);
auto it = std::find_if(barriers.begin(), barriers.end(), [&](const Barrier& barrier) { return barrier.textureId == textureId; });
if (it != barriers.end())
return *it;
else
{
// Insert a new barrier
auto& barrier = barriers.emplace_back();
barrier.textureId = textureId;
barrier.layout = TextureLayout::Undefined;
return barrier;
}
};
for (std::size_t passId : m_pending.passList)
{
const FramePass& framePass = m_framePasses[passId];
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); };
for (const auto& input : framePass.GetInputs())
{
auto& barrier = GetInvalidationBarrier(input.attachmentId);
if (barrier.layout != TextureLayout::Undefined)
throw std::runtime_error("layout mismatch");
barrier.access |= MemoryAccess::ColorRead | MemoryAccess::ColorWrite;
barrier.stages |= PipelineStage::ColorOutput;
barrier.layout = TextureLayout::ColorInput;
}
for (const auto& output : framePass.GetOutputs())
{
auto& barrier = GetFlushBarrier(output.attachmentId);
if (barrier.layout != TextureLayout::Undefined)
throw std::runtime_error("layout mismatch");
barrier.access |= MemoryAccess::ColorRead | MemoryAccess::ColorWrite;
barrier.stages |= PipelineStage::ColorOutput;
barrier.layout = TextureLayout::ColorOutput;
}
std::size_t dsInputAttachment = framePass.GetDepthStencilInput();
std::size_t dsOutputAttachement = framePass.GetDepthStencilOutput();
bool depthRead = false;
if (dsInputAttachment != FramePass::InvalidAttachmentId && dsOutputAttachement != FramePass::InvalidAttachmentId)
{
// DS input/output
auto& 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;
auto& flushBarrier = GetInvalidationBarrier(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)
throw std::runtime_error("layout mismatch");
invalidationBarrier.layout = TextureLayout::DepthStencilReadWrite;
invalidationBarrier.access = MemoryAccess::DepthStencilRead;
invalidationBarrier.stages = PipelineStage::FragmentTestsEarly | PipelineStage::FragmentTestsLate;
}
else if (dsOutputAttachement != FramePass::InvalidAttachmentId)
{
// DS output-only
auto& flushBarrier = GetInvalidationBarrier(dsOutputAttachement);
if (flushBarrier.layout != TextureLayout::Undefined)
throw std::runtime_error("layout mismatch");
flushBarrier.layout = TextureLayout::DepthStencilReadWrite;
flushBarrier.access = MemoryAccess::DepthStencilWrite;
flushBarrier.stages = PipelineStage::FragmentTestsLate;
}
}
}
void FrameGraph::BuildPhysicalBarriers()
{
struct TextureStates
{
MemoryAccessFlags invalidatedAccesses;
MemoryAccessFlags flushedAccesses;
PipelineStageFlags invalidatedStages;
PipelineStageFlags flushedStages;
TextureLayout initialLayout = TextureLayout::Undefined;
TextureLayout finalLayout = TextureLayout::Undefined;
};
std::vector<TextureStates> textureStates;
auto barriersIt = m_pending.barrierList.begin();
for (auto& physicalPass : m_pending.physicalPasses)
{
textureStates.clear();
textureStates.resize(m_pending.textures.size());
for (auto& subpass : physicalPass.passes)
{
auto& barriers = *barriersIt++;
for (auto& invalidation : barriers.invalidationBarriers)
{
auto& states = textureStates[invalidation.textureId];
if (states.initialLayout == TextureLayout::Undefined)
{
states.invalidatedAccesses |= invalidation.access;
states.invalidatedStages |= invalidation.stages;
states.initialLayout = invalidation.layout;
}
states.finalLayout = invalidation.layout;
states.flushedAccesses = 0;
states.flushedStages = 0;
}
for (auto& flush : barriers.flushBarriers)
{
auto& states = textureStates[flush.textureId];
states.flushedAccesses |= flush.access;
states.flushedStages |= flush.stages;
states.finalLayout = flush.layout;
if (states.initialLayout == TextureLayout::Undefined)
{
// First flush in a render pass needs a matching invalidation
states.initialLayout = flush.layout;
states.invalidatedAccesses = flush.access;
states.invalidatedStages = flush.stages;
if (states.invalidatedAccesses & MemoryAccess::ColorWrite)
states.invalidatedAccesses |= MemoryAccess::ColorRead;
if (states.invalidatedAccesses & MemoryAccess::DepthStencilWrite)
states.invalidatedAccesses |= MemoryAccess::DepthStencilRead;
if (states.invalidatedAccesses & MemoryAccess::ShaderWrite)
states.invalidatedAccesses |= MemoryAccess::ShaderRead;
// TODO: Discard resource
}
}
}
for (std::size_t textureId = 0; textureId < textureStates.size(); ++textureId)
{
const auto& state = textureStates[textureId];
if (state.initialLayout == TextureLayout::Undefined && state.finalLayout == TextureLayout::Undefined)
continue; //< Texture wasn't touched in this pass
assert(state.finalLayout != TextureLayout::Undefined);
// TODO: Register invalidation
if (state.flushedAccesses)
; // TODO: Register flush
if (state.invalidatedAccesses)
; // TODO: Register flush
}
}
}
void FrameGraph::BuildPhysicalPassDependencies(std::size_t colorAttachmentCount, bool hasDepthStencilAttachment, std::vector<RenderPass::Attachment>& renderPassAttachments, std::vector<RenderPass::SubpassDescription>& subpasses, std::vector<RenderPass::SubpassDependency>& dependencies)
{
if (hasDepthStencilAttachment)
{
auto& depthStencilAttachment = renderPassAttachments[colorAttachmentCount];
if (PixelFormatInfo::GetContent(depthStencilAttachment.format) == PixelFormatContent_DepthStencil)
{
depthStencilAttachment.stencilLoadOp = depthStencilAttachment.loadOp;
depthStencilAttachment.stencilStoreOp = depthStencilAttachment.storeOp;
}
else
{
depthStencilAttachment.stencilLoadOp = AttachmentLoadOp::Discard;
depthStencilAttachment.stencilStoreOp = AttachmentStoreOp::Discard;
}
}
struct SubpassInfo
{
bool hasColorWrite = false;
bool hasDepthStencilRead = false;
bool hasDepthStencilWrite = false;
bool externalColorSynchronization = false;
bool externalDepthSynchronization = false;
};
StackArray<SubpassInfo> subpassInfo = NazaraStackArray(SubpassInfo, subpasses.size());
for (std::size_t attachmentIndex = 0; attachmentIndex < renderPassAttachments.size(); ++attachmentIndex)
{
bool used = false; //< has the attachment already been used in a previous subpass
TextureLayout currentLayout = renderPassAttachments[attachmentIndex].initialLayout;
auto FindColor = [&](std::size_t subpassIndex) -> RenderPass::AttachmentReference*
{
auto& subpassDesc = subpasses[subpassIndex];
for (auto& colorReference : subpassDesc.colorAttachment)
{
if (colorReference.attachmentIndex == attachmentIndex)
return &colorReference;
}
return nullptr;
};
auto FindDepthStencil = [&](std::size_t subpassIndex) -> RenderPass::AttachmentReference*
{
auto& subpassDesc = subpasses[subpassIndex];
if (subpassDesc.depthStencilAttachment && subpassDesc.depthStencilAttachment->attachmentIndex == attachmentIndex)
return &subpassDesc.depthStencilAttachment.value();
return nullptr;
};
for (std::size_t subpassIndex = 0; subpassIndex < subpasses.size(); ++subpassIndex)
{
RenderPass::SubpassDescription& subpassDesc = subpasses[subpassIndex];
RenderPass::AttachmentReference* colorAttachment = FindColor(subpassIndex);
RenderPass::AttachmentReference* depthStencilAttachment = FindDepthStencil(subpassIndex);
if (!colorAttachment && !depthStencilAttachment)
{
if (used)
subpassDesc.preserveAttachments.push_back(attachmentIndex);
continue;
}
if (colorAttachment)
{
subpassInfo[subpassIndex].hasColorWrite = true;
currentLayout = colorAttachment->attachmentLayout;
// If this subpass performs a layout change, color must be synchronized with external
if (!used && renderPassAttachments[attachmentIndex].initialLayout != currentLayout)
subpassInfo[subpassIndex].externalColorSynchronization = true;
}
else if (depthStencilAttachment)
{
if (depthStencilAttachment->attachmentLayout == TextureLayout::DepthStencilReadWrite)
subpassInfo[subpassIndex].hasDepthStencilWrite = true;
subpassInfo[subpassIndex].hasDepthStencilRead = true;
currentLayout = depthStencilAttachment->attachmentLayout;
// If this subpass performs a layout change, depth must be synchronized with external
if (!used && renderPassAttachments[attachmentIndex].initialLayout != currentLayout)
subpassInfo[subpassIndex].externalDepthSynchronization = true;
}
used = true;
}
}
// Handle external subpass dependencies
for (std::size_t subpassIndex = 0; subpassIndex < subpasses.size(); ++subpassIndex)
{
const auto& sync = subpassInfo[subpassIndex];
if (!sync.externalColorSynchronization && !sync.externalDepthSynchronization)
continue;
auto& subpassDependency = dependencies.emplace_back();
subpassDependency.fromSubpassIndex = RenderPass::ExternalSubpassIndex;
subpassDependency.toSubpassIndex = subpassIndex;
// TODO: Handle bottom of pipe?
if (sync.externalColorSynchronization)
{
subpassDependency.fromStages |= PipelineStage::ColorOutput;
subpassDependency.fromAccessFlags |= MemoryAccess::ColorWrite;
subpassDependency.toStages |= PipelineStage::ColorOutput;
subpassDependency.toAccessFlags |= MemoryAccess::ColorRead | MemoryAccess::ColorWrite;
}
if (sync.externalDepthSynchronization)
{
subpassDependency.fromStages |= PipelineStage::FragmentTestsLate;
subpassDependency.fromAccessFlags |= MemoryAccess::DepthStencilWrite;
subpassDependency.toStages |= PipelineStage::FragmentTestsEarly | PipelineStage::FragmentTestsLate;
subpassDependency.toAccessFlags |= MemoryAccess::DepthStencilRead | MemoryAccess::DepthStencilWrite;
}
}
// TODO: Handle self-dependencies
// Handle pass to pass dependencies
for (std::size_t subpassIndex = 1; subpassIndex < subpasses.size(); ++subpassIndex)
{
auto& subpassDependency = dependencies.emplace_back();
subpassDependency.fromSubpassIndex = subpassIndex - 1;
subpassDependency.toSubpassIndex = subpassIndex;
subpassDependency.tilable = true;
const auto& prevSync = subpassInfo[subpassDependency.fromSubpassIndex];
const auto& sync = subpassInfo[subpassDependency.toSubpassIndex];
// Previous pass flags
if (prevSync.hasColorWrite)
{
subpassDependency.fromAccessFlags = MemoryAccess::ColorWrite;
subpassDependency.fromStages = PipelineStage::ColorOutput;
}
if (prevSync.hasDepthStencilRead)
{
subpassDependency.fromStages |= PipelineStage::FragmentTestsEarly | PipelineStage::FragmentTestsLate;
subpassDependency.fromAccessFlags |= MemoryAccess::DepthStencilRead;
}
if (prevSync.hasDepthStencilWrite)
{
subpassDependency.fromStages |= PipelineStage::FragmentTestsEarly | PipelineStage::FragmentTestsLate;
subpassDependency.fromAccessFlags |= MemoryAccess::DepthStencilWrite;
}
// Current pass flags
if (sync.hasColorWrite)
{
subpassDependency.toStages = PipelineStage::ColorOutput;
subpassDependency.toAccessFlags = MemoryAccess::ColorRead | MemoryAccess::ColorWrite;
}
if (sync.hasDepthStencilRead)
{
subpassDependency.toStages |= PipelineStage::FragmentTestsEarly | PipelineStage::FragmentTestsLate;
subpassDependency.toAccessFlags |= MemoryAccess::DepthStencilRead;
}
if (sync.hasDepthStencilWrite)
{
subpassDependency.toStages |= PipelineStage::FragmentTestsEarly | PipelineStage::FragmentTestsLate;
subpassDependency.toAccessFlags |= MemoryAccess::DepthStencilRead | MemoryAccess::DepthStencilWrite;
}
// TODO: Handle InputAttachment
}
}
void FrameGraph::BuildPhysicalPasses()
{
RenderDevice& renderDevice = Graphics::Instance()->GetRenderDevice();
std::unordered_map<std::size_t /*textureId*/, TextureLayout> textureLayouts;
std::size_t physicalPassIndex = 0;
for (auto& physicalPass : m_pending.physicalPasses)
{
std::unordered_map<std::size_t /*textureId*/, std::size_t /*attachmentIndex*/> usedTextureAttachments;
std::size_t depthStencilAttachmentId;
std::optional<std::size_t> depthStencilAttachmentIndex;
std::vector<RenderPass::Attachment> renderPassAttachments;
std::vector<RenderPass::SubpassDescription> subpassesDesc;
std::vector<RenderPass::SubpassDependency> subpassesDeps;
auto RegisterColorInput = [&](const FramePass::Input& input, PhysicalPassData::Subpass& subpass)
{
std::size_t textureId = Retrieve(m_pending.attachmentToTextures, input.attachmentId);
auto it = textureLayouts.find(textureId);
assert(it != textureLayouts.end());
TextureLayout& textureLayout = it->second;
if (textureLayout != TextureLayout::ColorInput)
{
auto& transition = physicalPass.textureTransitions.emplace_back();
transition.textureId = textureId;
transition.srcAccessMask = MemoryAccess::ColorWrite;
transition.srcStageMask = PipelineStage::ColorOutput;
transition.dstStageMask = PipelineStage::ColorOutput;
transition.dstAccessMask = MemoryAccess::ColorRead | MemoryAccess::ColorWrite;
transition.oldLayout = textureLayout;
transition.newLayout = TextureLayout::ColorInput;
}
textureLayout = TextureLayout::ColorInput;
};
auto RegisterColorOutput = [&](const FramePass::Output& output)
{
std::size_t textureId = Retrieve(m_pending.attachmentToTextures, output.attachmentId);
TextureLayout initialLayout = TextureLayout::Undefined;
auto layoutIt = textureLayouts.find(textureId);
if (layoutIt != textureLayouts.end())
{
initialLayout = layoutIt->second;
layoutIt->second = TextureLayout::ColorOutput;
}
else
textureLayouts.emplace(textureId, TextureLayout::ColorOutput);
auto it = usedTextureAttachments.find(textureId);
if (it != usedTextureAttachments.end())
return it->second;
std::size_t attachmentIndex = renderPassAttachments.size();
auto& attachment = renderPassAttachments.emplace_back();
attachment.format = m_pending.textures[textureId].format;
attachment.initialLayout = initialLayout;
attachment.loadOp = (output.clearColor) ? AttachmentLoadOp::Clear : AttachmentLoadOp::Discard;
attachment.storeOp = AttachmentStoreOp::Store;
attachment.stencilLoadOp = AttachmentLoadOp::Discard;
attachment.stencilStoreOp = AttachmentStoreOp::Discard;
usedTextureAttachments.emplace(textureId, attachmentIndex);
return attachmentIndex;
};
auto RegisterDepthStencil = [&](std::size_t attachmentId, TextureLayout textureLayout, bool* first) -> RenderPass::Attachment&
{
if (depthStencilAttachmentIndex)
{
assert(depthStencilAttachmentId == attachmentId);
*first = false;
return renderPassAttachments[depthStencilAttachmentIndex.value()];
}
*first = true;
std::size_t textureId = Retrieve(m_pending.attachmentToTextures, attachmentId);
TextureLayout initialLayout = TextureLayout::Undefined;
auto layoutIt = textureLayouts.find(textureId);
if (layoutIt != textureLayouts.end())
{
initialLayout = layoutIt->second;
layoutIt->second = textureLayout;
}
else
textureLayouts.emplace(textureId, textureLayout);
depthStencilAttachmentId = attachmentId;
depthStencilAttachmentIndex = renderPassAttachments.size();
usedTextureAttachments.emplace(textureId, *depthStencilAttachmentIndex);
auto& depthStencilAttachment = renderPassAttachments.emplace_back();
depthStencilAttachment.format = m_pending.textures[textureId].format;
depthStencilAttachment.initialLayout = initialLayout;
return depthStencilAttachment;
};
std::size_t subpassIndex = 0;
std::vector<RenderPass::AttachmentReference> colorAttachments;
for (auto& subpass : physicalPass.passes)
{
const FramePass& framePass = m_framePasses[subpass.passIndex];
const auto& subpassInputs = framePass.GetInputs();
const auto& subpassOutputs = framePass.GetOutputs();
colorAttachments.reserve(subpassOutputs.size());
for (const auto& input : subpassInputs)
RegisterColorInput(input, subpass);
bool hasColorWrite = false;
for (const auto& output : subpassOutputs)
{
hasColorWrite = true;
std::size_t attachmentIndex = RegisterColorOutput(output);
colorAttachments.push_back({
attachmentIndex,
TextureLayout::ColorOutput
});
}
}
std::size_t colorAttachmentCount = renderPassAttachments.size();
std::optional<RenderPass::AttachmentReference> depthStencilAttachment;
for (auto& subpass : physicalPass.passes)
{
const FramePass& framePass = m_framePasses[subpass.passIndex];
std::size_t dsInputAttachment = framePass.GetDepthStencilInput();
std::size_t dsOutputAttachement = framePass.GetDepthStencilOutput();
bool depthRead = false;
if (dsInputAttachment != FramePass::InvalidAttachmentId && dsOutputAttachement != FramePass::InvalidAttachmentId)
{
// DS input/output
bool first;
auto& dsAttachment = RegisterDepthStencil(dsInputAttachment, TextureLayout::DepthStencilReadWrite, &first);
if (first)
{
dsAttachment.loadOp = AttachmentLoadOp::Load;
dsAttachment.storeOp = AttachmentStoreOp::Store;
}
depthStencilAttachment = RenderPass::AttachmentReference{
depthStencilAttachmentIndex.value(),
TextureLayout::DepthStencilReadWrite
};
}
else if (dsInputAttachment != FramePass::InvalidAttachmentId)
{
// DS input-only
bool first;
auto& dsAttachment = RegisterDepthStencil(dsInputAttachment, TextureLayout::DepthStencilReadOnly, &first);
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)
{
std::size_t readPhysicalPassIndex = Retrieve(m_pending.passIdToPhysicalPassIndex, passIndex);
if (readPhysicalPassIndex > physicalPassIndex) //< Read in a future pass?
{
// Yes, store it
dsAttachment.storeOp = AttachmentStoreOp::Store;
break;
}
}
}
}
depthStencilAttachment = RenderPass::AttachmentReference{
depthStencilAttachmentIndex.value(),
TextureLayout::DepthStencilReadOnly
};
}
else if (dsOutputAttachement != FramePass::InvalidAttachmentId)
{
// DS output-only
bool first;
auto& dsAttachment = RegisterDepthStencil(dsOutputAttachement, TextureLayout::DepthStencilReadWrite, &first);
if (first)
{
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{
depthStencilAttachmentIndex.value(),
TextureLayout::DepthStencilReadWrite
};
}
subpassesDesc.push_back({
std::move(colorAttachments),
{},
{},
std::move(depthStencilAttachment)
});
subpassIndex++;
}
// Assign final layout (TODO: Use this to perform layouts useful for future passes?)
for (const auto& [textureId, attachmentIndex] : usedTextureAttachments)
{
auto layoutIt = textureLayouts.find(textureId);
assert(layoutIt != textureLayouts.end());
auto& attachment = renderPassAttachments[attachmentIndex];
attachment.finalLayout = layoutIt->second;
}
BuildPhysicalPassDependencies(colorAttachmentCount, depthStencilAttachmentIndex.has_value(), renderPassAttachments, subpassesDesc, subpassesDeps);
m_pending.renderPasses.push_back(renderDevice.InstantiateRenderPass(std::move(renderPassAttachments), std::move(subpassesDesc), std::move(subpassesDeps)));
physicalPassIndex++;
}
}
void FrameGraph::BuildReadWriteList()
{
for (std::size_t passIndex = 0; passIndex < m_framePasses.size(); ++passIndex)
{
const FramePass& framePass = m_framePasses[passIndex];
for (const auto& input : framePass.GetInputs())
UniquePushBack(m_pending.attachmentReadList[input.attachmentId], passIndex);
if (std::size_t depthStencilId = framePass.GetDepthStencilInput(); depthStencilId != FramePass::InvalidAttachmentId)
UniquePushBack(m_pending.attachmentReadList[depthStencilId], passIndex);
for (const auto& output : framePass.GetOutputs())
UniquePushBack(m_pending.attachmentWriteList[output.attachmentId], passIndex);
if (std::size_t depthStencilId = framePass.GetDepthStencilOutput(); depthStencilId != FramePass::InvalidAttachmentId)
UniquePushBack(m_pending.attachmentWriteList[depthStencilId], passIndex);
}
}
void FrameGraph::ReorderPasses()
{
/* TODO */
}
void FrameGraph::TraverseGraph(std::size_t passIndex)
{
m_pending.passList.push_back(passIndex);
const FramePass& framePass = m_framePasses[passIndex];
for (const auto& input : framePass.GetInputs())
{
auto it = m_pending.attachmentWriteList.find(input.attachmentId);
if (it != m_pending.attachmentWriteList.end())
{
const PassList& dependencyPassList = it->second;
for (std::size_t dependencyPass : dependencyPassList)
TraverseGraph(dependencyPass);
}
}
}
void FrameGraph::RemoveDuplicatePasses()
{
// A way to remove duplicates from a std::vector without sorting it
std::unordered_set<std::size_t> seen;
auto itRead = m_pending.passList.begin();
auto itWrite = m_pending.passList.begin();
while (itRead != m_pending.passList.end())
{
std::size_t passIndex = *itRead;
if (seen.find(passIndex) == seen.end())
{
if (itRead != itWrite)
*itWrite++ = passIndex;
else
++itWrite;
}
++itRead;
}
m_pending.passList.erase(itWrite, m_pending.passList.end());
}
}

View File

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

View File

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

View File

@@ -19,9 +19,9 @@ namespace Nz
m_commandBuffer.BeginDebugRegion(regionName, color);
}
void OpenGLCommandBufferBuilder::BeginRenderPass(const Framebuffer& framebuffer, const RenderPass& renderPass, Nz::Recti /*renderRect*/, std::initializer_list<ClearValues> clearValues)
void OpenGLCommandBufferBuilder::BeginRenderPass(const Framebuffer& framebuffer, const RenderPass& renderPass, Nz::Recti /*renderRect*/, const ClearValues* clearValues, std::size_t clearValueCount)
{
m_commandBuffer.SetFramebuffer(static_cast<const OpenGLFramebuffer&>(framebuffer), renderPass, clearValues);
m_commandBuffer.SetFramebuffer(static_cast<const OpenGLFramebuffer&>(framebuffer), renderPass, clearValues, clearValueCount);
}
void OpenGLCommandBufferBuilder::BindIndexBuffer(Nz::AbstractBuffer* indexBuffer, UInt64 offset)
@@ -84,14 +84,22 @@ namespace Nz
void OpenGLCommandBufferBuilder::EndRenderPass()
{
/* nothing to do */
}
void OpenGLCommandBufferBuilder::NextSubpass()
{
/* nothing to do */
}
void OpenGLCommandBufferBuilder::PreTransferBarrier()
{
/* nothing to do */
}
void OpenGLCommandBufferBuilder::PostTransferBarrier()
{
/* nothing to do */
}
void OpenGLCommandBufferBuilder::SetScissor(Nz::Recti scissorRegion)
@@ -103,4 +111,9 @@ namespace Nz
{
m_commandBuffer.SetViewport(viewportRegion);
}
void OpenGLCommandBufferBuilder::TextureBarrier(PipelineStageFlags srcStageMask, PipelineStageFlags dstStageMask, MemoryAccessFlags srcAccessMask, MemoryAccessFlags dstAccessMask, TextureLayout oldLayout, TextureLayout newLayout, const Texture& texture)
{
/* nothing to do */
}
}

View File

@@ -11,6 +11,7 @@
#include <Nazara/VulkanRenderer/VulkanRenderPipelineLayout.hpp>
#include <Nazara/VulkanRenderer/VulkanSingleFramebuffer.hpp>
#include <Nazara/VulkanRenderer/VulkanShaderBinding.hpp>
#include <Nazara/VulkanRenderer/VulkanTexture.hpp>
#include <Nazara/VulkanRenderer/VulkanUploadPool.hpp>
#include <Nazara/VulkanRenderer/Debug.hpp>
@@ -26,7 +27,7 @@ namespace Nz
m_commandBuffer.BeginDebugRegion(regionNameEOS.data(), color);
}
void VulkanCommandBufferBuilder::BeginRenderPass(const Framebuffer& framebuffer, const RenderPass& renderPass, Nz::Recti renderRect, std::initializer_list<ClearValues> clearValues)
void VulkanCommandBufferBuilder::BeginRenderPass(const Framebuffer& framebuffer, const RenderPass& renderPass, Nz::Recti renderRect, const ClearValues* clearValues, std::size_t clearValueCount)
{
const VulkanRenderPass& vkRenderPass = static_cast<const VulkanRenderPass&>(renderPass);
@@ -49,14 +50,15 @@ namespace Nz
throw std::runtime_error("Unhandled framebuffer type " + std::to_string(UnderlyingCast(vkFramebuffer.GetType())));
}();
StackArray<VkClearValue> vkClearValues = NazaraStackArray(VkClearValue, clearValues.size());
std::size_t attachmentCount = vkRenderPass.GetAttachmentCount();
std::size_t index = 0;
for (const ClearValues& values : clearValues)
StackArray<VkClearValue> vkClearValues = NazaraStackArray(VkClearValue, attachmentCount);
for (std::size_t i = 0; i < attachmentCount; ++i)
{
auto& vkValues = vkClearValues[index];
const auto& values = (i < clearValueCount) ? clearValues[i] : CommandBufferBuilder::ClearValues{};
auto& vkValues = vkClearValues[i];
if (PixelFormatInfo::GetContent(vkRenderPass.GetAttachment(index).format) == PixelFormatContent_ColorRGBA)
if (PixelFormatInfo::GetContent(vkRenderPass.GetAttachment(i).format) == PixelFormatContent_ColorRGBA)
{
vkValues.color.float32[0] = values.color.r / 255.f;
vkValues.color.float32[1] = values.color.g / 255.f;
@@ -68,8 +70,6 @@ namespace Nz
vkValues.depthStencil.depth = values.depth;
vkValues.depthStencil.stencil = values.stencil;
}
index++;
}
VkRenderPassBeginInfo beginInfo = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO };
@@ -157,6 +157,11 @@ namespace Nz
m_currentRenderPass = nullptr;
}
void VulkanCommandBufferBuilder::NextSubpass()
{
m_commandBuffer.NextSubpass();
}
void VulkanCommandBufferBuilder::PreTransferBarrier()
{
m_commandBuffer.MemoryBarrier(VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0U, VK_ACCESS_TRANSFER_READ_BIT);
@@ -176,4 +181,11 @@ namespace Nz
{
m_commandBuffer.SetViewport(Nz::Rectf(viewportRegion), 0.f, 1.f);
}
void VulkanCommandBufferBuilder::TextureBarrier(PipelineStageFlags srcStageMask, PipelineStageFlags dstStageMask, MemoryAccessFlags srcAccessMask, MemoryAccessFlags dstAccessMask, TextureLayout oldLayout, TextureLayout newLayout, const Texture& texture)
{
const VulkanTexture& vkTexture = static_cast<const VulkanTexture&>(texture);
m_commandBuffer.ImageBarrier(ToVulkan(srcStageMask), ToVulkan(dstStageMask), VkDependencyFlags(0), ToVulkan(srcAccessMask), ToVulkan(dstAccessMask), ToVulkan(oldLayout), ToVulkan(newLayout), vkTexture.GetImage(), VK_IMAGE_ASPECT_COLOR_BIT);
}
}

View File

@@ -40,6 +40,7 @@ namespace Nz
}
StackVector<VkAttachmentReference> vkAttachmentReferences = NazaraStackVector(VkAttachmentReference, totalAttachmentReference);
StackVector<UInt32> vkPreserveAttachments = NazaraStackVector(UInt32, attachments.size());
StackVector<VkSubpassDescription> vkSubpassDescs = NazaraStackVector(VkSubpassDescription, m_subpassDescriptions.size());
for (const SubpassDescription& subpassInfo : m_subpassDescriptions)
@@ -72,6 +73,10 @@ namespace Nz
});
}
assert(subpassInfo.preserveAttachments.size() <= vkPreserveAttachments.size());
for (std::size_t attachmentIndex : subpassInfo.preserveAttachments)
vkPreserveAttachments.push_back(UInt32(attachmentIndex));
vkSubpassDescs.push_back({
VkSubpassDescriptionFlags(0),
VK_PIPELINE_BIND_POINT_GRAPHICS,
@@ -81,17 +86,25 @@ namespace Nz
(!subpassInfo.colorAttachment.empty()) ? &vkAttachmentReferences[colorAttachmentIndex] : nullptr,
nullptr,
(subpassInfo.depthStencilAttachment) ? &vkAttachmentReferences[depthStencilAttachmentIndex] : nullptr,
0,
nullptr
UInt32(vkPreserveAttachments.size()),
(!vkPreserveAttachments.empty()) ? &vkPreserveAttachments[0] : nullptr
});
}
StackVector<VkSubpassDependency> vkSubpassDeps = NazaraStackVector(VkSubpassDependency, m_subpassDependencies.size());
for (const SubpassDependency& subpassDependency : m_subpassDependencies)
{
auto ToSubPassIndex = [](std::size_t subpassIndex) -> UInt32
{
if (subpassIndex == ExternalSubpassIndex)
return VK_SUBPASS_EXTERNAL;
return UInt32(subpassIndex);
};
vkSubpassDeps.push_back({
UInt32(subpassDependency.fromSubpassIndex),
UInt32(subpassDependency.toSubpassIndex),
ToSubPassIndex(subpassDependency.fromSubpassIndex),
ToSubPassIndex(subpassDependency.toSubpassIndex),
ToVulkan(subpassDependency.fromStages),
ToVulkan(subpassDependency.toStages),
ToVulkan(subpassDependency.fromAccessFlags),

View File

@@ -4,6 +4,7 @@
#include <Nazara/VulkanRenderer/VulkanSurface.hpp>
#include <Nazara/VulkanRenderer/Vulkan.hpp>
#include <vulkan/vk_sdk_platform.h>
#include <Nazara/VulkanRenderer/Debug.hpp>
namespace Nz