From a1ebf7e88b6db80adccc4efa6e0fa7930ba9a89e Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Sat, 2 Sep 2023 17:45:15 +0200 Subject: [PATCH 1/5] Reduce number of surfaces when presenting platform views --- flow/embedded_views.cc | 5 +- flow/embedded_views.h | 11 +- .../external_view_embedder.cc | 8 +- .../framework/Source/FlutterPlatformViews.mm | 22 +- .../Source/FlutterPlatformViews_Internal.h | 2 +- .../embedder/embedder_external_view.cc | 14 +- .../embedder/embedder_external_view.h | 18 +- .../embedder_external_view_embedder.cc | 362 +++++++++++++----- .../embedder/embedder_render_target_cache.cc | 50 +-- .../embedder/embedder_render_target_cache.h | 24 +- .../embedder/tests/embedder_unittests.cc | 32 +- 11 files changed, 322 insertions(+), 226 deletions(-) diff --git a/flow/embedded_views.cc b/flow/embedded_views.cc index 086882b76adf4..1bf6e6c1d2911 100644 --- a/flow/embedded_views.cc +++ b/flow/embedded_views.cc @@ -21,9 +21,8 @@ void DisplayListEmbedderViewSlice::end_recording() { builder_ = nullptr; } -std::list DisplayListEmbedderViewSlice::searchNonOverlappingDrawnRects( - const SkRect& query) const { - return display_list_->rtree()->searchAndConsolidateRects(query); +const DlRegion& DisplayListEmbedderViewSlice::getRegion() const { + return display_list_->rtree()->region(); } void DisplayListEmbedderViewSlice::render_into(DlCanvas* canvas) { diff --git a/flow/embedded_views.h b/flow/embedded_views.h index c16426980f88d..ed414ed678e35 100644 --- a/flow/embedded_views.h +++ b/flow/embedded_views.h @@ -338,8 +338,11 @@ class EmbedderViewSlice { virtual ~EmbedderViewSlice() = default; virtual DlCanvas* canvas() = 0; virtual void end_recording() = 0; - virtual std::list searchNonOverlappingDrawnRects( - const SkRect& query) const = 0; + virtual const DlRegion& getRegion() const = 0; + DlRegion region(const SkRect& query) const { + return DlRegion::MakeIntersection(getRegion(), DlRegion(query.roundOut())); + } + virtual void render_into(DlCanvas* canvas) = 0; }; @@ -350,8 +353,8 @@ class DisplayListEmbedderViewSlice : public EmbedderViewSlice { DlCanvas* canvas() override; void end_recording() override; - std::list searchNonOverlappingDrawnRects( - const SkRect& query) const override; + const DlRegion& getRegion() const override; + void render_into(DlCanvas* canvas) override; void dispatch(DlOpReceiver& receiver); bool is_empty(); diff --git a/shell/platform/android/external_view_embedder/external_view_embedder.cc b/shell/platform/android/external_view_embedder/external_view_embedder.cc index 905c2751a3d4b..e92edbca5996e 100644 --- a/shell/platform/android/external_view_embedder/external_view_embedder.cc +++ b/shell/platform/android/external_view_embedder/external_view_embedder.cc @@ -104,15 +104,15 @@ void AndroidExternalViewEmbedder::SubmitFrame( // The rect above the `current_view_rect` SkRect partial_joined_rect = SkRect::MakeEmpty(); // Each rect corresponds to a native view that renders Flutter UI. - std::list intersection_rects = - slice->searchNonOverlappingDrawnRects(current_view_rect); + std::vector intersection_rects = + slice->region(current_view_rect).getRects(); // Limit the number of native views, so it doesn't grow forever. // // In this case, the rects are merged into a single one that is the union // of all the rects. - for (const SkRect& rect : intersection_rects) { - partial_joined_rect.join(rect); + for (const SkIRect& rect : intersection_rects) { + partial_joined_rect.join(SkRect::Make(rect)); } // Get the intersection rect with the `current_view_rect`, partial_joined_rect.intersect(current_view_rect); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index a9fc569bc1165..dc8904ea2a061 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -705,8 +705,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, for (size_t j = i + 1; j > 0; j--) { int64_t current_platform_view_id = composition_order_[j - 1]; SkRect platform_view_rect = GetPlatformViewRect(current_platform_view_id); - std::list intersection_rects = - slice->searchNonOverlappingDrawnRects(platform_view_rect); + std::vector intersection_rects = slice->region(platform_view_rect).getRects(); auto allocation_size = intersection_rects.size(); // For testing purposes, the overlay id is used to find the overlay view. @@ -719,8 +718,8 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, // TODO(egarciad): Consider making this configurable. // https://github.com/flutter/flutter/issues/52510 if (allocation_size > kMaxLayerAllocations) { - SkRect joined_rect = SkRect::MakeEmpty(); - for (const SkRect& rect : intersection_rects) { + SkIRect joined_rect = SkIRect::MakeEmpty(); + for (const SkIRect& rect : intersection_rects) { joined_rect.join(rect); } // Replace the rects in the intersection rects list for a single rect that is @@ -728,18 +727,13 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, intersection_rects.clear(); intersection_rects.push_back(joined_rect); } - for (SkRect& joined_rect : intersection_rects) { + for (SkIRect& joined_rect : intersection_rects) { // Get the intersection rect between the current rect // and the platform view rect. - joined_rect.intersect(platform_view_rect); - // Subpixels in the platform may not align with the canvas subpixels. - // To workaround it, round the floating point bounds and make the rect slightly larger. - // For example, {0.3, 0.5, 3.1, 4.7} becomes {0, 0, 4, 5}. - joined_rect.setLTRB(std::floor(joined_rect.left()), std::floor(joined_rect.top()), - std::ceil(joined_rect.right()), std::ceil(joined_rect.bottom())); + joined_rect.intersect(platform_view_rect.roundOut()); // Clip the background canvas, so it doesn't contain any of the pixels drawn // on the overlay layer. - background_canvas->ClipRect(joined_rect, DlCanvas::ClipOp::kDifference); + background_canvas->ClipRect(SkRect::Make(joined_rect), DlCanvas::ClipOp::kDifference); // Get a new host layer. std::shared_ptr layer = GetLayer(gr_context, // @@ -817,7 +811,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, GrDirectContext* gr_context, const std::shared_ptr& ios_context, EmbedderViewSlice* slice, - SkRect rect, + SkIRect rect, int64_t view_id, int64_t overlay_id, MTLPixelFormat pixel_format) { @@ -852,7 +846,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, DlCanvas* overlay_canvas = frame->Canvas(); int restore_count = overlay_canvas->GetSaveCount(); overlay_canvas->Save(); - overlay_canvas->ClipRect(rect); + overlay_canvas->ClipRect(SkRect::Make(rect)); overlay_canvas->Clear(DlColor::kTransparent()); slice->render_into(overlay_canvas); overlay_canvas->RestoreToCount(restore_count); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index e754aec279257..677941421ca9e 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -319,7 +319,7 @@ class FlutterPlatformViewsController { std::shared_ptr GetLayer(GrDirectContext* gr_context, const std::shared_ptr& ios_context, EmbedderViewSlice* slice, - SkRect rect, + SkIRect rect, int64_t view_id, int64_t overlay_id, MTLPixelFormat pixel_format); diff --git a/shell/platform/embedder/embedder_external_view.cc b/shell/platform/embedder/embedder_external_view.cc index 843ef85dfed91..9360810463026 100644 --- a/shell/platform/embedder/embedder_external_view.cc +++ b/shell/platform/embedder/embedder_external_view.cc @@ -43,7 +43,7 @@ EmbedderExternalView::~EmbedderExternalView() = default; EmbedderExternalView::RenderTargetDescriptor EmbedderExternalView::CreateRenderTargetDescriptor() const { - return {view_identifier_, render_surface_size_}; + return RenderTargetDescriptor(render_surface_size_); } DlCanvas* EmbedderExternalView::GetCanvas() { @@ -62,9 +62,8 @@ bool EmbedderExternalView::HasPlatformView() const { return view_identifier_.platform_view_id.has_value(); } -std::list EmbedderExternalView::GetEngineRenderedContentsRegion( - const SkRect& query) const { - return slice_->searchNonOverlappingDrawnRects(query); +const DlRegion& EmbedderExternalView::GetDlRegion() const { + return slice_->getRegion(); } bool EmbedderExternalView::HasEngineRenderedContents() { @@ -87,7 +86,8 @@ const EmbeddedViewParams* EmbedderExternalView::GetEmbeddedViewParams() const { return embedded_view_params_.get(); } -bool EmbedderExternalView::Render(const EmbedderRenderTarget& render_target) { +bool EmbedderExternalView::Render(const EmbedderRenderTarget& render_target, + bool clear_surface) { TRACE_EVENT0("flutter", "EmbedderExternalView::Render"); TryEndRecording(); FML_DCHECK(HasEngineRenderedContents()) @@ -124,7 +124,9 @@ bool EmbedderExternalView::Render(const EmbedderRenderTarget& render_target) { DlSkCanvasAdapter dl_canvas(canvas); int restore_count = dl_canvas.GetSaveCount(); dl_canvas.SetTransform(surface_transformation_); - dl_canvas.Clear(DlColor::kTransparent()); + if (clear_surface) { + dl_canvas.Clear(DlColor::kTransparent()); + } slice_->render_into(&dl_canvas); dl_canvas.RestoreToCount(restore_count); dl_canvas.Flush(); diff --git a/shell/platform/embedder/embedder_external_view.h b/shell/platform/embedder/embedder_external_view.h index 6ba27860a449b..f2557cb508d82 100644 --- a/shell/platform/embedder/embedder_external_view.h +++ b/shell/platform/embedder/embedder_external_view.h @@ -46,28 +46,23 @@ class EmbedderExternalView { }; struct RenderTargetDescriptor { - ViewIdentifier view_identifier; SkISize surface_size; - RenderTargetDescriptor(ViewIdentifier p_view_identifier, - SkISize p_surface_size) - : view_identifier(p_view_identifier), surface_size(p_surface_size) {} + explicit RenderTargetDescriptor(const SkISize& p_surface_size) + : surface_size(p_surface_size) {} struct Hash { constexpr std::size_t operator()( const RenderTargetDescriptor& desc) const { return fml::HashCombine(desc.surface_size.width(), - desc.surface_size.height(), - ViewIdentifier::Hash{}(desc.view_identifier)); + desc.surface_size.height()); } }; struct Equal { bool operator()(const RenderTargetDescriptor& lhs, const RenderTargetDescriptor& rhs) const { - return lhs.surface_size == rhs.surface_size && - ViewIdentifier::Equal{}(lhs.view_identifier, - rhs.view_identifier); + return lhs.surface_size == rhs.surface_size; } }; }; @@ -107,9 +102,10 @@ class EmbedderExternalView { SkISize GetRenderSurfaceSize() const; - bool Render(const EmbedderRenderTarget& render_target); + bool Render(const EmbedderRenderTarget& render_target, + bool clear_surface = true); - std::list GetEngineRenderedContentsRegion(const SkRect& query) const; + const DlRegion& GetDlRegion() const; private: // End the recording of the slice. diff --git a/shell/platform/embedder/embedder_external_view_embedder.cc b/shell/platform/embedder/embedder_external_view_embedder.cc index 94c770f809277..1b6d04b2a3aa9 100644 --- a/shell/platform/embedder/embedder_external_view_embedder.cc +++ b/shell/platform/embedder/embedder_external_view_embedder.cc @@ -4,7 +4,7 @@ #include "flutter/shell/platform/embedder/embedder_external_view_embedder.h" -#include +#include #include #include "flutter/shell/platform/embedder/embedder_layers.h" @@ -13,6 +13,8 @@ namespace flutter { +static const auto kRootViewIdentifier = EmbedderExternalView::ViewIdentifier{}; + EmbedderExternalViewEmbedder::EmbedderExternalViewEmbedder( bool avoid_backing_store_cache, const CreateRenderTargetCallback& create_render_target_callback, @@ -61,9 +63,6 @@ void EmbedderExternalViewEmbedder::BeginFrame( pending_device_pixel_ratio_ = device_pixel_ratio; pending_surface_transformation_ = GetSurfaceTransformation(); - static const auto kRootViewIdentifier = - EmbedderExternalView::ViewIdentifier{}; - pending_views_[kRootViewIdentifier] = std::make_unique( pending_frame_size_, pending_surface_transformation_); composition_order_.push_back(kRootViewIdentifier); @@ -87,7 +86,7 @@ void EmbedderExternalViewEmbedder::PrerollCompositeEmbeddedView( // |ExternalViewEmbedder| DlCanvas* EmbedderExternalViewEmbedder::GetRootCanvas() { - auto found = pending_views_.find(EmbedderExternalView::ViewIdentifier{}); + auto found = pending_views_.find(kRootViewIdentifier); if (found == pending_views_.end()) { FML_DLOG(WARNING) << "No root canvas could be found. This is extremely unlikely and " @@ -122,13 +121,260 @@ static FlutterBackingStoreConfig MakeBackingStoreConfig( return config; } -// |ExternalViewEmbedder| +namespace { + +struct PlatformView { + EmbedderExternalView::ViewIdentifier view_identifier; + const EmbeddedViewParams* params; + SkRect clipped_frame; + + explicit PlatformView(const EmbedderExternalView* view) { + assert(view->HasPlatformView()); + view_identifier = view->GetViewIdentifier(); + params = view->GetEmbeddedViewParams(); + + clipped_frame = view->GetEmbeddedViewParams()->finalBoundingRect(); + SkMatrix transform; + for (auto i = params->mutatorsStack().Begin(); + i != params->mutatorsStack().End(); ++i) { + const auto& m = *i; + if (m->GetType() == MutatorType::kTransform) { + transform.preConcat(m->GetMatrix()); + } else if (m->GetType() == MutatorType::kClipRect) { + auto rect = transform.mapRect(m->GetRect()); + if (!clipped_frame.intersect(rect)) { + clipped_frame = SkRect::MakeEmpty(); + } + } else if (m->GetType() == MutatorType::kClipRRect) { + auto rect = transform.mapRect(m->GetRRect().getBounds()); + if (!clipped_frame.intersect(rect)) { + clipped_frame = SkRect::MakeEmpty(); + } + } else if (m->GetType() == MutatorType::kClipPath) { + auto rect = transform.mapRect(m->GetPath().getBounds()); + if (!clipped_frame.intersect(rect)) { + clipped_frame = SkRect::MakeEmpty(); + } + } + } + } +}; + +class Layer { + public: + bool IntersectsPlatformView(const SkRect& rect) { + for (auto& platform_view : platform_views_) { + if (platform_view.clipped_frame.intersects(rect)) { + return true; + } + } + return false; + } + + bool IntersectsPlatformView(const DlRegion& region) { + for (auto& platform_view : platform_views_) { + if (region.intersects(platform_view.clipped_frame.roundOut())) { + return true; + } + } + return false; + } + + bool IntersectsFlutterContents(const SkRect& rect) { + return flutter_contents_region_.intersects(rect.roundOut()); + } + + bool IntersectsFlutterContents(const DlRegion& region) { + return flutter_contents_region_.intersects(region); + } + + void AddPlatformView(const PlatformView& platform_view) { + platform_views_.push_back(platform_view); + } + + void AddFlutterContents(EmbedderExternalView* contents, + const DlRegion& contents_region) { + flutter_contents_.push_back(contents); + flutter_contents_region_ = + DlRegion::MakeUnion(flutter_contents_region_, contents_region); + } + + bool has_flutter_contents() const { return !flutter_contents_.empty(); } + + void SetRenderTarget(std::unique_ptr target) { + FML_DCHECK(render_target_ == nullptr); + FML_DCHECK(has_flutter_contents()); + render_target_ = std::move(target); + } + + void RenderFlutterContents() { + FML_DCHECK(has_flutter_contents()); + if (render_target_) { + bool clear_surface = true; + for (auto c : flutter_contents_) { + c->Render(*render_target_, clear_surface); + clear_surface = false; + } + } + } + + const std::vector& platform_views() const { + return platform_views_; + } + + EmbedderRenderTarget* render_target() { return render_target_.get(); } + + std::vector coverage() { + return flutter_contents_region_.getRects(); + } + + private: + std::vector platform_views_; + std::vector flutter_contents_; + DlRegion flutter_contents_region_; + friend class LayerBuilder; + std::unique_ptr render_target_; +}; + +class LayerBuilder { + public: + explicit LayerBuilder(SkISize frame_size) : frame_size_(frame_size) { + layers_.push_back(Layer()); + } + + void AddExternalView(EmbedderExternalView* view) { + if (view->HasPlatformView()) { + PlatformView platform_view(view); + AddPlatformView(platform_view); + } + if (view->HasEngineRenderedContents()) { + AddFlutterContents(view); + } + } + + void PrepareBackingStore( + const std::function( + FlutterBackingStoreConfig)>& target_provider) { + auto config = MakeBackingStoreConfig(frame_size_); + for (auto& layer : layers_) { + if (layer.has_flutter_contents()) { + layer.SetRenderTarget(target_provider(config)); + } + } + } + + void Render() { + for (auto& layer : layers_) { + if (layer.has_flutter_contents()) { + layer.RenderFlutterContents(); + } + } + } + + void PushLayers(EmbedderLayers& layers) { + for (auto& layer : layers_) { + for (auto& view : layer.platform_views()) { + auto platform_view_id = view.view_identifier.platform_view_id; + if (platform_view_id.has_value()) { + layers.PushPlatformViewLayer(platform_view_id.value(), *view.params); + } + } + if (layer.render_target() != nullptr) { + layers.PushBackingStoreLayer(layer.render_target()->GetBackingStore(), + layer.coverage()); + } + } + } + + std::vector> + ClearAndCollectRenderTargets() { + std::vector> result; + for (auto& layer : layers_) { + if (layer.render_target() != nullptr) { + result.push_back(std::move(layer.render_target_)); + } + } + layers_.clear(); + return result; + } + + private: + void AddPlatformView(PlatformView view) { + GetLayerForPlatformView(view).AddPlatformView(view); + } + + void AddFlutterContents(EmbedderExternalView* contents) { + assert(contents->HasEngineRenderedContents()); + + DlRegion region = contents->GetDlRegion(); + GetLayerForFlutterContentsRegion(region).AddFlutterContents(contents, + region); + } + + Layer& GetLayerForPlatformView(PlatformView view) { + for (auto iter = layers_.rbegin(); iter != layers_.rend(); ++iter) { + if (iter->IntersectsFlutterContents(view.clipped_frame)) { + if (iter == layers_.rbegin()) { + layers_.emplace_back(); + return layers_.back(); + } else { + --iter; + return *iter; + } + } + if (iter->IntersectsPlatformView(view.clipped_frame)) { + return *iter; + } + } + return layers_.front(); + } + + Layer& GetLayerForFlutterContentsRegion(const DlRegion& region) { + for (auto iter = layers_.rbegin(); iter != layers_.rend(); ++iter) { + if (iter->IntersectsPlatformView(region) || + iter->IntersectsFlutterContents(region)) { + return *iter; + } + } + return layers_.front(); + } + + std::vector layers_; + SkISize frame_size_; +}; + +}; // namespace + +// https://flutter.dev/go/optimized-platform-view-layers void EmbedderExternalViewEmbedder::SubmitFrame( GrDirectContext* context, const std::shared_ptr& aiks_context, std::unique_ptr frame) { - auto [matched_render_targets, pending_keys] = - render_target_cache_.GetExistingTargetsInCache(pending_views_); + SkRect _rect = SkRect::MakeIWH(pending_frame_size_.width(), + pending_frame_size_.height()); + pending_surface_transformation_.mapRect(&_rect); + + LayerBuilder builder(SkISize::Make(_rect.width(), _rect.height())); + + for (auto view_id : composition_order_) { + auto& view = pending_views_[view_id]; + builder.AddExternalView(view.get()); + } + + builder.PrepareBackingStore([&](FlutterBackingStoreConfig config) { + std::unique_ptr target; + if (!avoid_backing_store_cache_) { + target = render_target_cache_.GetRenderTarget( + EmbedderExternalView::RenderTargetDescriptor( + SkISize{static_cast(config.size.width), + static_cast(config.size.height)})); + } + if (target != nullptr) { + return target; + } else { + return create_render_target_callback_(context, aiks_context, config); + } + }); // This is where unused render targets will be collected. Control may flow to // the embedder. Here, the embedder has the opportunity to trample on the @@ -145,44 +391,6 @@ void EmbedderExternalViewEmbedder::SubmitFrame( auto deferred_cleanup_render_targets = render_target_cache_.ClearAllRenderTargetsInCache(); - for (const auto& pending_key : pending_keys) { - const auto& external_view = pending_views_.at(pending_key); - - // If the external view does not have engine rendered contents, it makes no - // sense to ask to embedder to create a render target for us as we don't - // intend to render into it and ask the embedder for presentation anyway. - // Save some memory. - if (!external_view->HasEngineRenderedContents()) { - continue; - } - - // This is the size of render surface we want the embedder to create for - // us. As or right now, this is going to always be equal to the frame size - // post transformation. But, in case optimizations are applied that make - // it so that embedder rendered into surfaces that aren't full screen, - // this assumption will break. So it's just best to ask view for its size - // directly. - const auto render_surface_size = external_view->GetRenderSurfaceSize(); - - const auto backing_store_config = - MakeBackingStoreConfig(render_surface_size); - - // This is where the embedder will create render targets for us. Control - // flow to the embedder makes the engine susceptible to having the embedder - // trample on the OpenGL context. Before any Skia operations are performed, - // the context must be reset. - // - // @warning: Embedder may trample on our OpenGL context here. - auto render_target = create_render_target_callback_(context, aiks_context, - backing_store_config); - - if (!render_target) { - FML_LOG(ERROR) << "Embedder did not return a valid render target."; - return; - } - matched_render_targets[pending_key] = std::move(render_target); - } - // The OpenGL context could have been trampled by the embedder at this point // as it attempted to collect old render targets and create new ones. Tell // Skia to not rely on existing bindings. @@ -190,16 +398,7 @@ void EmbedderExternalViewEmbedder::SubmitFrame( context->resetContext(kAll_GrBackendState); } - // Scribble embedder provide render targets. The order in which we scribble - // into the buffers is irrelevant to the presentation order. - for (const auto& render_target : matched_render_targets) { - if (!pending_views_.at(render_target.first) - ->Render(*render_target.second)) { - FML_LOG(ERROR) - << "Could not render into the embedder supplied render target."; - return; - } - } + builder.Render(); // We are going to be transferring control back over to the embedder there the // context may be trampled upon again. Flush all operations to the underlying @@ -210,51 +409,16 @@ void EmbedderExternalViewEmbedder::SubmitFrame( context->flushAndSubmit(); } - // Submit the scribbled layer to the embedder for presentation. - // - // @warning: Embedder may trample on our OpenGL context here. { + // Submit the scribbled layer to the embedder for presentation. + // + // @warning: Embedder may trample on our OpenGL context here. EmbedderLayers presented_layers(pending_frame_size_, pending_device_pixel_ratio_, pending_surface_transformation_); - // In composition order, submit backing stores and platform views to the - // embedder. - for (const auto& view_id : composition_order_) { - // If the external view has a platform view, ask the emebdder to place it - // before the Flutter rendered contents for that interleaving level. - const auto& external_view = pending_views_.at(view_id); - if (external_view->HasPlatformView()) { - presented_layers.PushPlatformViewLayer( - // Covered by HasPlatformView(). - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - external_view->GetViewIdentifier() - .platform_view_id.value(), // view id - *external_view->GetEmbeddedViewParams() // view params - ); - } - // If the view has engine rendered contents, ask the embedder to place - // Flutter rendered contents for this interleaving level on top of a - // platform view. - if (external_view->HasEngineRenderedContents()) { - const auto& exteral_render_target = matched_render_targets.at(view_id); - const auto& external_view = pending_views_.at(view_id); - auto rect_list = - external_view->GetEngineRenderedContentsRegion(SkRect::MakeIWH( - pending_frame_size_.width(), pending_frame_size_.height())); - std::vector rects; - rects.reserve(rect_list.size()); - for (const auto& rect : rect_list) { - rects.push_back(rect.roundOut()); - } - presented_layers.PushBackingStoreLayer( - exteral_render_target->GetBackingStore(), rects); - } - } + builder.PushLayers(presented_layers); - // Flush the layer description down to the embedder for presentation. - // - // @warning: Embedder may trample on our OpenGL context here. presented_layers.InvokePresentCallback(present_callback_); } @@ -263,12 +427,10 @@ void EmbedderExternalViewEmbedder::SubmitFrame( // @warning: Embedder may trample on our OpenGL context here. deferred_cleanup_render_targets.clear(); - // Hold all rendered layers in the render target cache for one frame to - // see if they may be reused next frame. - for (auto& render_target : matched_render_targets) { + auto render_targets = builder.ClearAndCollectRenderTargets(); + for (auto& render_target : render_targets) { if (!avoid_backing_store_cache_) { - render_target_cache_.CacheRenderTarget(render_target.first, - std::move(render_target.second)); + render_target_cache_.CacheRenderTarget(std::move(render_target)); } } diff --git a/shell/platform/embedder/embedder_render_target_cache.cc b/shell/platform/embedder/embedder_render_target_cache.cc index 3d0e6b4c99858..bc92d8d0565e7 100644 --- a/shell/platform/embedder/embedder_render_target_cache.cc +++ b/shell/platform/embedder/embedder_render_target_cache.cc @@ -10,63 +10,41 @@ EmbedderRenderTargetCache::EmbedderRenderTargetCache() = default; EmbedderRenderTargetCache::~EmbedderRenderTargetCache() = default; -std::pair -EmbedderRenderTargetCache::GetExistingTargetsInCache( - const EmbedderExternalView::PendingViews& pending_views) { - RenderTargets resolved_render_targets; - EmbedderExternalView::ViewIdentifierSet unmatched_identifiers; - - for (const auto& view : pending_views) { - const auto& external_view = view.second; - if (!external_view->HasEngineRenderedContents()) { - continue; - } - auto& compatible_targets = - cached_render_targets_[external_view->CreateRenderTargetDescriptor()]; - if (compatible_targets.empty()) { - unmatched_identifiers.insert(view.first); - } else { - std::unique_ptr target = - std::move(compatible_targets.top()); - compatible_targets.pop(); - resolved_render_targets[view.first] = std::move(target); - } +std::unique_ptr +EmbedderRenderTargetCache::GetRenderTarget( + const EmbedderExternalView::RenderTargetDescriptor& descriptor) { + auto compatible_target = cached_render_targets_.find(descriptor); + if (compatible_target == cached_render_targets_.end()) { + return nullptr; + } else { + auto target = std::move(compatible_target->second); + cached_render_targets_.erase(compatible_target); + return target; } - return {std::move(resolved_render_targets), std::move(unmatched_identifiers)}; } std::set> EmbedderRenderTargetCache::ClearAllRenderTargetsInCache() { std::set> cleared_targets; for (auto& targets : cached_render_targets_) { - auto& targets_stack = targets.second; - while (!targets_stack.empty()) { - cleared_targets.emplace(std::move(targets_stack.top())); - targets_stack.pop(); - } + cleared_targets.insert(std::move(targets.second)); } cached_render_targets_.clear(); return cleared_targets; } void EmbedderRenderTargetCache::CacheRenderTarget( - EmbedderExternalView::ViewIdentifier view_identifier, std::unique_ptr target) { if (target == nullptr) { return; } auto desc = EmbedderExternalView::RenderTargetDescriptor{ - view_identifier, target->GetRenderTargetSize()}; - cached_render_targets_[desc].push(std::move(target)); + target->GetRenderTargetSize()}; + cached_render_targets_.insert(std::make_pair(desc, std::move(target))); } size_t EmbedderRenderTargetCache::GetCachedTargetsCount() const { - size_t count = 0; - for (const auto& targets : cached_render_targets_) { - count += targets.second.size(); - } - return count; + return cached_render_targets_.size(); } } // namespace flutter diff --git a/shell/platform/embedder/embedder_render_target_cache.h b/shell/platform/embedder/embedder_render_target_cache.h index 3c595da9bdb17..fb3251a5b9fdf 100644 --- a/shell/platform/embedder/embedder_render_target_cache.h +++ b/shell/platform/embedder/embedder_render_target_cache.h @@ -25,30 +25,22 @@ class EmbedderRenderTargetCache { ~EmbedderRenderTargetCache(); - using RenderTargets = - std::unordered_map, - EmbedderExternalView::ViewIdentifier::Hash, - EmbedderExternalView::ViewIdentifier::Equal>; - - std::pair - GetExistingTargetsInCache( - const EmbedderExternalView::PendingViews& pending_views); + std::unique_ptr GetRenderTarget( + const EmbedderExternalView::RenderTargetDescriptor& descriptor); std::set> ClearAllRenderTargetsInCache(); - void CacheRenderTarget(EmbedderExternalView::ViewIdentifier view_identifier, - std::unique_ptr target); + void CacheRenderTarget(std::unique_ptr target); size_t GetCachedTargetsCount() const; private: - using CachedRenderTargets = - std::unordered_map>, - EmbedderExternalView::RenderTargetDescriptor::Hash, - EmbedderExternalView::RenderTargetDescriptor::Equal>; + using CachedRenderTargets = std::unordered_multimap< + EmbedderExternalView::RenderTargetDescriptor, + std::unique_ptr, + EmbedderExternalView::RenderTargetDescriptor::Hash, + EmbedderExternalView::RenderTargetDescriptor::Equal>; CachedRenderTargets cached_render_targets_; diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc index 377ea7dced8ec..9e6fa5f6e5351 100644 --- a/shell/platform/embedder/tests/embedder_unittests.cc +++ b/shell/platform/embedder/tests/embedder_unittests.cc @@ -1297,7 +1297,7 @@ TEST_F(EmbedderTest, VerifyB143464703WithSoftwareBackend) { fml::CountDownLatch latch(1); context.GetCompositor().SetNextPresentCallback( [&](const FlutterLayer** layers, size_t layers_count) { - ASSERT_EQ(layers_count, 3u); + ASSERT_EQ(layers_count, 2u); // Layer 0 (Root) { @@ -1345,36 +1345,6 @@ TEST_F(EmbedderTest, VerifyB143464703WithSoftwareBackend) { ASSERT_EQ(*layers[1], layer); } - // Layer 2 - { - FlutterBackingStore backing_store = *layers[2]->backing_store; - backing_store.type = kFlutterBackingStoreTypeSoftware; - backing_store.did_update = true; - - FlutterRect paint_region_rects[] = { - FlutterRectMakeLTRB(135, 0, 1024, 60), - }; - FlutterRegion paint_region = { - .struct_size = sizeof(FlutterRegion), - .rects_count = 1, - .rects = paint_region_rects, - }; - FlutterBackingStorePresentInfo present_info = { - .struct_size = sizeof(FlutterBackingStorePresentInfo), - .paint_region = &paint_region, - }; - - FlutterLayer layer = {}; - layer.struct_size = sizeof(layer); - layer.type = kFlutterLayerContentTypeBackingStore; - layer.backing_store = &backing_store; - layer.size = FlutterSizeMake(1024.0, 600.0); - layer.offset = FlutterPointMake(0.0, 0.0); - layer.backing_store_present_info = &present_info; - - ASSERT_EQ(*layers[2], layer); - } - latch.CountDown(); }); From ca58a799f9debc423e92b58f4f3edf38181a5f7b Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Fri, 24 Nov 2023 14:18:24 +0100 Subject: [PATCH 2/5] Update golden image --- .../verifyb143464703_soft_noxform.png | Bin 14273 -> 14266 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/shell/platform/embedder/fixtures/verifyb143464703_soft_noxform.png b/shell/platform/embedder/fixtures/verifyb143464703_soft_noxform.png index e18fc3769b34271a586e5b66d908ffdfdea2d433..35f482b381d22f6d007107be0f93cd8928a962c1 100644 GIT binary patch literal 14266 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 literal 14273 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 From f098cf8e13e55c363330e27d81c4beac8713dba6 Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Fri, 24 Nov 2023 16:02:22 +0100 Subject: [PATCH 3/5] Add comments --- .../embedder_external_view_embedder.cc | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/shell/platform/embedder/embedder_external_view_embedder.cc b/shell/platform/embedder/embedder_external_view_embedder.cc index 1b6d04b2a3aa9..400b47480b5e4 100644 --- a/shell/platform/embedder/embedder_external_view_embedder.cc +++ b/shell/platform/embedder/embedder_external_view_embedder.cc @@ -126,6 +126,8 @@ namespace { struct PlatformView { EmbedderExternalView::ViewIdentifier view_identifier; const EmbeddedViewParams* params; + + // The frame of the platform view, after clipping, in screen coordinates. SkRect clipped_frame; explicit PlatformView(const EmbedderExternalView* view) { @@ -160,8 +162,15 @@ struct PlatformView { } }; +/// Each layer will result in a single physical surface that contains Flutter +/// contents. It may contain multiple platform views and the slices +/// that would be otherwise rendered between these platform views will be +/// collapsed into this layer, as long as it does not intersect any of the +/// platform views. class Layer { public: + /// Returns whether the rectangle intersects any of the platform views of + /// this layer. bool IntersectsPlatformView(const SkRect& rect) { for (auto& platform_view : platform_views_) { if (platform_view.clipped_frame.intersects(rect)) { @@ -171,6 +180,8 @@ class Layer { return false; } + /// Returns whether the region intersects any of the platform views of this + /// layer. bool IntersectsPlatformView(const DlRegion& region) { for (auto& platform_view : platform_views_) { if (region.intersects(platform_view.clipped_frame.roundOut())) { @@ -180,18 +191,24 @@ class Layer { return false; } + /// Returns whether the rectangle intersects any of the Flutter contents of + /// this layer. bool IntersectsFlutterContents(const SkRect& rect) { return flutter_contents_region_.intersects(rect.roundOut()); } + /// Returns whether the region intersects any of the Flutter contents of this + /// layer. bool IntersectsFlutterContents(const DlRegion& region) { return flutter_contents_region_.intersects(region); } + /// Adds a platform view to this layer. void AddPlatformView(const PlatformView& platform_view) { platform_views_.push_back(platform_view); } + /// Adds Flutter contents to this layer. void AddFlutterContents(EmbedderExternalView* contents, const DlRegion& contents_region) { flutter_contents_.push_back(contents); @@ -207,6 +224,8 @@ class Layer { render_target_ = std::move(target); } + /// Renders this layer Flutter contents to the render target previously + /// assigned with SetRenderTarget. void RenderFlutterContents() { FML_DCHECK(has_flutter_contents()); if (render_target_) { @@ -218,6 +237,8 @@ class Layer { } } + /// Returns platform views for this layer. In Z-order the platform views are + /// positioned *below* this layer's Flutter contents. const std::vector& platform_views() const { return platform_views_; } @@ -236,12 +257,24 @@ class Layer { std::unique_ptr render_target_; }; +/// A layout builder is responsible for building an optimized list of Layers +/// from a list of `EmbedderExternalView`s. Single EmbedderExternalView contains +/// at most one platform view and at most one layer of Flutter contents. +/// LayerBuilder is responsible for producing as few Layers from the list of +/// EmbedderExternalViews as possible while maintaining identical visual result. +/// +/// Implements https://flutter.dev/go/optimized-platform-view-layers class LayerBuilder { public: explicit LayerBuilder(SkISize frame_size) : frame_size_(frame_size) { layers_.push_back(Layer()); } + /// Adds the platform view and/or flutter contents from the + /// EmbedderExternalView instance. + /// + /// This will try to add the content and platform view to an existing layer + /// if possible. If not, a new layer will be created. void AddExternalView(EmbedderExternalView* view) { if (view->HasPlatformView()) { PlatformView platform_view(view); @@ -252,6 +285,7 @@ class LayerBuilder { } } + /// Prepares the render targets for all layers that have Flutter contents. void PrepareBackingStore( const std::function( FlutterBackingStoreConfig)>& target_provider) { @@ -263,6 +297,8 @@ class LayerBuilder { } } + /// Renders all layers with Flutter contents to their respective render + /// targets. void Render() { for (auto& layer : layers_) { if (layer.has_flutter_contents()) { @@ -271,6 +307,7 @@ class LayerBuilder { } } + /// Populates EmbedderLayers from layer builder's layers. void PushLayers(EmbedderLayers& layers) { for (auto& layer : layers_) { for (auto& view : layer.platform_views()) { @@ -286,6 +323,7 @@ class LayerBuilder { } } + /// Removes the render targets from layers and returns them for collection. std::vector> ClearAndCollectRenderTargets() { std::vector> result; @@ -311,8 +349,16 @@ class LayerBuilder { region); } + /// Returns the deepest layer to which the platform view can be added. That + /// would be (whichever comes first): + /// - First layer from back that has platform view that intersects with this + /// view + /// - Very last layer from back that has surface that doesn't intersect with + /// this. That is because layer content renders on top of the platform view. Layer& GetLayerForPlatformView(PlatformView view) { for (auto iter = layers_.rbegin(); iter != layers_.rend(); ++iter) { + // This layer has surface that intersects with this view. That means we + // went one too far and need the layer before this. if (iter->IntersectsFlutterContents(view.clipped_frame)) { if (iter == layers_.rbegin()) { layers_.emplace_back(); @@ -329,6 +375,8 @@ class LayerBuilder { return layers_.front(); } + /// Finds layer to which the Flutter content can be added. That would + /// be first layer from back that has any intersection with this region. Layer& GetLayerForFlutterContentsRegion(const DlRegion& region) { for (auto iter = layers_.rbegin(); iter != layers_.rend(); ++iter) { if (iter->IntersectsPlatformView(region) || @@ -345,7 +393,6 @@ class LayerBuilder { }; // namespace -// https://flutter.dev/go/optimized-platform-view-layers void EmbedderExternalViewEmbedder::SubmitFrame( GrDirectContext* context, const std::shared_ptr& aiks_context, From 3d3e80337d6a9137f6cdbdac450ed9932d9e75cc Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Fri, 24 Nov 2023 23:51:47 +0100 Subject: [PATCH 4/5] wording --- .../embedder/embedder_external_view_embedder.cc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/shell/platform/embedder/embedder_external_view_embedder.cc b/shell/platform/embedder/embedder_external_view_embedder.cc index 400b47480b5e4..c1c45057b7c92 100644 --- a/shell/platform/embedder/embedder_external_view_embedder.cc +++ b/shell/platform/embedder/embedder_external_view_embedder.cc @@ -165,8 +165,9 @@ struct PlatformView { /// Each layer will result in a single physical surface that contains Flutter /// contents. It may contain multiple platform views and the slices /// that would be otherwise rendered between these platform views will be -/// collapsed into this layer, as long as it does not intersect any of the +/// collapsed into this layer, as long as they do not intersect any of the /// platform views. +/// In Z order the Flutter contents of Layer is above the platform views. class Layer { public: /// Returns whether the rectangle intersects any of the platform views of @@ -259,9 +260,10 @@ class Layer { /// A layout builder is responsible for building an optimized list of Layers /// from a list of `EmbedderExternalView`s. Single EmbedderExternalView contains -/// at most one platform view and at most one layer of Flutter contents. -/// LayerBuilder is responsible for producing as few Layers from the list of -/// EmbedderExternalViews as possible while maintaining identical visual result. +/// at most one platform view and at most one layer of Flutter contents +/// ('slice'). LayerBuilder is responsible for producing as few Layers from the +/// list of EmbedderExternalViews as possible while maintaining identical visual +/// result. /// /// Implements https://flutter.dev/go/optimized-platform-view-layers class LayerBuilder { From 26b0c665531fb87da872ea8ef5bc5bc93dfe72f3 Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Mon, 27 Nov 2023 11:10:05 +0100 Subject: [PATCH 5/5] Nits --- .../embedder_external_view_embedder.cc | 49 ++++++++++++------- .../embedder/embedder_render_target_cache.cc | 7 ++- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/shell/platform/embedder/embedder_external_view_embedder.cc b/shell/platform/embedder/embedder_external_view_embedder.cc index c1c45057b7c92..f0462b19c2068 100644 --- a/shell/platform/embedder/embedder_external_view_embedder.cc +++ b/shell/platform/embedder/embedder_external_view_embedder.cc @@ -131,7 +131,7 @@ struct PlatformView { SkRect clipped_frame; explicit PlatformView(const EmbedderExternalView* view) { - assert(view->HasPlatformView()); + FML_DCHECK(view->HasPlatformView()); view_identifier = view->GetViewIdentifier(); params = view->GetEmbeddedViewParams(); @@ -140,23 +140,35 @@ struct PlatformView { for (auto i = params->mutatorsStack().Begin(); i != params->mutatorsStack().End(); ++i) { const auto& m = *i; - if (m->GetType() == MutatorType::kTransform) { - transform.preConcat(m->GetMatrix()); - } else if (m->GetType() == MutatorType::kClipRect) { - auto rect = transform.mapRect(m->GetRect()); - if (!clipped_frame.intersect(rect)) { - clipped_frame = SkRect::MakeEmpty(); + switch (m->GetType()) { + case kClipRect: { + auto rect = transform.mapRect(m->GetRect()); + if (!clipped_frame.intersect(rect)) { + clipped_frame = SkRect::MakeEmpty(); + } + break; } - } else if (m->GetType() == MutatorType::kClipRRect) { - auto rect = transform.mapRect(m->GetRRect().getBounds()); - if (!clipped_frame.intersect(rect)) { - clipped_frame = SkRect::MakeEmpty(); + case kClipRRect: { + auto rect = transform.mapRect(m->GetRRect().getBounds()); + if (!clipped_frame.intersect(rect)) { + clipped_frame = SkRect::MakeEmpty(); + } + break; } - } else if (m->GetType() == MutatorType::kClipPath) { - auto rect = transform.mapRect(m->GetPath().getBounds()); - if (!clipped_frame.intersect(rect)) { - clipped_frame = SkRect::MakeEmpty(); + case kClipPath: { + auto rect = transform.mapRect(m->GetPath().getBounds()); + if (!clipped_frame.intersect(rect)) { + clipped_frame = SkRect::MakeEmpty(); + } + break; } + case kTransform: { + transform.preConcat(m->GetMatrix()); + break; + } + case kOpacity: + case kBackdropFilter: + break; } } } @@ -254,8 +266,8 @@ class Layer { std::vector platform_views_; std::vector flutter_contents_; DlRegion flutter_contents_region_; - friend class LayerBuilder; std::unique_ptr render_target_; + friend class LayerBuilder; }; /// A layout builder is responsible for building an optimized list of Layers @@ -344,7 +356,7 @@ class LayerBuilder { } void AddFlutterContents(EmbedderExternalView* contents) { - assert(contents->HasEngineRenderedContents()); + FML_DCHECK(contents->HasEngineRenderedContents()); DlRegion region = contents->GetDlRegion(); GetLayerForFlutterContentsRegion(region).AddFlutterContents(contents, @@ -420,9 +432,8 @@ void EmbedderExternalViewEmbedder::SubmitFrame( } if (target != nullptr) { return target; - } else { - return create_render_target_callback_(context, aiks_context, config); } + return create_render_target_callback_(context, aiks_context, config); }); // This is where unused render targets will be collected. Control may flow to diff --git a/shell/platform/embedder/embedder_render_target_cache.cc b/shell/platform/embedder/embedder_render_target_cache.cc index bc92d8d0565e7..ac0da63959c2f 100644 --- a/shell/platform/embedder/embedder_render_target_cache.cc +++ b/shell/platform/embedder/embedder_render_target_cache.cc @@ -16,11 +16,10 @@ EmbedderRenderTargetCache::GetRenderTarget( auto compatible_target = cached_render_targets_.find(descriptor); if (compatible_target == cached_render_targets_.end()) { return nullptr; - } else { - auto target = std::move(compatible_target->second); - cached_render_targets_.erase(compatible_target); - return target; } + auto target = std::move(compatible_target->second); + cached_render_targets_.erase(compatible_target); + return target; } std::set>