First step in reworking how rendertargets work in the framegraph

The ultimate goal is to get rid of the concept of render targets in
the framegraph, and this is the first step. Here RenderTarget doesn't
store the discard flags anymore. These flags are now calculated
per-resource, per-pass.

A side effect of this change is that the discard flags are now slightly
better, as they don't contain attachments that don't exist.
i.e. we won't see the STENCIL flag set for instance.
This commit is contained in:
Pixelflinger
2020-02-25 23:53:56 -08:00
committed by Mathias Agopian
parent 82523bd1d3
commit fcd7e198c2
9 changed files with 90 additions and 97 deletions

View File

@@ -152,10 +152,36 @@ FrameGraphPassResources::getRenderTarget(FrameGraphRenderTargetHandle handle, ui
assert(info.target);
// overwrite discard flags with the per-rendertarget (per-pass) computed value
info.params.flags.discardStart = renderTarget.targetFlags.discardStart;
info.params.flags.discardEnd = renderTarget.targetFlags.discardEnd;
info.params.flags.discardStart = TargetBufferFlags::NONE;
info.params.flags.discardEnd = TargetBufferFlags::NONE;
info.params.flags.clear = renderTarget.userClearFlags;
static constexpr TargetBufferFlags flags[] = {
TargetBufferFlags::COLOR,
TargetBufferFlags::DEPTH,
TargetBufferFlags::STENCIL };
auto& resourceNodes = fg.mResourceNodes;
for (size_t i = 0; i <renderTarget.desc.attachments.textures.size(); i++) {
FrameGraphHandle attachment = renderTarget.desc.attachments.textures[i];
if (attachment.isValid()) {
if (resourceNodes[attachment.index].resource->discardStart) {
info.params.flags.discardStart |= flags[i];
}
if (resourceNodes[attachment.index].resource->discardEnd) {
info.params.flags.discardEnd |= flags[i];
}
}
}
// clear implies discarding the content of the buffer
info.params.flags.discardStart |= (renderTarget.userClearFlags & TargetBufferFlags::ALL);
if (renderTarget.cache->imported) {
// we never discard more than the user flags
info.params.flags.discardStart &= renderTarget.cache->discardStart;
info.params.flags.discardEnd &= renderTarget.cache->discardEnd;
}
// check that this FrameGraphRenderTarget is indeed declared by this pass
ASSERT_POSTCONDITION_NON_FATAL(info.target,
"Pass \"%s\" doesn't declare rendertarget \"%s\" -- expect graphic corruptions",
@@ -369,57 +395,25 @@ FrameGraphRenderTargetHandle FrameGraph::importRenderTarget(const char* name,
return FrameGraphRenderTargetHandle(renderTarget.index);
}
TargetBufferFlags FrameGraph::computeDiscardFlags(DiscardPhase phase,
PassNode const* curr, PassNode const* first, fg::RenderTarget const& renderTarget) {
bool FrameGraph::computeDiscard(const Vector<FrameGraphHandle> fg::PassNode::* list,
PassNode const* curr, PassNode const* first, FrameGraphHandle attachment) {
auto& resourceNodes = mResourceNodes;
TargetBufferFlags discardFlags = TargetBufferFlags::ALL;
static constexpr TargetBufferFlags flags[] = {
TargetBufferFlags::COLOR,
TargetBufferFlags::DEPTH,
TargetBufferFlags::STENCIL };
auto const& desc = renderTarget.cache->desc;
// for each pass...
while (any(discardFlags) && curr != first) {
while (curr != first) {
PassNode const& pass = *curr++;
// TODO: maybe find a more efficient way of figuring this out
// for each resource written or read...
for (FrameGraphHandle cur : ((phase == DiscardPhase::START) ? pass.writes : pass.reads)) {
for (FrameGraphHandle cur : pass.*list) {
// for all possible attachments of our renderTarget...
ResourceEntryBase const* const pResource = resourceNodes[cur.index].resource;
for (size_t i = 0; i < desc.attachments.textures.size(); i++) {
FrameGraphHandle attachment = desc.attachments.textures[i];
if (attachment.isValid() && resourceNodes[attachment.index].resource == pResource) {
// we can't discard this attachment since it's read/written
discardFlags &= ~flags[i];
}
}
if (!discardFlags) {
break;
if (attachment.isValid() && resourceNodes[attachment.index].resource == pResource) {
return false;
}
}
}
if (phase == DiscardPhase::START) {
// clear implies discarding the content of the buffer
discardFlags |= (renderTarget.userClearFlags & TargetBufferFlags::ALL);
}
if (renderTarget.cache->imported) {
// we never discard more than the user flags
if (phase == DiscardPhase::START) {
discardFlags &= renderTarget.cache->discardStart;
}
if (phase == DiscardPhase::END) {
discardFlags &= renderTarget.cache->discardEnd;
}
}
return discardFlags;
return true;
}
FrameGraph& FrameGraph::compile() noexcept {
Vector<fg::PassNode>& passNodes = mPassNodes;
Vector<fg::RenderTarget>& renderTargets = mRenderTargets;
@@ -586,8 +580,6 @@ FrameGraph& FrameGraph::compile() noexcept {
* compute first/last users for active passes
*/
auto const first = passNodes.data();
auto const last = passNodes.data() + passNodes.size();
for (PassNode& pass : passNodes) {
if (!pass.refCount) {
assert(!pass.hasSideEffect);
@@ -608,30 +600,11 @@ FrameGraph& FrameGraph::compile() noexcept {
pResource->last = &pass;
}
for (auto i : pass.renderTargets) {
fg::RenderTarget* const pRenderTarget = &renderTargets[i];
VirtualResource* const pResource = renderTargets[i].cache;
// figure out which is the first pass to need this resource
pResource->first = pResource->first ? pResource->first : &pass;
// figure out which is the last pass to need this resource
pResource->last = &pass;
// compute this resource discard flag for this pass for this resource
// does anyone writes to this resource before us -- if so, don't discard those buffers on enter
// (i.e. if nobody wrote, no need to load from memory)
TargetBufferFlags discardStart = computeDiscardFlags(
DiscardPhase::START, first, &pass, *pRenderTarget);
// does anyone reads this resource after us -- if so, don't discard those buffers on exit
// (i.e. if nobody is going to read, no need to write back to memory)
TargetBufferFlags discardEnd = computeDiscardFlags(
DiscardPhase::END, &pass + 1, last, *pRenderTarget);
pRenderTarget->targetFlags = {
.clear = {}, // this is eventually set by the user
.discardStart = discardStart,
.discardEnd = discardEnd
};
}
}
@@ -662,16 +635,22 @@ void FrameGraph::executeInternal(PassNode const& node, DriverApi& driver) noexce
assert(node.base);
// create concrete resources and rendertargets
for (VirtualResource* resource : node.devirtualize) {
resource->create(*this);
resource->preExecuteDevirtualize(*this);
}
for (VirtualResource* resource : node.destroy) {
resource->preExecuteDestroy(*this);
}
// execute the pass
FrameGraphPassResources resources(*this, node);
node.base->execute(resources, driver);
for (VirtualResource* resource : node.devirtualize) {
resource->postExecuteDevirtualize(*this);
}
// destroy concrete resources
for (VirtualResource* resource : node.destroy) {
resource->destroy(*this);
resource->postExecuteDestroy(*this);
}
}
@@ -741,7 +720,8 @@ void FrameGraph::export_graphviz(utils::io::ostream& out) {
ResourceEntryBase const* subresource = node.resource;
out << "\"R" << node.resource->id << "_" << +node.version << "\""
"[label=\"" << node.resource->name << "\\n(version: " << +node.version << ")" "\\nid:" << node.resource->id <<
"[label=\"" << node.resource->name << "\\n(version: " << +node.version << ")"
"\\nid:" << node.resource->id <<
"\\nrefs:" << node.resource->refs;
#if UTILS_HAS_RTTI

View File

@@ -288,10 +288,8 @@ private:
FrameGraphHandle createResourceNode(fg::ResourceEntryBase* resource) noexcept;
enum class DiscardPhase { START, END };
backend::TargetBufferFlags computeDiscardFlags(DiscardPhase phase,
fg::PassNode const* curr, fg::PassNode const* first,
fg::RenderTarget const& renderTarget);
bool computeDiscard(const Vector<FrameGraphHandle> fg::PassNode::* list,
fg::PassNode const* curr, fg::PassNode const* first, FrameGraphHandle resource);
bool equals(FrameGraphRenderTarget::Descriptor const& cacheEntry,
FrameGraphRenderTarget::Descriptor const& rt) const noexcept;

View File

@@ -107,7 +107,7 @@ RenderTargetHandle ResourceAllocator::createRenderTarget(const char* name,
}
void ResourceAllocator::destroyRenderTarget(RenderTargetHandle h) noexcept {
return mBackend.destroyRenderTarget(h);
mBackend.destroyRenderTarget(h);
}
backend::TextureHandle ResourceAllocator::createTexture(const char* name,

View File

@@ -49,7 +49,7 @@ struct RenderTarget { // 32
backend::TargetBufferFlags userClearFlags{};
// set in compile
backend::RenderPassFlags targetFlags{};
backend::TargetBufferFlags clear = {}; // this is eventually set by the user
RenderTargetResource* cache = nullptr;
void resolve(FrameGraph& fg) noexcept;

View File

@@ -22,7 +22,7 @@ using namespace backend;
namespace fg {
void RenderTargetResource::create(FrameGraph& fg) noexcept {
void RenderTargetResource::preExecuteDevirtualize(FrameGraph& fg) noexcept {
if (!imported) {
if (any(attachments)) {
// devirtualize our texture handles. By this point these handles have been
@@ -61,7 +61,7 @@ void RenderTargetResource::create(FrameGraph& fg) noexcept {
}
}
void RenderTargetResource::destroy(FrameGraph& fg) noexcept {
void RenderTargetResource::postExecuteDestroy(FrameGraph& fg) noexcept {
if (!imported) {
if (targetInfo.target) {
fg.getResourceAllocator().destroyRenderTarget(targetInfo.target);

View File

@@ -69,9 +69,10 @@ struct RenderTargetResource final : public VirtualResource { // 104
// updated during execute with the current pass' discard flags
FrameGraphPassResources::RenderTargetInfo targetInfo;
void create(FrameGraph& fg) noexcept override;
void destroy(FrameGraph& fg) noexcept override;
void preExecuteDevirtualize(FrameGraph& fg) noexcept override;
void postExecuteDestroy(FrameGraph& fg) noexcept override;
void preExecuteDestroy(FrameGraph& fg) noexcept override {}
void postExecuteDevirtualize(FrameGraph& fg) noexcept override {}
};
} // namespace fg

View File

@@ -35,6 +35,14 @@ public:
ResourceEntryBase(ResourceEntryBase const&) = default;
~ResourceEntryBase() override;
void preExecuteDestroy(FrameGraph& fg) noexcept override {
discardEnd = true;
}
void postExecuteDevirtualize(FrameGraph& fg) noexcept override {
discardStart = false;
}
// constants
const char* const name;
const uint16_t id; // for debugging and graphing
@@ -45,6 +53,10 @@ public:
// computed during compile()
uint32_t refs = 0; // final reference count
// updated during execute()
bool discardStart = true;
bool discardEnd = false;
};
@@ -68,13 +80,13 @@ public:
T& getResource() noexcept { return resource; }
void create(FrameGraph& fg) noexcept override {
void preExecuteDevirtualize(FrameGraph& fg) noexcept override {
if (!imported) {
resource.create(fg, name, descriptor);
}
}
void destroy(FrameGraph& fg) noexcept override {
void postExecuteDestroy(FrameGraph& fg) noexcept override {
if (!imported) {
resource.destroy(fg);
}

View File

@@ -28,8 +28,10 @@ struct PassNode;
struct VirtualResource {
VirtualResource() noexcept = default;
VirtualResource(VirtualResource const&) = default;
virtual void create(FrameGraph& fg) noexcept = 0;
virtual void destroy(FrameGraph& fg) noexcept = 0;
virtual void preExecuteDevirtualize(FrameGraph& fg) noexcept = 0;
virtual void preExecuteDestroy(FrameGraph& fg) noexcept = 0;
virtual void postExecuteDestroy(FrameGraph& fg) noexcept = 0;
virtual void postExecuteDevirtualize(FrameGraph& fg) noexcept = 0;
virtual ~VirtualResource();
// computed during compile()

View File

@@ -60,8 +60,8 @@ TEST(FrameGraphTest, SimpleRenderPass) {
renderPassExecuted = true;
auto const& rt = resources.getRenderTarget(data.rt);
EXPECT_TRUE(rt.target);
EXPECT_EQ(TargetBufferFlags::ALL, rt.params.flags.discardStart);
EXPECT_EQ(TargetBufferFlags::DEPTH_AND_STENCIL, rt.params.flags.discardEnd);
EXPECT_EQ(TargetBufferFlags::COLOR, rt.params.flags.discardStart);
EXPECT_EQ(TargetBufferFlags::NONE, rt.params.flags.discardEnd);
});
@@ -113,8 +113,8 @@ TEST(FrameGraphTest, SimpleRenderPass2) {
renderPassExecuted = true;
auto const& rt = resources.getRenderTarget(data.rt);
EXPECT_TRUE(rt.target);
EXPECT_EQ(TargetBufferFlags::ALL, rt.params.flags.discardStart);
EXPECT_EQ(TargetBufferFlags::STENCIL, rt.params.flags.discardEnd);
EXPECT_EQ(TargetBufferFlags::COLOR_AND_DEPTH, rt.params.flags.discardStart);
EXPECT_EQ(TargetBufferFlags::NONE, rt.params.flags.discardEnd);
});
fg.present(renderPass.getData().outColor);
@@ -158,8 +158,8 @@ TEST(FrameGraphTest, ScenarioDepthPrePass) {
depthPrepassExecuted = true;
auto const& rt = resources.getRenderTarget(data.rt);
EXPECT_TRUE(rt.target);
EXPECT_EQ(TargetBufferFlags::ALL, rt.params.flags.discardStart);
EXPECT_EQ(TargetBufferFlags::COLOR_AND_STENCIL, rt.params.flags.discardEnd);
EXPECT_EQ(TargetBufferFlags::DEPTH, rt.params.flags.discardStart);
EXPECT_EQ(TargetBufferFlags::NONE, rt.params.flags.discardEnd);
});
struct ColorPassData {
@@ -195,8 +195,8 @@ TEST(FrameGraphTest, ScenarioDepthPrePass) {
colorPassExecuted = true;
auto const& rt = resources.getRenderTarget(data.rt);
EXPECT_TRUE(rt.target);
EXPECT_EQ(TargetBufferFlags::COLOR_AND_STENCIL, rt.params.flags.discardStart);
EXPECT_EQ(TargetBufferFlags::DEPTH_AND_STENCIL, rt.params.flags.discardEnd);
EXPECT_EQ(TargetBufferFlags::COLOR, rt.params.flags.discardStart);
EXPECT_EQ(TargetBufferFlags::DEPTH, rt.params.flags.discardEnd);
});
fg.present(colorPass.getData().outColor);
@@ -235,8 +235,8 @@ TEST(FrameGraphTest, SimplePassCulling) {
renderPassExecuted = true;
auto const& rt = resources.getRenderTarget(data.rt);
EXPECT_TRUE(rt.target);
EXPECT_EQ(TargetBufferFlags::ALL, rt.params.flags.discardStart);
EXPECT_EQ(TargetBufferFlags::DEPTH_AND_STENCIL, rt.params.flags.discardEnd);
EXPECT_EQ(TargetBufferFlags::COLOR, rt.params.flags.discardStart);
EXPECT_EQ(TargetBufferFlags::NONE, rt.params.flags.discardEnd);
});
@@ -259,8 +259,8 @@ TEST(FrameGraphTest, SimplePassCulling) {
postProcessPassExecuted = true;
auto const& rt = resources.getRenderTarget(data.rt);
EXPECT_TRUE(rt.target);
EXPECT_EQ(TargetBufferFlags::ALL, rt.params.flags.discardStart);
EXPECT_EQ(TargetBufferFlags::DEPTH_AND_STENCIL, rt.params.flags.discardEnd);
EXPECT_EQ(TargetBufferFlags::COLOR, rt.params.flags.discardStart);
EXPECT_EQ(TargetBufferFlags::NONE, rt.params.flags.discardEnd);
});
@@ -362,8 +362,8 @@ TEST(FrameGraphTest, RenderTargetLifetime) {
rt1 = rt.target;
EXPECT_TRUE(rt.target);
EXPECT_EQ(TargetBufferFlags::COLOR, rt.params.flags.clear);
EXPECT_EQ(TargetBufferFlags::ALL, rt.params.flags.discardStart);
EXPECT_EQ(TargetBufferFlags::DEPTH_AND_STENCIL, rt.params.flags.discardEnd);
EXPECT_EQ(TargetBufferFlags::COLOR, rt.params.flags.discardStart);
EXPECT_EQ(TargetBufferFlags::NONE, rt.params.flags.discardEnd);
});
auto& renderPass2 = fg.addPass<RenderPassData>("Render2",
@@ -381,8 +381,8 @@ TEST(FrameGraphTest, RenderTargetLifetime) {
EXPECT_TRUE(rt.target);
EXPECT_EQ(TargetBufferFlags::NONE, rt.params.flags.clear);
EXPECT_EQ(rt1.getId(), rt.target.getId());
EXPECT_EQ(TargetBufferFlags::DEPTH_AND_STENCIL, rt.params.flags.discardStart);
EXPECT_EQ(TargetBufferFlags::DEPTH_AND_STENCIL, rt.params.flags.discardEnd);
EXPECT_EQ(TargetBufferFlags::NONE, rt.params.flags.discardStart);
EXPECT_EQ(TargetBufferFlags::NONE, rt.params.flags.discardEnd);
});