Switch to FrameGraph

Filament's main rendering loop now uses the new FrameGraph API.
This commit is contained in:
Mathias Agopian
2019-03-01 16:46:52 -08:00
committed by Mathias Agopian
parent c5fc7a01e8
commit bb72e796bf
5 changed files with 136 additions and 296 deletions
+18 -148
View File
@@ -15,7 +15,6 @@
*/
#include "PostProcessManager.h"
#include "RenderTargetPool.h"
#include "details/Engine.h"
@@ -30,14 +29,13 @@
namespace filament {
using namespace utils;
using namespace math;
using namespace driver;
using namespace details;
using namespace filament::details;
void PostProcessManager::init(FEngine& engine) noexcept {
mEngine = &engine;
mCommands.reserve(8);
mPostProcessUb = UniformBuffer(engine.getPerPostProcessUib());
// create sampler for post-process FBO
@@ -70,10 +68,11 @@ void PostProcessManager::setSource(uint32_t viewportWidth, uint32_t viewportHeig
auto duration = engine.getEngineTime();
float fraction = (duration.count() % 1000000000) / 1000000000.0f;
float2 uvScale = float2{ viewportWidth, viewportHeight } / float2{ textureWidth, textureHeight };
UniformBuffer& ub = mPostProcessUb;
ub.setUniform(offsetof(PostProcessingUib, time), fraction);
ub.setUniform(offsetof(PostProcessingUib, uvScale),
filament::math::float2{ viewportWidth, viewportHeight } / filament::math::float2{ textureWidth, textureHeight });
ub.setUniform(offsetof(PostProcessingUib, uvScale), uvScale);
// The shader may need to know the offset between the top of the texture and the top
// of the rectangle that it actually needs to sample from.
@@ -84,144 +83,8 @@ void PostProcessManager::setSource(uint32_t viewportWidth, uint32_t viewportHeig
driver.updateUniformBuffer(mPostProcessUbh, ub.toBufferDescriptor(driver));
}
void PostProcessManager::blit(driver::TextureFormat format) noexcept {
mCommands.push_back({{}, format});
}
void PostProcessManager::pass(driver::TextureFormat format, Handle<HwProgram> program) noexcept {
mCommands.push_back({program, format});
}
void PostProcessManager::finish(driver::TargetBufferFlags discarded,
Handle<HwRenderTarget> viewRenderTarget,
filament::Viewport const& vp,
RenderTargetPool::Target const* previous,
filament::Viewport const& svp) {
assert(viewRenderTarget);
assert(previous);
FEngine& engine = *mEngine;
DriverApi& driver = engine.getDriverApi();
RenderTargetPool& rtp = engine.getRenderTargetPool();
Handle<HwRenderPrimitive> const& fullScreenRenderPrimitive = engine.getFullScreenRenderPrimitive();
std::vector<Command>& commands = mCommands;
if (UTILS_UNLIKELY(commands.empty())) {
rtp.put(previous);
return;
}
Driver::PipelineState pipeline;
pipeline.rasterState.culling = Driver::RasterState::CullingMode::NONE;
pipeline.rasterState.colorWrite = true;
pipeline.rasterState.depthFunc = Driver::RasterState::DepthFunc::A;
RenderPassParams params = {};
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::DEPTH_AND_STENCIL;
params.flags.dependencies = RenderPassFlags::DEPENDENCY_BY_REGION;
params.viewport.left = 0;
params.viewport.bottom = 0;
params.viewport.width = svp.width;
params.viewport.height = svp.height;
for (size_t i = 0, c = commands.size() - 1; i < c; i++) {
// if the next command is a blit, it we don't need a texture
uint8_t flags = !commands[i + 1].program ? RenderTargetPool::Target::NO_TEXTURE : uint8_t();
// create a render target for this pass
RenderTargetPool::Target const* target = rtp.get(
TargetBufferFlags::COLOR, svp.width, svp.height, 1, commands[i].format, flags);
assert(target);
if (commands[i].program) {
// set the source for this pass (i.e. previous target)
setSource(params.viewport.width, params.viewport.height, previous->texture, previous->w, previous->h);
// draw a full screen triangle
pipeline.program = commands[i].program;
driver.beginRenderPass(target->target, params);
driver.draw(pipeline, fullScreenRenderPrimitive);
driver.endRenderPass();
} else {
driver.blit(TargetBufferFlags::COLOR,
target->target, { 0, 0, svp.width, svp.height },
previous->target, { 0, 0, svp.width, svp.height });
}
// return the previous target to the pool
rtp.put(previous);
previous = target;
}
assert(!commands.empty());
assert(previous);
// The last command is special, it always draw to the viewRenderTarget and uses
// the non scaled viewport.
if (commands.back().program) {
params.flags.discardStart = discarded;
params.flags.discardEnd = TargetBufferFlags::DEPTH_AND_STENCIL;
params.viewport.left = vp.left;
params.viewport.bottom = vp.bottom;
params.viewport.width = vp.width;
params.viewport.height = vp.height;
setSource(params.viewport.width, params.viewport.height, previous->texture, previous->w, previous->h);
pipeline.program = commands.back().program;
driver.beginRenderPass(viewRenderTarget, params);
driver.draw(pipeline, fullScreenRenderPrimitive);
driver.endRenderPass();
} else {
driver.blit(TargetBufferFlags::COLOR,
viewRenderTarget, { vp.left, vp.bottom, vp.width, vp.height },
previous->target, { 0, 0, svp.width, svp.height });
}
rtp.put(previous);
// clear our command buffer
commands.clear();
}
// ------------------------------------------------------------------------------------------------
FrameGraphResource PostProcessManager::msaa(FrameGraph& fg,
FrameGraphResource input, driver::TextureFormat outFormat) noexcept {
struct PostProcessMSAA {
FrameGraphResource input;
FrameGraphResource output;
};
auto& ppMSAA = fg.addPass<PostProcessMSAA>("msaa",
[&](FrameGraph::Builder& builder, PostProcessMSAA& data) {
auto const* inputDesc = fg.getDescriptor(input);
data.input = builder.useRenderTarget(input).textures[0];
FrameGraphResource::Descriptor outputDesc{
.width = inputDesc->width,
.height = inputDesc->height,
.format = outFormat
};
data.output = builder.createTexture("msaa output", outputDesc);
data.output = builder.useRenderTarget(data.output).textures[0];
},
[=](FrameGraphPassResources const& resources,
PostProcessMSAA const& data, DriverApi& driver) {
auto in = resources.getRenderTarget(data.input);
auto out = resources.getRenderTarget(data.output);
driver.blit(TargetBufferFlags::COLOR,
out.target, out.params.viewport, in.target, in.params.viewport);
});
return ppMSAA.getData().output;
}
FrameGraphResource PostProcessManager::toneMapping(FrameGraph& fg,
FrameGraphResource input, driver::TextureFormat outFormat, bool translucent) noexcept {
@@ -257,10 +120,14 @@ FrameGraphResource PostProcessManager::toneMapping(FrameGraph& fg,
pipeline.rasterState.depthFunc = Driver::RasterState::DepthFunc::A;
pipeline.program = toneMappingProgram;
auto const& targetDesc = resources.getDescriptor(data.output);
auto const& textureDesc = resources.getDescriptor(data.input);
auto const& texture = resources.getTexture(data.input);
setSource(targetDesc.width, targetDesc.height, texture, textureDesc.width, textureDesc.height);
// TODO: the first parameters below are the *actual viewport* size
// (as opposed to the size of the source texture). Currently we don't allow
// the texture to be resized, so they match. We'll need something more
// sofisticated in the future.
setSource(textureDesc.width, textureDesc.height,
texture, textureDesc.width, textureDesc.height);
auto const& target = resources.getRenderTarget(data.output);
driver.beginRenderPass(target.target, target.params);
@@ -308,10 +175,14 @@ FrameGraphResource PostProcessManager::fxaa(FrameGraph& fg,
pipeline.rasterState.depthFunc = Driver::RasterState::DepthFunc::A;
pipeline.program = antiAliasingProgram;
auto const& targetDesc = resources.getDescriptor(data.output);
auto const& textureDesc = resources.getDescriptor(data.input);
auto const& texture = resources.getTexture(data.input);
setSource(targetDesc.width, targetDesc.height, texture, textureDesc.width, textureDesc.height);
// TODO: the first parameters below are the *actual viewport* size
// (as opposed to the size of the source texture). Currently we don't allow
// the texture to be resized, so they match. We'll need something more
// sofisticated in the future.
setSource(textureDesc.width, textureDesc.height,
texture, textureDesc.width, textureDesc.height);
auto const& target = resources.getRenderTarget(data.output);
driver.beginRenderPass(target.target, target.params);
@@ -323,8 +194,7 @@ FrameGraphResource PostProcessManager::fxaa(FrameGraph& fg,
}
FrameGraphResource PostProcessManager::dynamicScaling(FrameGraph& fg,
FrameGraphResource input, driver::TextureFormat outFormat,
filament::Viewport const& outViewport) noexcept {
FrameGraphResource input, driver::TextureFormat outFormat) noexcept {
struct PostProcessScaling {
FrameGraphResource input;
+4 -40
View File
@@ -17,21 +17,14 @@
#ifndef TNT_FILAMENT_POSTPROCESS_MANAGER_H
#define TNT_FILAMENT_POSTPROCESS_MANAGER_H
#include "RenderTargetPool.h"
#include "UniformBuffer.h"
#include "driver/DriverApiForward.h"
#include "fg/FrameGraphResource.h"
#include "driver/DriverApiForward.h"
#include "driver/Handle.h"
#include <filament/Viewport.h>
#include <filament/driver/DriverEnums.h>
#include <vector>
namespace filament {
namespace details {
@@ -43,29 +36,9 @@ class PostProcessManager {
public:
void init(details::FEngine& engine) noexcept;
void terminate(driver::DriverApi& driver) noexcept;
void setSource(uint32_t viewportWidth, uint32_t viewportHeight, Handle <HwTexture> texture,
void setSource(uint32_t viewportWidth, uint32_t viewportHeight, Handle<HwTexture> texture,
uint32_t textureWidth, uint32_t textureHeight) const noexcept;
// start() is a scam, it does nothing
void start() noexcept { }
// a fullscreen pass, using the given format as target and writing into the specified program
void pass(driver::TextureFormat format, Handle<HwProgram> program) noexcept;
// a blit pass, using the given format as target
void blit(driver::TextureFormat format = driver::TextureFormat::RGBA8) noexcept;
void finish(driver::TargetBufferFlags discarded,
Handle<HwRenderTarget> viewRenderTarget,
Viewport const& vp,
RenderTargetPool::Target const* linearTarget,
Viewport const& svp);
FrameGraphResource msaa(
FrameGraph& fg, FrameGraphResource input,
driver::TextureFormat outFormat) noexcept;
FrameGraphResource toneMapping(
FrameGraph& fg, FrameGraphResource input, driver::TextureFormat outFormat,
bool translucent) noexcept;
@@ -75,20 +48,11 @@ public:
bool translucent) noexcept;
FrameGraphResource dynamicScaling(
FrameGraph& fg, FrameGraphResource input,
driver::TextureFormat outFormat, Viewport const& outViewport) noexcept;
FrameGraph& fg, FrameGraphResource input, driver::TextureFormat outFormat) noexcept;
private:
details::FEngine* mEngine = nullptr;
struct Command {
Handle<HwProgram> program = {};
driver::TextureFormat format;
};
std::vector<Command> mCommands;
// we need only one of these
mutable UniformBuffer mPostProcessUb;
Handle<HwSamplerBuffer> mPostProcessSbh;
+67 -83
View File
@@ -157,7 +157,6 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
JobSystem& js = engine.getJobSystem();
FEngine::DriverApi& driver = engine.getDriverApi();
PostProcessManager& ppm = engine.getPostProcessManager();
RenderTargetPool& rtp = engine.getRenderTargetPool();
// DEBUG: driver commands must all happen from the same thread. Enforce that on debug builds.
engine.getDriverApi().debugThreading();
@@ -205,103 +204,88 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
commands.clear();
}
/*
* Frame graph
*/
FrameGraph fg;
const TextureFormat hdrFormat = getHdrFormat(view);
const uint8_t useMSAA = view.getSampleCount();
// FIXME: we use "hasPostProcess" as a proxy for deciding if we need a depth-buffer or not
// historically this has been true, but it's definitely wrong.
// This hack is needed because viewRenderTarget(output) doesn't have a depth-buffer,
// so when skipping post-process (which draws directly into it), we can't rely on it.
const bool colorPassNeedsDepthBuffer = hasPostProcess;
const Handle<HwRenderTarget> viewRenderTarget = getRenderTarget();
FrameGraphResource output = fg.importResource("viewRenderTarget",
{ .viewport = vp }, viewRenderTarget, vp.width, vp.height);
/*
* Depth + Color passes
*/
const uint8_t useMSAA = view.getSampleCount();
const TextureFormat hdrFormat = getHdrFormat(view);
const TextureFormat ldrFormat = getLdrFormat();
RenderTargetPool::Target const* colorTarget = nullptr;
struct ColorPassData {
FrameGraphResource color;
FrameGraphResource depth;
};
if (UTILS_LIKELY(hasPostProcess)) {
// allocate the target we need for rendering the scene
colorTarget = rtp.get(TargetBufferFlags::COLOR_AND_DEPTH,
svp.width, svp.height, useMSAA, hdrFormat);
svp.left = svp.bottom = 0;
}
auto& colorPass = fg.addPass<ColorPassData>("Color pass",
[&](FrameGraph::Builder& builder, ColorPassData& data) {
// FIXME: viewRenderTarget doesn't have a depth-buffer, so when skipping post-process, don't rely on it
const Handle<HwRenderTarget> viewRenderTarget = getRenderTarget();
ColorPass::renderColorPass(engine, js, jobFroxelize,
colorTarget ? colorTarget->target : viewRenderTarget, view, svp, commands);
data.color = builder.createTexture("color buffer",
{ .width = svp.width, .height = svp.height, .format = hdrFormat });
if (colorPassNeedsDepthBuffer) {
data.depth = builder.createTexture("depth buffer",
{ .width = svp.width, .height = svp.height });
}
FrameGraphRenderTarget::Descriptor desc{
.samples = useMSAA,
.attachments.color = data.color,
.attachments.depth = data.depth
};
data.color = builder.useRenderTarget("colorRenderTarget", desc).textures[0];
},
[=, &engine, &js, &view, &commands]
(FrameGraphPassResources const& resources,
ColorPassData const& data, DriverApi& driver) {
auto out = resources.getRenderTarget(data.color);
ColorPass::renderColorPass(engine, js, jobFroxelize, out.target, view,
static_cast<filament::Viewport const&>(out.params.viewport),
commands);
});
FrameGraphResource input = colorPass.getData().color;
/*
* Post Processing...
*/
if (hasPostProcess) {
const TextureFormat ldrFormat = getLdrFormat();
const bool translucent = mSwapChain->isTransparent();
#define USE_FRAME_GRAPH false
if (UTILS_LIKELY(hasPostProcess)) {
driver.pushGroupMarker("Post Processing");
assert(colorTarget);
if (USE_FRAME_GRAPH) {
FrameGraph fg;
const bool translucent = mSwapChain->isTransparent();
FrameGraphResource::Descriptor colorDesc{
.width = colorTarget->w,
.height= colorTarget->h,
.format= colorTarget->format,
};
FrameGraphResource input = fg.importResource("colorTarget",
colorDesc, colorTarget->texture);
FrameGraphResource output = fg.importResource("viewRenderTarget", { .viewport = vp },
viewRenderTarget, vp.width, vp.height);
if (useMSAA > 1) {
input = ppm.msaa(fg, input, hdrFormat);
}
input = ppm.toneMapping(fg, input, ldrFormat, translucent);
if (useFXAA) {
input = ppm.fxaa(fg, input, ldrFormat, translucent);
}
if (scaled) {
input = ppm.dynamicScaling(fg, input, ldrFormat, vp);
}
fg.moveResource(output, input);
fg.present(output);
fg.compile();
//fg.export_graphviz(slog.d);
fg.execute(driver);
rtp.put(colorTarget);
} else {
ppm.start();
const bool translucent = mSwapChain->isTransparent();
Handle<HwProgram> toneMappingProgram = engine.getPostProcessProgram(
translucent ? PostProcessStage::TONE_MAPPING_TRANSLUCENT
: PostProcessStage::TONE_MAPPING_OPAQUE);
ppm.pass(useFXAA ? TextureFormat::RGBA8 : ldrFormat, toneMappingProgram);
if (useFXAA) {
Handle<HwProgram> antiAliasingProgram = engine.getPostProcessProgram(
translucent ? PostProcessStage::ANTI_ALIASING_TRANSLUCENT
: PostProcessStage::ANTI_ALIASING_OPAQUE);
ppm.pass(ldrFormat, antiAliasingProgram);
}
if (scaled) {
// because it's the last command, the TextureFormat is not relevant
ppm.blit();
}
ppm.finish(view.getDiscardedTargetBuffers(), viewRenderTarget, vp, colorTarget, svp);
input = ppm.toneMapping(fg, input, ldrFormat, translucent);
if (useFXAA) {
input = ppm.fxaa(fg, input, ldrFormat, translucent);
}
if (scaled) {
input = ppm.dynamicScaling(fg, input, ldrFormat);
}
driver.popGroupMarker();
}
fg.present(input);
fg.moveResource(output, input);
fg.compile();
//fg.export_graphviz(slog.d);
fg.execute(driver);
// for debugging
recordHighWatermark(commands);
}
+40 -25
View File
@@ -43,13 +43,6 @@ struct Alias { //4
FrameGraphResource from, to;
};
struct TargetFlags {
uint8_t clear = 0; // this is provided by the user -- it overides discardStart
uint8_t discardStart = 0; // calculated in compile()
uint8_t discardEnd = 0; // calculated in compile()
uint8_t dependencies = 0; // currently ignored
};
struct VirtualResource {
VirtualResource() noexcept = default;
VirtualResource(VirtualResource const&) = default;
@@ -110,6 +103,10 @@ struct ResourceNode { // 24
PassNode* writer = nullptr; // writer to this node
uint32_t readerCount = 0; // # of passes reading from this resource
// updated by builder
static constexpr uint16_t UNINITIALIZED = std::numeric_limits<uint16_t>::max();
uint16_t renderTargetIndex = UNINITIALIZED; // used to retrieve the RT infos
// constants
const uint8_t version; // version of the resource when the node was created
};
@@ -202,10 +199,10 @@ struct RenderTarget { // 32
// set by builder
FrameGraphRenderTarget::Descriptor desc;
TargetFlags userTargetFlags{};
RenderPassFlags userTargetFlags{};
// set in compile
TargetFlags targetFlags{};
RenderPassFlags targetFlags{};
RenderTargetResource* cache = nullptr;
void resolve(FrameGraph& fg) noexcept {
@@ -258,9 +255,9 @@ struct RenderTarget { // 32
colorFormat = pResource->desc.format;
}
// if the resource doesn't need an actual texture, we are free to
// increase its size.
relaxed = relaxed && (pResource->desc.relaxed || !pResource->needsTexture);
// TODO: if the resource doesn't need an actual texture, we may be able to
// increase its size.
relaxed = relaxed && pResource->desc.relaxed;
}
}
@@ -397,13 +394,6 @@ struct PassNode { // 200
return r;
}
FrameGraphResource useRenderTarget(FrameGraph& fg, FrameGraphResource handle) {
// using a resource as a RT implies reading (i.e. adds a reference to that resource) from it
handle = read(fg, handle, true);
// using a resource as a RT implies writing (i.e. adds a reference to the pass) into it
return write(fg, handle);
}
// constants
const char* const name; // our name
const uint32_t id; // a unique id (only for debugging)
@@ -506,13 +496,18 @@ FrameGraph::Builder::Attachments FrameGraph::Builder::useRenderTarget(const char
for (FrameGraphResource const& attachment : desc.attachments.textures) {
const size_t index = &attachment - desc.attachments.textures.data();
if (attachment.isValid()) {
// using a resource as a RT implies reading (i.e. adds a reference to that resource) from it
rt.textures[index] = mPass.read(fg, attachment, true);
// using a resource as a RT implies writing (i.e. adds a reference to the pass) into it
rt.textures[index] = mPass.write(fg, rt.textures[index]);
ResourceNode& node = fg.getResource(attachment);
ResourceNode& node = fg.getResource(rt.textures[index]);
uint8_t usage = node.resource->usage;
usage |= usages[index];
node.resource->usage = TextureUsage(usage);
rt.textures[index] = mPass.useRenderTarget(fg, attachment);
// renderTargetIndex is used to retrieve the Descriptor
node.renderTargetIndex = renderTarget.index;
}
}
return rt;
@@ -520,13 +515,14 @@ FrameGraph::Builder::Attachments FrameGraph::Builder::useRenderTarget(const char
FrameGraph::Builder::Attachments FrameGraph::Builder::useRenderTarget(
FrameGraphResource texture, TargetBufferFlags clearFlags) noexcept {
uint8_t samples = 1;
if (isAttachment(texture)) {
samples = getRenderTargetDescriptor(texture).samples;
}
ResourceNode& resourceNode = mFrameGraph.getResource(texture);
Resource* pResource = resourceNode.resource;
assert(pResource);
FrameGraphRenderTarget::Descriptor desc {
.attachments.color = texture,
.samples = 1
};
FrameGraphRenderTarget::Descriptor desc { .attachments.color = texture, .samples = samples };
return useRenderTarget(pResource->name, desc, clearFlags);
}
@@ -547,6 +543,21 @@ FrameGraph::Builder& FrameGraph::Builder::sideEffect() noexcept {
return *this;
}
bool FrameGraph::Builder::isAttachment(FrameGraphResource resource) const noexcept {
ResourceNode& node = mFrameGraph.getResource(resource);
return node.renderTargetIndex != ResourceNode::UNINITIALIZED;
}
FrameGraphRenderTarget::Descriptor const&
FrameGraph::Builder::getRenderTargetDescriptor(FrameGraphResource attachment) const {
FrameGraph& fg = mFrameGraph;
ResourceNode& node = fg.getResource(attachment);
ASSERT_POSTCONDITION(node.renderTargetIndex != ResourceNode::UNINITIALIZED,
"Resource \"%s\" isn't a render target attachment", node.resource->name);
assert(node.renderTargetIndex < fg.mRenderTargets.size());
return fg.mRenderTargets[node.renderTargetIndex].desc;
}
// ------------------------------------------------------------------------------------------------
FrameGraphPassResources::FrameGraphPassResources(FrameGraph& fg, fg::PassNode const& pass) noexcept
@@ -726,6 +737,10 @@ bool FrameGraph::equals(FrameGraphRenderTarget::Descriptor const& lhs,
return true;
}
}
if (!rhs.isValid()) {
// it's okay if the cached RT has more attachments than we require
return true;
}
return false;
}) && lhs.samples == rhs.samples;
}
+7
View File
@@ -94,6 +94,13 @@ public:
// Calling write() on an imported resource automatically adds a side-effect.
Builder& sideEffect() noexcept;
// returns whether this resource is an attachment to some rendertarget
bool isAttachment(FrameGraphResource resource) const noexcept;
// returns the descriptor of the render target this attachment belongs to
FrameGraphRenderTarget::Descriptor const& getRenderTargetDescriptor(
FrameGraphResource attachment) const;
private:
// this is private for now because we only have textures, and this is for regular buffers
FrameGraphResource write(FrameGraphResource const& output);