From 04329c5d00d7c36509c0f2bfaa8b7393fc9a08d7 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Tue, 31 Oct 2023 17:23:37 -0700 Subject: [PATCH] [Impeller] stencil buffer record/replay instead of MSAA storage. (#47397) When restoring from a backdrop filter, replay the clip affecting cmds into a new stencil buffer instead of storing 4x MSAA stencil buffer. Fixes https://github.com/flutter/flutter/issues/137561 Fixes https://github.com/flutter/flutter/issues/137448 Fixes https://github.com/flutter/flutter/issues/137302 Helps https://github.com/flutter/flutter/issues/137108 --- impeller/entity/entity_pass.cc | 85 ++++++++++++++++---------- impeller/entity/entity_pass.h | 23 +++++++ impeller/entity/inline_pass_context.cc | 13 +--- impeller/entity/inline_pass_context.h | 2 +- 4 files changed, 80 insertions(+), 43 deletions(-) diff --git a/impeller/entity/entity_pass.cc b/impeller/entity/entity_pass.cc index 1948a72a69d3d..5e09d2b1ad91f 100644 --- a/impeller/entity/entity_pass.cc +++ b/impeller/entity/entity_pass.cc @@ -253,18 +253,15 @@ void EntityPass::AddSubpassInline(std::unique_ptr pass) { pass->advanced_blend_reads_from_pass_texture_; } -static RenderTarget::AttachmentConfig GetDefaultStencilConfig(bool readable) { - return RenderTarget::AttachmentConfig{ - .storage_mode = readable ? StorageMode::kDevicePrivate - : StorageMode::kDeviceTransient, - .load_action = LoadAction::kDontCare, - .store_action = StoreAction::kDontCare, - }; -} +static const constexpr RenderTarget::AttachmentConfig kDefaultStencilConfig = + RenderTarget::AttachmentConfig{ + .storage_mode = StorageMode::kDeviceTransient, + .load_action = LoadAction::kDontCare, + .store_action = StoreAction::kDontCare, + }; static EntityPassTarget CreateRenderTarget(ContentContext& renderer, ISize size, - bool readable, const Color& clear_color) { auto context = renderer.GetContext(); @@ -285,8 +282,8 @@ static EntityPassTarget CreateRenderTarget(ContentContext& renderer, .resolve_storage_mode = StorageMode::kDevicePrivate, .load_action = LoadAction::kDontCare, .store_action = StoreAction::kMultisampleResolve, - .clear_color = clear_color}, // color_attachment_config - GetDefaultStencilConfig(readable) // stencil_attachment_config + .clear_color = clear_color}, // color_attachment_config + kDefaultStencilConfig // stencil_attachment_config ); } else { target = RenderTarget::CreateOffscreen( @@ -299,8 +296,8 @@ static EntityPassTarget CreateRenderTarget(ContentContext& renderer, .load_action = LoadAction::kDontCare, .store_action = StoreAction::kDontCare, .clear_color = clear_color, - }, // color_attachment_config - GetDefaultStencilConfig(readable) // stencil_attachment_config + }, // color_attachment_config + kDefaultStencilConfig // stencil_attachment_config ); } @@ -361,9 +358,9 @@ bool EntityPass::Render(ContentContext& renderer, // and then blit the results onto the onscreen texture. If using this branch, // there's no need to set up a stencil attachment on the root render target. if (!supports_onscreen_backdrop_reads && reads_from_onscreen_backdrop) { - auto offscreen_target = CreateRenderTarget( - renderer, root_render_target.GetRenderTargetSize(), true, - GetClearColor(render_target.GetRenderTargetSize())); + auto offscreen_target = + CreateRenderTarget(renderer, root_render_target.GetRenderTargetSize(), + GetClearColor(render_target.GetRenderTargetSize())); if (!OnRender(renderer, // renderer capture, // capture @@ -465,8 +462,7 @@ bool EntityPass::Render(ContentContext& renderer, *renderer.GetContext(), *renderer.GetRenderTargetCache(), color0.texture->GetSize(), renderer.GetContext()->GetCapabilities()->SupportsOffscreenMSAA(), - "ImpellerOnscreen", - GetDefaultStencilConfig(reads_from_onscreen_backdrop)); + "ImpellerOnscreen", kDefaultStencilConfig); } // Set up the clear color of the root pass. @@ -503,10 +499,10 @@ EntityPass::EntityResult EntityPass::GetEntityForElement( //-------------------------------------------------------------------------- /// Setup entity element. /// - if (const auto& entity = std::get_if(&element)) { element_entity = *entity; element_entity.SetCapture(capture.CreateChild("Entity")); + if (!global_pass_position.IsZero()) { // If the pass image is going to be rendered with a non-zero position, // apply the negative translation to entity copies before rendering them @@ -515,16 +511,15 @@ EntityPass::EntityResult EntityPass::GetEntityForElement( Matrix::MakeTranslation(Vector3(-global_pass_position)) * element_entity.GetTransformation()); } + return EntityPass::EntityResult::Success(element_entity); } //-------------------------------------------------------------------------- /// Setup subpass element. /// - - else if (const auto& subpass_ptr = - std::get_if>(&element)) { + if (const auto& subpass_ptr = + std::get_if>(&element)) { auto subpass = subpass_ptr->get(); - if (subpass->delegate_->CanElide()) { return EntityPass::EntityResult::Skip(); } @@ -625,10 +620,9 @@ EntityPass::EntityResult EntityPass::GetEntityForElement( } auto subpass_target = CreateRenderTarget( - renderer, // renderer - subpass_size, // size - subpass->GetTotalPassReads(renderer) > 0, // readable - subpass->GetClearColor(subpass_size)); // clear_color + renderer, // renderer + subpass_size, // size + subpass->GetClearColor(subpass_size)); // clear_color if (!subpass_target.IsValid()) { VALIDATION_LOG << "Subpass render target is invalid."; @@ -696,11 +690,10 @@ EntityPass::EntityResult EntityPass::GetEntityForElement( element_entity.SetTransformation(subpass_texture_capture.AddMatrix( "Transform", Matrix::MakeTranslation(Vector3(subpass_coverage->origin - global_pass_position)))); - } else { - FML_UNREACHABLE(); - } - return EntityPass::EntityResult::Success(element_entity); + return EntityPass::EntityResult::Success(element_entity); + } + FML_UNREACHABLE(); } bool EntityPass::RenderElement(Entity& element_entity, @@ -724,6 +717,15 @@ bool EntityPass::RenderElement(Entity& element_entity, // blit the non-MSAA resolve texture of the previous pass to MSAA textures // (let alone a transient one). if (result.backdrop_texture) { + // Restore any clips that were recorded before the backdrop filter was + // applied. + auto& replay_entities = clip_replay_->GetReplayEntities(); + for (const auto& entity : replay_entities) { + if (!entity.Render(renderer, *result.pass)) { + VALIDATION_LOG << "Failed to render entity for clip restore."; + } + } + auto size_rect = Rect::MakeSize(result.pass->GetRenderTargetSize()); auto msaa_backdrop_contents = TextureContents::MakeRect(size_rect); msaa_backdrop_contents->SetStencilEnabled(false); @@ -836,6 +838,7 @@ bool EntityPass::RenderElement(Entity& element_entity, #endif element_entity.SetClipDepth(element_entity.GetClipDepth() - clip_depth_floor); + clip_replay_->RecordEntity(element_entity, clip_coverage.type); if (!element_entity.Render(renderer, *result.pass)) { VALIDATION_LOG << "Failed to render entity."; return false; @@ -1180,4 +1183,24 @@ void EntityPass::SetEnableOffscreenCheckerboard(bool enabled) { enable_offscreen_debug_checkerboard_ = enabled; } +EntityPassClipRecorder::EntityPassClipRecorder() {} + +void EntityPassClipRecorder::RecordEntity(const Entity& entity, + Contents::ClipCoverage::Type type) { + switch (type) { + case Contents::ClipCoverage::Type::kNoChange: + return; + case Contents::ClipCoverage::Type::kAppend: + rendered_clip_entities_.push_back(entity); + break; + case Contents::ClipCoverage::Type::kRestore: + rendered_clip_entities_.pop_back(); + break; + } +} + +const std::vector& EntityPassClipRecorder::GetReplayEntities() const { + return rendered_clip_entities_; +} + } // namespace impeller diff --git a/impeller/entity/entity_pass.h b/impeller/entity/entity_pass.h index 9293d41949771..90d2eb51adece 100644 --- a/impeller/entity/entity_pass.h +++ b/impeller/entity/entity_pass.h @@ -19,6 +19,7 @@ namespace impeller { class ContentContext; +class EntityPassClipRecorder; class EntityPass { public: @@ -285,6 +286,8 @@ class EntityPass { bool flood_clip_ = false; bool enable_offscreen_debug_checkerboard_ = false; std::optional bounds_limit_; + std::unique_ptr clip_replay_ = + std::make_unique(); /// These values are incremented whenever something is added to the pass that /// requires reading from the backdrop texture. Currently, this can happen in @@ -309,4 +312,24 @@ class EntityPass { EntityPass& operator=(const EntityPass&) = delete; }; +/// @brief A class that tracks all clips that have been recorded in the current +/// entity pass stencil. +/// +/// These clips are replayed when restoring the backdrop so that the +/// stencil buffer is left in an identical state. +class EntityPassClipRecorder { + public: + EntityPassClipRecorder(); + + ~EntityPassClipRecorder() = default; + + /// @brief Record the entity based on the provided coverage [type]. + void RecordEntity(const Entity& entity, Contents::ClipCoverage::Type type); + + const std::vector& GetReplayEntities() const; + + private: + std::vector rendered_clip_entities_; +}; + } // namespace impeller diff --git a/impeller/entity/inline_pass_context.cc b/impeller/entity/inline_pass_context.cc index 4253e75987729..c1f9f59649c78 100644 --- a/impeller/entity/inline_pass_context.cc +++ b/impeller/entity/inline_pass_context.cc @@ -21,7 +21,6 @@ InlinePassContext::InlinePassContext( std::optional collapsed_parent_pass) : context_(std::move(context)), pass_target_(pass_target), - total_pass_reads_(pass_texture_reads), is_collapsed_(collapsed_parent_pass.has_value()) { if (collapsed_parent_pass.has_value()) { pass_ = collapsed_parent_pass.value().pass; @@ -140,17 +139,9 @@ InlinePassContext::RenderPassResult InlinePassContext::GetRenderPass( return {}; } - // Only clear the stencil if this is the very first pass of the - // layer. - stencil->load_action = - pass_count_ > 0 ? LoadAction::kLoad : LoadAction::kClear; - // If we're on the last pass of the layer, there's no need to store the - // stencil because nothing needs to read it. - stencil->store_action = pass_count_ == total_pass_reads_ - ? StoreAction::kDontCare - : StoreAction::kStore; + stencil->load_action = LoadAction::kClear; + stencil->store_action = StoreAction::kDontCare; pass_target_.target_.SetStencilAttachment(stencil.value()); - pass_target_.target_.SetColorAttachment(color0, 0); pass_ = command_buffer_->CreateRenderPass(pass_target_.GetRenderTarget()); diff --git a/impeller/entity/inline_pass_context.h b/impeller/entity/inline_pass_context.h index cf828f35a2dea..0c89bd5630e75 100644 --- a/impeller/entity/inline_pass_context.h +++ b/impeller/entity/inline_pass_context.h @@ -40,7 +40,7 @@ class InlinePassContext { std::shared_ptr command_buffer_; std::shared_ptr pass_; uint32_t pass_count_ = 0; - uint32_t total_pass_reads_ = 0; + // Whether this context is collapsed into a parent entity pass. bool is_collapsed_ = false;