refactor Renderer to make it more flexible

This is in preparation to supporting screen-space effects.
There are two major changes:

- RenderPass is now copiable and intended to be passed by copy to
  the execute stage of frame graph passes

- The color pass is in its own function now


This actually simplify RenderPass api.
This commit is contained in:
Mathias Agopian
2020-01-07 16:56:42 -08:00
committed by Mathias Agopian
parent d9ee2fa10d
commit e27158507e
10 changed files with 159 additions and 109 deletions

View File

@@ -473,7 +473,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::ssao(FrameGraph& fg, RenderP
return ssao;
}
FrameGraphId<FrameGraphTexture> PostProcessManager::depthPass(FrameGraph& fg, RenderPass& pass,
FrameGraphId<FrameGraphTexture> PostProcessManager::depthPass(FrameGraph& fg, RenderPass const& pass,
uint32_t width, uint32_t height,
View::AmbientOcclusionOptions const& options) noexcept {
@@ -483,9 +483,6 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::depthPass(FrameGraph& fg, Re
FrameGraphRenderTargetHandle rt;
};
RenderPass::Command const* first = pass.getCommands().begin();
RenderPass::Command const* last = pass.getCommands().end();
// sanitize a bit the user provided scaling factor
const float scale = std::min(std::abs(options.resolution), 1.0f);
width = std::ceil(width * scale);
@@ -510,10 +507,10 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::depthPass(FrameGraph& fg, Re
data.rt = builder.createRenderTarget("SSAO Depth Target", d,
TargetBufferFlags::DEPTH);
},
[=, &pass](FrameGraphPassResources const& resources,
[pass](FrameGraphPassResources const& resources,
DepthPassData const& data, DriverApi& driver) {
auto out = resources.getRenderTarget(data.rt);
pass.execute(resources.getPassName(), out.target, out.params, first, last);
pass.execute(resources.getPassName(), out.target, out.params);
});
return ssaoDepthPass.getData().depth;

View File

@@ -69,7 +69,7 @@ public:
private:
details::FEngine& mEngine;
FrameGraphId<FrameGraphTexture> depthPass(FrameGraph& fg, details::RenderPass& pass,
FrameGraphId<FrameGraphTexture> depthPass(FrameGraph& fg, details::RenderPass const& pass,
uint32_t width, uint32_t height, View::AmbientOcclusionOptions const& options) noexcept;
FrameGraphId<FrameGraphTexture> mipmapPass(FrameGraph& fg,

View File

@@ -74,6 +74,12 @@ void RenderPass::overrideMaterial(FMaterial const* material, FMaterialInstance c
mMaterialInstanceOverride = mi;
}
RenderPass::Command* RenderPass::newCommandBuffer() noexcept {
GrowingSlice<Command>& commands = mCommands;
commands = GrowingSlice<Command>(commands.end(), commands.capacity() - commands.size());
return commands.begin();
}
RenderPass::Command* RenderPass::appendCommands(CommandTypeFlags const commandTypeFlags) noexcept {
SYSTRACE_CONTEXT();
@@ -149,15 +155,15 @@ RenderPass::Command* RenderPass::appendCustomCommand(Pass pass, CustomCommand cu
return curr + 1;
}
RenderPass::Command* RenderPass::sortCommands(Command* curr) noexcept {
RenderPass::Command* RenderPass::sortCommands() noexcept {
SYSTRACE_NAME("sort and trim commands");
GrowingSlice<Command>& commands = mCommands;
std::sort(curr, commands.end());
std::sort(commands.begin(), commands.end());
// find the last command
Command const* const last = std::partition_point(curr, commands.end(),
Command const* const last = std::partition_point(commands.begin(), commands.end(),
[](Command const& c) {
return c.key != uint64_t(Pass::SENTINEL);
});
@@ -169,10 +175,11 @@ RenderPass::Command* RenderPass::sortCommands(Command* curr) noexcept {
void RenderPass::execute(const char* name,
backend::Handle<backend::HwRenderTarget> renderTarget,
backend::RenderPassParams params,
Command const* first, Command const* last) const noexcept {
backend::RenderPassParams params) const noexcept {
FEngine& engine = mEngine;
Command const* const first = mCommands.begin();
Command const* const last = mCommands.end();
// Take care not to upload data within the render pass (synchronize can commit froxel data)
DriverApi& driver = engine.getDriverApi();

View File

@@ -256,6 +256,8 @@ public:
void setCamera(const CameraInfo& camera) noexcept;
void setRenderFlags(RenderFlags flags) noexcept;
Command* newCommandBuffer() noexcept;
// returns mCommands.end()
Command* appendCommands(CommandTypeFlags commandTypeFlags) noexcept;
@@ -263,14 +265,13 @@ public:
Command* appendCustomCommand(Pass pass, CustomCommand custom, uint32_t order,
std::function<void()> command);
// sorts commands from curr to mCommands.end(), then trims sentinels and returns
// sorts commands, then trims sentinels and returns
// the new mCommands.end()
Command* sortCommands(Command* curr) noexcept;
Command* sortCommands() noexcept;
void execute(const char* name,
backend::Handle<backend::HwRenderTarget> renderTarget,
backend::RenderPassParams params,
Command const* first, Command const* last) const noexcept;
backend::RenderPassParams params) const noexcept;
utils::GrowingSlice<Command>& getCommands() { return mCommands; }
utils::Slice<Command> const& getCommands() const { return mCommands; }
@@ -317,8 +318,7 @@ private:
// a reference to the Engine, mostly to get to things like JobSystem
FEngine& mEngine;
// a reference to the command vector (so we can for e.g. append to it)
utils::GrowingSlice<Command>& mCommands;
utils::GrowingSlice<Command> mCommands;
// the SOA containing the renderables we're interested in
FScene::RenderableSoa const* mRenderableSoa = nullptr;

View File

@@ -219,7 +219,7 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
[&engine, &view](JobSystem&, JobSystem::Job*) { view.froxelize(engine); }));
/*
* Allocate command buffer.
* Allocate command buffer
*/
FScene& scene = *view.getScene();
@@ -229,7 +229,6 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
GrowingSlice<Command> commands(
arena.allocate<Command>(commandsCount, CACHELINE_SIZE), commandsCount);
RenderPass pass(engine, commands);
RenderPass::RenderFlags renderFlags = 0;
if (view.hasShadowing()) renderFlags |= RenderPass::HAS_SHADOWING;
@@ -238,16 +237,16 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
if (view.isFrontFaceWindingInverted()) renderFlags |= RenderPass::HAS_INVERSE_FRONT_FACES;
pass.setRenderFlags(renderFlags);
/*
* Shadow pass
*/
if (view.hasShadowing()) {
view.getShadowMap().render(driver, pass, view);
// TODO: use the framegraph for the shadow passes
RenderPass shadowMapPass = pass;
view.getShadowMap().render(driver, shadowMapPass, view);
driver.flush(); // Kick the GPU since we're done with this render target
engine.flush(); // Wake-up the driver thread
commands.clear();
}
/*
@@ -284,34 +283,62 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
pass.setCamera(cameraInfo);
pass.setGeometry(scene.getRenderableData(), view.getVisibleRenderables(), scene.getRenderableUBO());
view.updatePrimitivesLod(engine, cameraInfo,
scene.getRenderableData(), view.getVisibleRenderables());
view.updatePrimitivesLod(engine, cameraInfo,scene.getRenderableData(), view.getVisibleRenderables());
view.prepareCamera(cameraInfo, svp);
view.commitUniforms(driver);
// --------------------------------------------------------------------------------------------
// SSAO pass
const bool useSSAO = view.getAmbientOcclusion() != View::AmbientOcclusion::NONE;
// SSAO pass -- automatically culled if not used
if (useSSAO) {
auto curr = pass.getCommands().end();
// don't generate commands if we don't have SSAO
// TODO: ideally this should be a FrameGraph pass to participate to automatic culling
pass.newCommandBuffer();
pass.appendCommands(RenderPass::CommandTypeFlags::SSAO);
pass.sortCommands(curr);
pass.sortCommands();
}
FrameGraphId<FrameGraphTexture> ssao = ppm.ssao(fg, pass, svp, cameraInfo, view.getAmbientOcclusionOptions());
// SSAO pass -- automatically culled if not used
FrameGraphId<FrameGraphTexture> ssao = ppm.ssao(fg, pass, svp, cameraInfo,
view.getAmbientOcclusionOptions());
// --------------------------------------------------------------------------------------------
// Color passes
// generate the normal commands
// TODO: ideally this should be a FrameGraph pass to participate to automatic culling
RenderPass::CommandTypeFlags commandType = getCommandType(view.getDepthPrepass());
Command* colorPassBegin = pass.getCommands().end();
pass.newCommandBuffer();
pass.appendCommands(commandType);
Command const* colorPassEnd = pass.sortCommands(colorPassBegin);
pass.sortCommands();
// We only honor the view's color buffer clear flags, depth/stencil are handled by the framefraph
// We use a framegraph pass to commit the View's uniforms and wait for froxelization to finish
auto& prepareColorPasses = fg.addPass<PrepareColorPassesData>("Prepare Color Passes",
[useSSAO, ssao, msaa, hdrFormat, &svp]
(FrameGraph::Builder& builder, PrepareColorPassesData& data) {
if (useSSAO) {
data.ssao = builder.sample(ssao);
}
data.svp = svp;
data.hdrFormat = hdrFormat;
data.msaa = msaa;
builder.sideEffect();
},
[&ppm, &js, &view, jobFroxelize]
(FrameGraphPassResources const& resources,
PrepareColorPassesData const& data, DriverApi& driver) {
view.prepareSSAO(data.ssao.isValid() ? resources.getTexture(data.ssao)
: ppm.getNoSSAOTexture());
view.commitUniforms(driver);
if (jobFroxelize) {
auto sync = jobFroxelize;
js.waitAndRelease(sync);
view.commitFroxels(driver);
}
});
// We only honor the view's color buffer clear flags, depth/stencil are handled by the FrameGraph
uint8_t viewClearFlags = view.getClearFlags() & (uint8_t)TargetBufferFlags::ALL;
// FIXME: when the view doesn't ask for a clear, but it's drawn in an intermediate buffer
@@ -319,71 +346,18 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
TargetBufferFlags clearFlags = (TargetBufferFlags(viewClearFlags) & TargetBufferFlags::COLOR)
| TargetBufferFlags::DEPTH;
struct ColorPassData {
FrameGraphId<FrameGraphTexture> color;
FrameGraphId<FrameGraphTexture> depth;
FrameGraphId<FrameGraphTexture> ssao;
FrameGraphRenderTargetHandle rt{};
};
FrameGraphId<FrameGraphTexture> colorPassOutput = colorPass(fg, prepareColorPasses.getData(),
pass, clearFlags, view.getClearColor());
auto& colorPass = fg.addPass<ColorPassData>("Color Pass",
[&svp, hdrFormat, msaa, clearFlags, useSSAO, ssao]
(FrameGraph::Builder& builder, ColorPassData& data) {
FrameGraphId<FrameGraphTexture> input = colorPassOutput;
if (useSSAO) {
data.ssao = builder.sample(ssao);
}
fg.simpleSideEffectPass("Finish Color Passes", [&view]() {
// Unbind SSAO sampler, b/c the FrameGraph will delete the texture at the end of the pass.
view.cleanupSSAO();
});
data.color = builder.createTexture("Color Buffer",
{ .width = svp.width, .height = svp.height, .format = hdrFormat });
data.depth = builder.createTexture("Depth Buffer", {
.width = svp.width, .height = svp.height,
.format = TextureFormat::DEPTH24
});
data.depth = builder.write(builder.read(data.depth));
data.color = builder.write(builder.read(data.color));
data.rt = builder.createRenderTarget("Color Pass Target", {
.attachments = { data.color, data.depth },
.samples = msaa,
}, clearFlags);
},
[&pass, &ppm, colorPassBegin, colorPassEnd, jobFroxelize, &js, &view]
(FrameGraphPassResources const& resources,
ColorPassData const& data, DriverApi& driver) {
auto out = resources.getRenderTarget(data.rt);
Handle<HwTexture> ssao;
if (data.ssao.isValid()) {
ssao = resources.getTexture(data.ssao);
} else {
ssao = ppm.getNoSSAOTexture();
}
view.prepareSSAO(ssao);
view.commitUniforms(driver);
out.params.clearColor = view.getClearColor();
if (jobFroxelize) {
auto sync = jobFroxelize;
js.waitAndRelease(sync);
view.commitFroxels(driver);
}
pass.execute(resources.getPassName(), out.target, out.params,
colorPassBegin, colorPassEnd);
// Unbind the SSAO sampler, as the frame graph will delete the texture at the end of
// the pass.
view.cleanupSSAO();
});
jobFroxelize = nullptr;
FrameGraphId<FrameGraphTexture> input = colorPass.getData().color;
/*
* Post Processing...
*/
// --------------------------------------------------------------------------------------------
// Post Processing...
const TextureFormat ldrFormat = (toneMapping && fxaa) ?
TextureFormat::RGBA8 : getLdrFormat(translucent); // e.g. RGB8 or RGBA8
@@ -403,7 +377,7 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
// We need to do special processing when rendering directly into the swap-chain (see
// comments below). That is when the viewRenderTarget is the default render target
// (mRenderTarget) and we're rendering into it.
if (input == colorPass.getData().color) {
if (input == colorPassOutput) {
// here we know we're not scaled because either post-processing is disabled (which implies
// no scaling, or scaled==false because otherwise we wouldn't be rendering in the
// default target.
@@ -439,6 +413,58 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
recordHighWatermark(pass.getCommandsHighWatermark());
}
FrameGraphId<FrameGraphTexture> FRenderer::colorPass(FrameGraph& fg,
PrepareColorPassesData const& blackboard,
RenderPass const& pass, TargetBufferFlags clearFlags, float4 clearColor) noexcept {
struct ColorPassData {
FrameGraphId<FrameGraphTexture> color;
FrameGraphId<FrameGraphTexture> depth;
FrameGraphId<FrameGraphTexture> ssao;
FrameGraphRenderTargetHandle rt{};
};
auto& colorPass = fg.addPass<ColorPassData>("Color Pass",
[&blackboard, clearFlags]
(FrameGraph::Builder& builder, ColorPassData& data) {
auto& svp = blackboard.svp;
auto hdrFormat = blackboard.hdrFormat;
auto msaa = blackboard.msaa;
if (blackboard.ssao.isValid()) {
data.ssao = builder.sample(blackboard.ssao);
}
data.color = builder.createTexture("Color Buffer",
{ .width = svp.width, .height = svp.height, .format = hdrFormat });
data.depth = builder.createTexture("Depth Buffer", {
.width = svp.width, .height = svp.height,
.format = TextureFormat::DEPTH24
});
data.color = builder.write(builder.read(data.color));
data.depth = builder.write(builder.read(data.depth));
data.rt = builder.createRenderTarget("Color Pass Target", {
.attachments = { data.color, data.depth },
.samples = msaa,
}, clearFlags);
},
[pass, clearColor]
(FrameGraphPassResources const& resources,
ColorPassData const& data, DriverApi& driver) {
auto out = resources.getRenderTarget(data.rt);
out.params.clearColor = clearColor;
pass.execute(resources.getPassName(), out.target, out.params);
});
return colorPass.getData().color;
}
void FRenderer::copyFrame(FSwapChain* dstSwapChain, filament::Viewport const& dstViewport,
filament::Viewport const& srcViewport, CopyFrameFlag flags) {
SYSTRACE_CALL();
@@ -645,7 +671,7 @@ Handle<HwRenderTarget> FRenderer::getRenderTarget(FView& view) const noexcept {
return viewRenderTarget ? viewRenderTarget : mRenderTarget;
}
RenderPass::CommandTypeFlags FRenderer::getCommandType(View::DepthPrepass prepass) const noexcept {
RenderPass::CommandTypeFlags FRenderer::getCommandType(View::DepthPrepass prepass) noexcept {
RenderPass::CommandTypeFlags commandType;
switch (prepass) {
case View::DepthPrepass::DEFAULT:

View File

@@ -186,14 +186,9 @@ void ShadowMap::render(DriverApi& driver, RenderPass& pass, FView& view) noexcep
view.commitUniforms(driver);
pass.overridePolygonOffset(&mPolygonOffset);
auto curr = pass.getCommands().end();
pass.appendCommands(RenderPass::SHADOW);
pass.sortCommands(curr);
pass.execute("Shadow map Pass", getRenderTarget(), params,
pass.getCommands().begin(), pass.getCommands().end());
pass.overridePolygonOffset(nullptr);
pass.sortCommands();
pass.execute("Shadow map Pass", getRenderTarget(), params);
}
void ShadowMap::terminate(DriverApi& driverApi) noexcept {

View File

@@ -100,7 +100,18 @@ private:
uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
backend::PixelBufferDescriptor&& buffer);
RenderPass::CommandTypeFlags getCommandType(View::DepthPrepass prepass) const noexcept;
static RenderPass::CommandTypeFlags getCommandType(View::DepthPrepass prepass) noexcept;
struct PrepareColorPassesData {
FrameGraphId<FrameGraphTexture> ssao;
Viewport svp;
backend::TextureFormat hdrFormat;
uint8_t msaa;
};
static FrameGraphId<FrameGraphTexture> colorPass(FrameGraph& fg,
PrepareColorPassesData const& blackboard, RenderPass const& pass,
backend::TargetBufferFlags clearFlags, math::float4 clearColor = {}) noexcept;
void recordHighWatermark(size_t watermark) noexcept {
mCommandsHighWatermark = std::max(mCommandsHighWatermark, watermark);

View File

@@ -216,7 +216,7 @@ FrameGraphHandle FrameGraph::moveResource(FrameGraphHandle from, FrameGraphHandl
}
void FrameGraph::present(FrameGraphHandle input) {
addPass<std::tuple<>>("Present",
addPass<Empty>("Present",
[&](Builder& builder, auto& data) {
builder.read(input);
builder.sideEffect();

View File

@@ -147,6 +147,8 @@ public:
FrameGraph& operator = (FrameGraph const&) = delete;
~FrameGraph();
struct Empty{};
/*
* Add a pass to the framegraph.
* The Setup lambda is called synchronously and used to declare which and how resources are
@@ -175,6 +177,16 @@ public:
// Adds a reference to 'input', preventing it from being culled.
void present(FrameGraphHandle input);
// Adds a simple execute-only pass with side effect (so it's not culled)
template<typename Execute>
void simpleSideEffectPass(const char* name, Execute&& execute) {
addPass<Empty>(name, [](FrameGraph::Builder& builder, auto& data) { builder.sideEffect(); },
[execute](FrameGraphPassResources const& resources, auto const& data,
backend::DriverApi& driver) {
execute();
});
}
// Returns whether the resource handle is valid. A resource handle becomes invalid after
// it's used to declare a resource write (see Builder::write()).
bool isValid(FrameGraphHandle r) const noexcept;

View File

@@ -163,6 +163,8 @@ public:
using size_type = typename Slice<T, SIZE_TYPE>::size_type;
GrowingSlice() noexcept = default;
GrowingSlice(GrowingSlice const& rhs) noexcept = default;
GrowingSlice(GrowingSlice&& rhs) noexcept = default;
template<typename Iter>
GrowingSlice(Iter begin, size_type count) noexcept
@@ -180,7 +182,7 @@ public:
template<typename Iter>
void set(Iter begin, size_type count) UTILS_RESTRICT noexcept {
this->Slice<T, SIZE_TYPE>::set(begin, count);
mCapOffset = count;
mCapOffset = count;
}
template<typename Iter>