diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 89e3d73495d0b..cf11fe92f84a0 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -568,6 +568,8 @@ FILE: ../../../flutter/impeller/entity/entity_pass_delegate.h FILE: ../../../flutter/impeller/entity/entity_playground.cc FILE: ../../../flutter/impeller/entity/entity_playground.h FILE: ../../../flutter/impeller/entity/entity_unittests.cc +FILE: ../../../flutter/impeller/entity/inline_pass_context.cc +FILE: ../../../flutter/impeller/entity/inline_pass_context.h FILE: ../../../flutter/impeller/entity/shaders/blending/advanced_blend.glsl FILE: ../../../flutter/impeller/entity/shaders/blending/advanced_blend.vert FILE: ../../../flutter/impeller/entity/shaders/blending/advanced_blend_color.frag diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc index b5001edaa1056..c7b317c08127b 100644 --- a/impeller/aiks/canvas.cc +++ b/impeller/aiks/canvas.cc @@ -265,14 +265,17 @@ size_t Canvas::GetStencilDepth() const { return xformation_stack_.back().stencil_depth; } -void Canvas::SaveLayer(Paint paint, std::optional bounds) { +void Canvas::SaveLayer(Paint paint, + std::optional bounds, + std::optional backdrop_filter) { Save(true, paint.blend_mode); auto& new_layer_pass = GetCurrentPass(); new_layer_pass.SetDelegate( std::make_unique(paint, bounds)); + new_layer_pass.SetBackdropFilter(backdrop_filter); - if (bounds.has_value()) { + if (bounds.has_value() && !backdrop_filter.has_value()) { // Render target switches due to a save layer can be elided. In such cases // where passes are collapsed into their parent, the clipping effect to // the size of the render target that would have been allocated will be diff --git a/impeller/aiks/canvas.h b/impeller/aiks/canvas.h index 974085fb4ef59..a58794a9ac6b3 100644 --- a/impeller/aiks/canvas.h +++ b/impeller/aiks/canvas.h @@ -35,7 +35,10 @@ class Canvas { void Save(); - void SaveLayer(Paint paint, std::optional bounds = std::nullopt); + void SaveLayer( + Paint paint, + std::optional bounds = std::nullopt, + std::optional backdrop_filter = std::nullopt); bool Restore(); diff --git a/impeller/display_list/display_list_dispatcher.cc b/impeller/display_list/display_list_dispatcher.cc index 40f7b4986919f..4acc0adaf07b6 100644 --- a/impeller/display_list/display_list_dispatcher.cc +++ b/impeller/display_list/display_list_dispatcher.cc @@ -324,9 +324,12 @@ void DisplayListDispatcher::setMaskFilter(const flutter::DlMaskFilter* filter) { } } -// |flutter::Dispatcher| -void DisplayListDispatcher::setImageFilter( +static std::optional ToImageFilterProc( const flutter::DlImageFilter* filter) { + if (filter == nullptr) { + return std::nullopt; + } + switch (filter->type()) { case flutter::DlImageFilterType::kBlur: { auto blur = filter->asBlur(); @@ -338,7 +341,7 @@ void DisplayListDispatcher::setImageFilter( UNIMPLEMENTED; } - paint_.image_filter = [sigma_x, sigma_y](FilterInput::Ref input) { + return [sigma_x, sigma_y](FilterInput::Ref input) { return FilterContents::MakeGaussianBlur(input, sigma_x, sigma_y); }; @@ -350,11 +353,16 @@ void DisplayListDispatcher::setImageFilter( case flutter::DlImageFilterType::kComposeFilter: case flutter::DlImageFilterType::kColorFilter: case flutter::DlImageFilterType::kUnknown: - UNIMPLEMENTED; - break; + return std::nullopt; } } +// |flutter::Dispatcher| +void DisplayListDispatcher::setImageFilter( + const flutter::DlImageFilter* filter) { + paint_.image_filter = ToImageFilterProc(filter); +} + // |flutter::Dispatcher| void DisplayListDispatcher::save() { canvas_.Save(); @@ -371,11 +379,8 @@ static std::optional ToRect(const SkRect* rect) { void DisplayListDispatcher::saveLayer(const SkRect* bounds, const flutter::SaveLayerOptions options, const flutter::DlImageFilter* backdrop) { - if (backdrop) { - UNIMPLEMENTED; - } - canvas_.SaveLayer(options.renders_with_attributes() ? paint_ : Paint{}, - ToRect(bounds)); + auto paint = options.renders_with_attributes() ? paint_ : Paint{}; + canvas_.SaveLayer(paint, ToRect(bounds), ToImageFilterProc(backdrop)); } // |flutter::Dispatcher| diff --git a/impeller/display_list/display_list_unittests.cc b/impeller/display_list/display_list_unittests.cc index 81d67c138fcc6..ce5c4ceff5786 100644 --- a/impeller/display_list/display_list_unittests.cc +++ b/impeller/display_list/display_list_unittests.cc @@ -3,8 +3,10 @@ // found in the LICENSE file. #include "display_list/display_list_blend_mode.h" +#include "display_list/display_list_color.h" #include "display_list/display_list_color_filter.h" #include "display_list/display_list_image_filter.h" +#include "display_list/display_list_paint.h" #include "display_list/display_list_tile_mode.h" #include "gtest/gtest.h" #include "third_party/imgui/imgui.h" @@ -285,5 +287,64 @@ TEST_P(DisplayListTest, CanDrawWithImageBlurFilter) { ASSERT_TRUE(OpenPlaygroundHere(callback)); } +TEST_P(DisplayListTest, CanDrawBackdropFilter) { + auto texture = CreateTextureForFixture("embarcadero.jpg"); + + bool first_frame = true; + auto callback = [&]() { + if (first_frame) { + first_frame = false; + ImGui::SetNextWindowSize({400, 100}); + ImGui::SetNextWindowPos({300, 650}); + } + + static float sigma[] = {10, 10}; + static bool use_bounds = true; + static bool draw_circle = true; + + ImGui::Begin("Controls"); + ImGui::SliderFloat2("Sigma", sigma, 0, 100); + ImGui::Checkbox("Use SaveLayer bounds", &use_bounds); + ImGui::Checkbox("Draw child element", &draw_circle); + ImGui::End(); + + flutter::DisplayListBuilder builder; + + Vector2 scale = GetContentScale(); + builder.scale(scale.x, scale.y); + + auto filter = flutter::DlBlurImageFilter(sigma[0], sigma[1], + flutter::DlTileMode::kClamp); + + std::optional bounds; + if (use_bounds) { + auto [p1, p2] = IMPELLER_PLAYGROUND_LINE( + Point(250, 150), Point(800, 600), 20, Color::White(), Color::White()); + bounds = SkRect::MakeLTRB(p1.x, p1.y, p2.x, p2.y); + } + + builder.drawImage(DlImageImpeller::Make(texture), SkPoint::Make(200, 200), + SkSamplingOptions{}, true); + builder.saveLayer(bounds.has_value() ? &bounds.value() : nullptr, nullptr, + &filter); + + if (draw_circle) { + auto circle_center = + IMPELLER_PLAYGROUND_POINT(Point(500, 400), 20, Color::Red()); + + builder.setStyle(flutter::DlDrawStyle::kStroke); + builder.setStrokeCap(flutter::DlStrokeCap::kButt); + builder.setStrokeJoin(flutter::DlStrokeJoin::kBevel); + builder.setStrokeWidth(10); + builder.setColor(flutter::DlColor::kRed().withAlpha(100)); + builder.drawCircle({circle_center.x, circle_center.y}, 100); + } + + return builder.Build(); + }; + + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + } // namespace testing } // namespace impeller diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn index 47cbe5a4be227..51bd1b7405db4 100644 --- a/impeller/entity/BUILD.gn +++ b/impeller/entity/BUILD.gn @@ -87,6 +87,8 @@ impeller_component("entity") { "entity_pass.h", "entity_pass_delegate.cc", "entity_pass_delegate.h", + "inline_pass_context.cc", + "inline_pass_context.h", ] public_deps = [ diff --git a/impeller/entity/entity_pass.cc b/impeller/entity/entity_pass.cc index 58db9cdfec07d..c61b4db7fddb1 100644 --- a/impeller/entity/entity_pass.cc +++ b/impeller/entity/entity_pass.cc @@ -4,15 +4,18 @@ #include "impeller/entity/entity_pass.h" +#include #include #include "flutter/fml/logging.h" +#include "flutter/fml/macros.h" #include "flutter/fml/trace_event.h" #include "impeller/base/validation.h" #include "impeller/entity/contents/content_context.h" #include "impeller/entity/contents/filters/filter_contents.h" #include "impeller/entity/contents/filters/inputs/filter_input.h" #include "impeller/entity/contents/texture_contents.h" +#include "impeller/entity/inline_pass_context.h" #include "impeller/geometry/path_builder.h" #include "impeller/renderer/allocator.h" #include "impeller/renderer/command.h" @@ -36,7 +39,7 @@ void EntityPass::SetDelegate(std::unique_ptr delegate) { void EntityPass::AddEntity(Entity entity) { if (entity.GetBlendMode() > Entity::BlendMode::kLastPipelineBlendMode) { - contains_advanced_blends_ = true; + reads_from_pass_texture_ = true; } elements_.emplace_back(std::move(entity)); @@ -122,8 +125,9 @@ EntityPass* EntityPass::AddSubpass(std::unique_ptr pass) { FML_DCHECK(pass->superpass_ == nullptr); pass->superpass_ = this; - if (pass->blend_mode_ > Entity::BlendMode::kLastPipelineBlendMode) { - contains_advanced_blends_ = true; + if (pass->blend_mode_ > Entity::BlendMode::kLastPipelineBlendMode || + pass->backdrop_filter_proc_.has_value()) { + reads_from_pass_texture_ = true; } auto subpass_pointer = pass.get(); @@ -133,13 +137,13 @@ EntityPass* EntityPass::AddSubpass(std::unique_ptr pass) { bool EntityPass::Render(ContentContext& renderer, RenderTarget render_target) const { - if (contains_advanced_blends_) { + if (reads_from_pass_texture_) { auto offscreen_target = RenderTarget::CreateOffscreen( *renderer.GetContext(), render_target.GetRenderTargetSize(), "EntityPass", // StorageMode::kDevicePrivate, LoadAction::kClear, StoreAction::kStore, StorageMode::kDevicePrivate, LoadAction::kClear, StoreAction::kStore); - if (!RenderInternal(renderer, offscreen_target, Point(), 0)) { + if (!OnRender(renderer, offscreen_target, Point(), Point(), 0)) { return false; } @@ -174,231 +178,252 @@ bool EntityPass::Render(ContentContext& renderer, return true; } - return RenderInternal(renderer, render_target, Point(), 0); + return OnRender(renderer, render_target, Point(), Point(), 0); } -bool EntityPass::RenderInternal(ContentContext& renderer, - RenderTarget render_target, - Point position, - uint32_t pass_depth, - size_t stencil_depth_floor) const { - TRACE_EVENT0("impeller", "EntityPass::Render"); +EntityPass::EntityResult EntityPass::GetEntityForElement( + const EntityPass::Element& element, + ContentContext& renderer, + InlinePassContext& pass_context, + Point position, + uint32_t pass_depth, + size_t stencil_depth_floor) const { + Entity element_entity; + + //-------------------------------------------------------------------------- + /// Setup entity element. + /// + + if (const auto& entity = std::get_if(&element)) { + element_entity = *entity; + if (!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 + // so that they'll end up rendering to the correct on-screen position. + element_entity.SetTransformation( + Matrix::MakeTranslation(Vector3(-position)) * + element_entity.GetTransformation()); + } + } - auto context = renderer.GetContext(); + //-------------------------------------------------------------------------- + /// Setup subpass element. + /// - std::shared_ptr command_buffer; - std::shared_ptr pass; - uint32_t pass_count = 0; + else if (const auto& subpass_ptr = + std::get_if>(&element)) { + auto subpass = subpass_ptr->get(); - auto end_pass = [&command_buffer, &pass, &context]() { - if (!pass->EncodeCommands(context->GetTransientsAllocator())) { - return false; + if (subpass->delegate_->CanElide()) { + return EntityPass::EntityResult::Skip(); } - if (!command_buffer->SubmitCommands()) { - return false; + if (subpass->delegate_->CanCollapseIntoParentPass() && + !subpass->backdrop_filter_proc_.has_value()) { + // Directly render into the parent target and move on. + if (!subpass->OnRender(renderer, pass_context.GetRenderTarget(), position, + position, stencil_depth_floor)) { + return EntityPass::EntityResult::Failure(); + } + return EntityPass::EntityResult::Skip(); } - return true; - }; - - for (const auto& element : elements_) { - Entity element_entity; - - // ========================================================================= - // Setup entity element for rendering ====================================== - // ========================================================================= - if (const auto& entity = std::get_if(&element)) { - element_entity = *entity; - if (!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 - // so that they'll end up rendering to the correct on-screen position. - element_entity.SetTransformation( - Matrix::MakeTranslation(Vector3(-position)) * - element_entity.GetTransformation()); - } + std::shared_ptr backdrop_contents = nullptr; + if (subpass->backdrop_filter_proc_.has_value()) { + auto texture = pass_context.GetTexture(); + // Render the backdrop texture before any of the pass elements. + const auto& proc = subpass->backdrop_filter_proc_.value(); + backdrop_contents = proc(FilterInput::Make(std::move(texture))); + + // The subpass will need to read from the current pass texture when + // rendering the backdrop, so if there's an active pass, end it prior to + // rendering the subpass. + pass_context.EndPass(); } - // ========================================================================= - // Setup subpass element for rendering ===================================== - // ========================================================================= - else if (const auto& subpass_ptr = - std::get_if>(&element)) { - auto subpass = subpass_ptr->get(); + auto subpass_coverage = GetSubpassCoverage(*subpass); - if (subpass->delegate_->CanElide()) { - continue; - } + if (backdrop_contents) { + auto backdrop_coverage = backdrop_contents->GetCoverage(Entity{}); + if (backdrop_coverage.has_value()) { + backdrop_coverage->origin += position; - if (subpass->delegate_->CanCollapseIntoParentPass()) { - // Directly render into the parent target and move on. - if (!subpass->RenderInternal(renderer, render_target, position, - pass_depth, stencil_depth_floor)) { - return false; + if (subpass_coverage.has_value()) { + subpass_coverage = subpass_coverage->Union(backdrop_coverage.value()); + } else { + subpass_coverage = backdrop_coverage; } - continue; } + } - const auto subpass_coverage = GetSubpassCoverage(*subpass); - if (!subpass_coverage.has_value()) { - continue; - } + if (!subpass_coverage.has_value()) { + return EntityPass::EntityResult::Skip(); + } - if (subpass_coverage->size.IsEmpty()) { - // It is not an error to have an empty subpass. But subpasses that can't - // create their intermediates must trip errors. - continue; - } + if (subpass_coverage->size.IsEmpty()) { + // It is not an error to have an empty subpass. But subpasses that can't + // create their intermediates must trip errors. + return EntityPass::EntityResult::Skip(); + } - RenderTarget subpass_target; - if (subpass->contains_advanced_blends_) { - subpass_target = RenderTarget::CreateOffscreen( - *context, ISize::Ceil(subpass_coverage->size), "EntityPass", - StorageMode::kDevicePrivate, LoadAction::kClear, - StoreAction::kStore, StorageMode::kDevicePrivate, - LoadAction::kClear, StoreAction::kStore); - } else { - subpass_target = RenderTarget::CreateOffscreen( - *context, ISize::Ceil(subpass_coverage->size), "EntityPass", - StorageMode::kDevicePrivate, LoadAction::kClear, - StoreAction::kStore, StorageMode::kDeviceTransient, - LoadAction::kClear, StoreAction::kDontCare); - } + RenderTarget subpass_target; + if (subpass->reads_from_pass_texture_) { + subpass_target = RenderTarget::CreateOffscreen( + *renderer.GetContext(), ISize::Ceil(subpass_coverage->size), + "EntityPass", StorageMode::kDevicePrivate, LoadAction::kClear, + StoreAction::kStore, StorageMode::kDevicePrivate, LoadAction::kClear, + StoreAction::kStore); + } else { + subpass_target = RenderTarget::CreateOffscreen( + *renderer.GetContext(), ISize::Ceil(subpass_coverage->size), + "EntityPass", StorageMode::kDevicePrivate, LoadAction::kClear, + StoreAction::kStore, StorageMode::kDeviceTransient, + LoadAction::kClear, StoreAction::kDontCare); + } - auto subpass_texture = subpass_target.GetRenderTargetTexture(); + auto subpass_texture = subpass_target.GetRenderTargetTexture(); - if (!subpass_texture) { - return false; - } + if (!subpass_texture) { + return EntityPass::EntityResult::Failure(); + } - auto offscreen_texture_contents = - subpass->delegate_->CreateContentsForSubpassTarget(subpass_texture); - - if (!offscreen_texture_contents) { - // This is an error because the subpass delegate said the pass couldn't - // be collapsed into its parent. Yet, when asked how it want's to - // postprocess the offscreen texture, it couldn't give us an answer. - // - // Theoretically, we could collapse the pass now. But that would be - // wasteful as we already have the offscreen texture and we don't want - // to discard it without ever using it. Just make the delegate do the - // right thing. - return false; - } + auto offscreen_texture_contents = + subpass->delegate_->CreateContentsForSubpassTarget(subpass_texture); + + if (!offscreen_texture_contents) { + // This is an error because the subpass delegate said the pass couldn't + // be collapsed into its parent. Yet, when asked how it want's to + // postprocess the offscreen texture, it couldn't give us an answer. + // + // Theoretically, we could collapse the pass now. But that would be + // wasteful as we already have the offscreen texture and we don't want + // to discard it without ever using it. Just make the delegate do the + // right thing. + return EntityPass::EntityResult::Failure(); + } - // Stencil textures aren't shared between EntityPasses (as much of the - // time they are transient). - if (!subpass->RenderInternal(renderer, subpass_target, - subpass_coverage->origin, ++pass_depth, - subpass->stencil_depth_)) { - return false; - } + // Stencil textures aren't shared between EntityPasses (as much of the + // time they are transient). + if (!subpass->OnRender(renderer, subpass_target, subpass_coverage->origin, + position, ++pass_depth, subpass->stencil_depth_, + backdrop_contents)) { + return EntityPass::EntityResult::Failure(); + } - element_entity.SetContents(std::move(offscreen_texture_contents)); - element_entity.SetStencilDepth(subpass->stencil_depth_); - element_entity.SetBlendMode(subpass->blend_mode_); - // Once we have filters being applied for SaveLayer, some special sauce - // may be needed here (or in PaintPassDelegate) to ensure the filter - // parameters are transformed by the `xformation_` matrix, while - // continuing to apply only the subpass offset to the offscreen texture. - element_entity.SetTransformation(Matrix::MakeTranslation( - Vector3(subpass_coverage->origin - position))); - } else { - FML_UNREACHABLE(); + element_entity.SetContents(std::move(offscreen_texture_contents)); + element_entity.SetStencilDepth(subpass->stencil_depth_); + element_entity.SetBlendMode(subpass->blend_mode_); + // Once we have filters being applied for SaveLayer, some special sauce + // may be needed here (or in PaintPassDelegate) to ensure the filter + // parameters are transformed by the `xformation_` matrix, while + // continuing to apply only the subpass offset to the offscreen texture. + element_entity.SetTransformation( + Matrix::MakeTranslation(Vector3(subpass_coverage->origin - position))); + } else { + FML_UNREACHABLE(); + } + + return EntityPass::EntityResult::Success(element_entity); +} + +bool EntityPass::OnRender(ContentContext& renderer, + RenderTarget render_target, + Point position, + Point parent_position, + uint32_t pass_depth, + size_t stencil_depth_floor, + std::shared_ptr backdrop_contents) const { + TRACE_EVENT0("impeller", "EntityPass::OnRender"); + + auto context = renderer.GetContext(); + InlinePassContext pass_context(context, render_target); + if (!pass_context.IsValid()) { + return false; + } + + auto render_element = [&stencil_depth_floor, &pass_context, &pass_depth, + &renderer](Entity element_entity) { + element_entity.SetStencilDepth(element_entity.GetStencilDepth() - + stencil_depth_floor); + + auto pass = pass_context.GetRenderPass(pass_depth); + if (!element_entity.Render(renderer, *pass)) { + return false; } + return true; + }; - // ========================================================================= - // Configure the RenderPass ================================================ - // ========================================================================= + if (backdrop_filter_proc_.has_value()) { + if (!backdrop_contents) { + return false; + } - if (pass && element_entity.GetBlendMode() > - Entity::BlendMode::kLastPipelineBlendMode) { + Entity backdrop_entity; + backdrop_entity.SetContents(std::move(backdrop_contents)); + backdrop_entity.SetTransformation( + Matrix::MakeTranslation(Vector3(parent_position - position))); + + render_element(backdrop_entity); + } + + for (const auto& element : elements_) { + EntityResult result = + GetEntityForElement(element, renderer, pass_context, position, + pass_depth, stencil_depth_floor); + + switch (result.status) { + case EntityResult::kSuccess: + break; + case EntityResult::kFailure: + return false; + case EntityResult::kSkip: + continue; + }; + + //-------------------------------------------------------------------------- + /// Setup advanced blends. + /// + + if (result.entity.GetBlendMode() > + Entity::BlendMode::kLastPipelineBlendMode) { // End the active pass and flush the buffer before rendering "advanced" // blends. Advanced blends work by binding the current render target // texture as an input ("destination"), blending with a second texture // input ("source"), writing the result to an intermediate texture, and // finally copying the data from the intermediate texture back to the - // render target texture. And so all of the commands that have written to - // the render target texture so far need to execute before it's bound for - // blending (otherwise the blend pass will end up executing before all the - // previous commands in the active pass). - if (!end_pass()) { + // render target texture. And so all of the commands that have written + // to the render target texture so far need to execute before it's bound + // for blending (otherwise the blend pass will end up executing before + // all the previous commands in the active pass). + if (!pass_context.EndPass()) { return false; } - // Resetting these handles triggers a new pass to get created below - pass = nullptr; - command_buffer = nullptr; - // Amend an advanced blend to the contents. - if (render_target.GetColorAttachments().empty()) { + // Amend an advanced blend filter to the contents, attaching the pass + // texture. + auto texture = pass_context.GetTexture(); + if (!texture) { return false; } - auto color0 = render_target.GetColorAttachments().find(0)->second; FilterInput::Vector inputs = { - FilterInput::Make(element_entity.GetContents()), - FilterInput::Make( - color0.resolve_texture ? color0.resolve_texture : color0.texture, - element_entity.GetTransformation().Invert())}; - element_entity.SetContents( - FilterContents::MakeBlend(element_entity.GetBlendMode(), inputs)); - element_entity.SetBlendMode(Entity::BlendMode::kSourceOver); - } - - // Create a new render pass to render the element if one isn't active. - if (!pass) { - command_buffer = context->CreateRenderCommandBuffer(); - if (!command_buffer) { - return false; - } - - command_buffer->SetLabel( - "EntityPass Command Buffer: Depth=" + std::to_string(pass_depth) + - " Count=" + std::to_string(pass_count)); - - // Never clear the texture for subsequent passes. - if (pass_count > 0) { - if (!render_target.GetColorAttachments().empty()) { - auto color0 = render_target.GetColorAttachments().find(0)->second; - color0.load_action = LoadAction::kLoad; - render_target.SetColorAttachment(color0, 0); - } - - if (auto stencil = render_target.GetStencilAttachment(); - stencil.has_value()) { - stencil->load_action = LoadAction::kLoad; - render_target.SetStencilAttachment(stencil.value()); - } - } - - pass = command_buffer->CreateRenderPass(render_target); - if (!pass) { - return false; - } - - pass->SetLabel( - "EntityPass Render Pass: Depth=" + std::to_string(pass_depth) + - " Count=" + std::to_string(pass_count)); - - ++pass_count; + FilterInput::Make(result.entity.GetContents()), + FilterInput::Make(texture, + result.entity.GetTransformation().Invert())}; + result.entity.SetContents( + FilterContents::MakeBlend(result.entity.GetBlendMode(), inputs)); + result.entity.SetBlendMode(Entity::BlendMode::kSourceOver); } - // ========================================================================= - // Render the element ====================================================== - // ========================================================================= - - element_entity.SetStencilDepth(element_entity.GetStencilDepth() - - stencil_depth_floor); + //-------------------------------------------------------------------------- + /// Render the Element. + /// - if (!element_entity.Render(renderer, *pass)) { + if (!render_element(result.entity)) { return false; } } - if (pass) { - return end_pass(); - } return true; } @@ -455,4 +480,11 @@ void EntityPass::SetBlendMode(Entity::BlendMode blend_mode) { blend_mode_ = blend_mode; } +void EntityPass::SetBackdropFilter(std::optional proc) { + backdrop_filter_proc_ = proc; + if (superpass_) { + superpass_->reads_from_pass_texture_ = true; + } +} + } // namespace impeller diff --git a/impeller/entity/entity_pass.h b/impeller/entity/entity_pass.h index 0566bd4b0f3b0..9954fe5898ea1 100644 --- a/impeller/entity/entity_pass.h +++ b/impeller/entity/entity_pass.h @@ -4,14 +4,17 @@ #pragma once +#include #include #include #include #include "flutter/fml/macros.h" #include "impeller/entity/contents/contents.h" +#include "impeller/entity/contents/filters/filter_contents.h" #include "impeller/entity/entity.h" #include "impeller/entity/entity_pass_delegate.h" +#include "impeller/entity/inline_pass_context.h" #include "impeller/renderer/render_target.h" #include "impeller/typographer/lazy_glyph_atlas.h" @@ -22,6 +25,8 @@ class ContentContext; class EntityPass { public: using Element = std::variant>; + using BackdropFilterProc = + std::function(FilterInput::Ref)>; EntityPass(); @@ -53,16 +58,50 @@ class EntityPass { void SetBlendMode(Entity::BlendMode blend_mode); + void SetBackdropFilter(std::optional proc); + std::optional GetSubpassCoverage(const EntityPass& subpass) const; std::optional GetElementsCoverage() const; private: - bool RenderInternal(ContentContext& renderer, - RenderTarget render_target, - Point position, - uint32_t pass_depth, - size_t stencil_depth_floor = 0) const; + struct EntityResult { + enum Status { + /// The entity was successfully resolved and can be rendered. + kSuccess, + /// An unexpected rendering error occurred while resolving the Entity. + kFailure, + /// The entity should be skipped because rendering it will contribute + /// nothing to the frame. + kSkip, + }; + + /// @brief The resulting entity that should be rendered. If `std::nullopt`, + /// there is nothing to render. + Entity entity; + /// @brief This is set to `false` if there was an unexpected rendering + /// error while resolving the Entity. + Status status = kFailure; + + static EntityResult Success(Entity e) { return {e, kSuccess}; } + static EntityResult Failure() { return {{}, kFailure}; } + static EntityResult Skip() { return {{}, kSkip}; } + }; + + EntityResult GetEntityForElement(const EntityPass::Element& element, + ContentContext& renderer, + InlinePassContext& pass_context, + Point position, + uint32_t pass_depth, + size_t stencil_depth_floor) const; + + bool OnRender(ContentContext& renderer, + RenderTarget render_target, + Point position, + Point parent_position, + uint32_t pass_depth, + size_t stencil_depth_floor = 0, + std::shared_ptr backdrop_contents = nullptr) const; std::vector elements_; @@ -70,7 +109,16 @@ class EntityPass { Matrix xformation_; size_t stencil_depth_ = 0u; Entity::BlendMode blend_mode_ = Entity::BlendMode::kSourceOver; - bool contains_advanced_blends_ = false; + + /// This flag is set to `true` whenever an entity is added to the pass that + /// requires reading the pass texture during rendering. This can happen in the + /// following scenarios: + /// 1. An entity with an "advanced blend" is added to the pass. + /// 2. A subpass with a backdrop filter is added to the pass. + bool reads_from_pass_texture_ = false; + + std::optional backdrop_filter_proc_ = std::nullopt; + std::unique_ptr delegate_ = EntityPassDelegate::MakeDefault(); std::shared_ptr lazy_glyph_atlas_ = diff --git a/impeller/entity/inline_pass_context.cc b/impeller/entity/inline_pass_context.cc new file mode 100644 index 0000000000000..0b41e8687e2d7 --- /dev/null +++ b/impeller/entity/inline_pass_context.cc @@ -0,0 +1,101 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/entity/inline_pass_context.h" + +#include "impeller/renderer/command_buffer.h" + +namespace impeller { + +InlinePassContext::InlinePassContext(std::shared_ptr context, + RenderTarget render_target) + : context_(context), render_target_(render_target) {} + +InlinePassContext::~InlinePassContext() { + EndPass(); +} + +bool InlinePassContext::IsValid() const { + return !render_target_.GetColorAttachments().empty(); +} + +bool InlinePassContext::IsActive() const { + return pass_ != nullptr; +} + +std::shared_ptr InlinePassContext::GetTexture() { + if (!IsValid()) { + return nullptr; + } + auto color0 = render_target_.GetColorAttachments().find(0)->second; + return color0.resolve_texture ? color0.resolve_texture : color0.texture; +} + +bool InlinePassContext::EndPass() { + if (!IsActive()) { + return true; + } + + if (!pass_->EncodeCommands(context_->GetTransientsAllocator())) { + return false; + } + + if (!command_buffer_->SubmitCommands()) { + return false; + } + + pass_ = nullptr; + command_buffer_ = nullptr; + + return true; +} + +const RenderTarget& InlinePassContext::GetRenderTarget() const { + return render_target_; +} + +std::shared_ptr InlinePassContext::GetRenderPass( + uint32_t pass_depth) { + // Create a new render pass if one isn't active. + if (!IsActive()) { + command_buffer_ = context_->CreateRenderCommandBuffer(); + if (!command_buffer_) { + return nullptr; + } + + command_buffer_->SetLabel( + "EntityPass Command Buffer: Depth=" + std::to_string(pass_depth) + + " Count=" + std::to_string(pass_count_)); + + // Never clear the texture for subsequent passes. + if (pass_count_ > 0) { + if (!render_target_.GetColorAttachments().empty()) { + auto color0 = render_target_.GetColorAttachments().find(0)->second; + color0.load_action = LoadAction::kLoad; + render_target_.SetColorAttachment(color0, 0); + } + + if (auto stencil = render_target_.GetStencilAttachment(); + stencil.has_value()) { + stencil->load_action = LoadAction::kLoad; + render_target_.SetStencilAttachment(stencil.value()); + } + } + + pass_ = command_buffer_->CreateRenderPass(render_target_); + if (!pass_) { + return nullptr; + } + + pass_->SetLabel( + "EntityPass Render Pass: Depth=" + std::to_string(pass_depth) + + " Count=" + std::to_string(pass_count_)); + + ++pass_count_; + } + + return pass_; +} + +} // namespace impeller diff --git a/impeller/entity/inline_pass_context.h b/impeller/entity/inline_pass_context.h new file mode 100644 index 0000000000000..cde3aad13b16f --- /dev/null +++ b/impeller/entity/inline_pass_context.h @@ -0,0 +1,37 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#pragma once + +#include "impeller/renderer/context.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/render_target.h" + +namespace impeller { + +class InlinePassContext { + public: + InlinePassContext(std::shared_ptr context, + RenderTarget render_target); + ~InlinePassContext(); + + bool IsValid() const; + bool IsActive() const; + std::shared_ptr GetTexture(); + bool EndPass(); + const RenderTarget& GetRenderTarget() const; + + std::shared_ptr GetRenderPass(uint32_t pass_depth); + + private: + std::shared_ptr context_; + RenderTarget render_target_; + std::shared_ptr command_buffer_; + std::shared_ptr pass_; + uint32_t pass_count_ = 0; + + FML_DISALLOW_COPY_AND_ASSIGN(InlinePassContext); +}; + +} // namespace impeller