From e27158507e87fca09a191c240bc1baecf89e7ca9 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Tue, 7 Jan 2020 16:56:42 -0800 Subject: [PATCH] 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. --- filament/src/PostProcessManager.cpp | 9 +- filament/src/PostProcessManager.h | 2 +- filament/src/RenderPass.cpp | 17 ++- filament/src/RenderPass.h | 12 +- filament/src/Renderer.cpp | 188 ++++++++++++++++------------ filament/src/ShadowMap.cpp | 9 +- filament/src/details/Renderer.h | 13 +- filament/src/fg/FrameGraph.cpp | 2 +- filament/src/fg/FrameGraph.h | 12 ++ libs/utils/include/utils/Slice.h | 4 +- 10 files changed, 159 insertions(+), 109 deletions(-) diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index 5d2b81dcbc..06dad7a4a1 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -473,7 +473,7 @@ FrameGraphId PostProcessManager::ssao(FrameGraph& fg, RenderP return ssao; } -FrameGraphId PostProcessManager::depthPass(FrameGraph& fg, RenderPass& pass, +FrameGraphId PostProcessManager::depthPass(FrameGraph& fg, RenderPass const& pass, uint32_t width, uint32_t height, View::AmbientOcclusionOptions const& options) noexcept { @@ -483,9 +483,6 @@ FrameGraphId 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 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; diff --git a/filament/src/PostProcessManager.h b/filament/src/PostProcessManager.h index adc9a85321..3f850f3450 100644 --- a/filament/src/PostProcessManager.h +++ b/filament/src/PostProcessManager.h @@ -69,7 +69,7 @@ public: private: details::FEngine& mEngine; - FrameGraphId depthPass(FrameGraph& fg, details::RenderPass& pass, + FrameGraphId depthPass(FrameGraph& fg, details::RenderPass const& pass, uint32_t width, uint32_t height, View::AmbientOcclusionOptions const& options) noexcept; FrameGraphId mipmapPass(FrameGraph& fg, diff --git a/filament/src/RenderPass.cpp b/filament/src/RenderPass.cpp index 151369dff1..164e640ddf 100644 --- a/filament/src/RenderPass.cpp +++ b/filament/src/RenderPass.cpp @@ -74,6 +74,12 @@ void RenderPass::overrideMaterial(FMaterial const* material, FMaterialInstance c mMaterialInstanceOverride = mi; } +RenderPass::Command* RenderPass::newCommandBuffer() noexcept { + GrowingSlice& commands = mCommands; + commands = GrowingSlice(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& 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 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(); diff --git a/filament/src/RenderPass.h b/filament/src/RenderPass.h index 8773d710e8..3f56c9d338 100644 --- a/filament/src/RenderPass.h +++ b/filament/src/RenderPass.h @@ -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 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 renderTarget, - backend::RenderPassParams params, - Command const* first, Command const* last) const noexcept; + backend::RenderPassParams params) const noexcept; utils::GrowingSlice& getCommands() { return mCommands; } utils::Slice 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& mCommands; + utils::GrowingSlice mCommands; // the SOA containing the renderables we're interested in FScene::RenderableSoa const* mRenderableSoa = nullptr; diff --git a/filament/src/Renderer.cpp b/filament/src/Renderer.cpp index cec1608492..16f32edce6 100644 --- a/filament/src/Renderer.cpp +++ b/filament/src/Renderer.cpp @@ -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 commands( arena.allocate(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 ssao = ppm.ssao(fg, pass, svp, cameraInfo, view.getAmbientOcclusionOptions()); + // SSAO pass -- automatically culled if not used + FrameGraphId 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("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 color; - FrameGraphId depth; - FrameGraphId ssao; - FrameGraphRenderTargetHandle rt{}; - }; + FrameGraphId colorPassOutput = colorPass(fg, prepareColorPasses.getData(), + pass, clearFlags, view.getClearColor()); - auto& colorPass = fg.addPass("Color Pass", - [&svp, hdrFormat, msaa, clearFlags, useSSAO, ssao] - (FrameGraph::Builder& builder, ColorPassData& data) { + FrameGraphId 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 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 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 FRenderer::colorPass(FrameGraph& fg, + PrepareColorPassesData const& blackboard, + RenderPass const& pass, TargetBufferFlags clearFlags, float4 clearColor) noexcept { + + struct ColorPassData { + FrameGraphId color; + FrameGraphId depth; + FrameGraphId ssao; + FrameGraphRenderTargetHandle rt{}; + }; + + auto& colorPass = fg.addPass("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 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: diff --git a/filament/src/ShadowMap.cpp b/filament/src/ShadowMap.cpp index 033337ba40..1190193ba4 100644 --- a/filament/src/ShadowMap.cpp +++ b/filament/src/ShadowMap.cpp @@ -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 { diff --git a/filament/src/details/Renderer.h b/filament/src/details/Renderer.h index 57f3063865..11b43239e0 100644 --- a/filament/src/details/Renderer.h +++ b/filament/src/details/Renderer.h @@ -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 ssao; + Viewport svp; + backend::TextureFormat hdrFormat; + uint8_t msaa; + }; + + static FrameGraphId 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); diff --git a/filament/src/fg/FrameGraph.cpp b/filament/src/fg/FrameGraph.cpp index 9ac4f8cde7..9665bc858f 100644 --- a/filament/src/fg/FrameGraph.cpp +++ b/filament/src/fg/FrameGraph.cpp @@ -216,7 +216,7 @@ FrameGraphHandle FrameGraph::moveResource(FrameGraphHandle from, FrameGraphHandl } void FrameGraph::present(FrameGraphHandle input) { - addPass>("Present", + addPass("Present", [&](Builder& builder, auto& data) { builder.read(input); builder.sideEffect(); diff --git a/filament/src/fg/FrameGraph.h b/filament/src/fg/FrameGraph.h index 40538c96d6..c77ae51e47 100644 --- a/filament/src/fg/FrameGraph.h +++ b/filament/src/fg/FrameGraph.h @@ -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 + void simpleSideEffectPass(const char* name, Execute&& execute) { + addPass(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; diff --git a/libs/utils/include/utils/Slice.h b/libs/utils/include/utils/Slice.h index 5d5d2d9155..1cad69c9e1 100644 --- a/libs/utils/include/utils/Slice.h +++ b/libs/utils/include/utils/Slice.h @@ -163,6 +163,8 @@ public: using size_type = typename Slice::size_type; GrowingSlice() noexcept = default; + GrowingSlice(GrowingSlice const& rhs) noexcept = default; + GrowingSlice(GrowingSlice&& rhs) noexcept = default; template GrowingSlice(Iter begin, size_type count) noexcept @@ -180,7 +182,7 @@ public: template void set(Iter begin, size_type count) UTILS_RESTRICT noexcept { this->Slice::set(begin, count); - mCapOffset = count; + mCapOffset = count; } template