| // 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 "flutter/shell/platform/embedder/embedder_external_view_embedder.h" | |
| #include <cassert> | |
| #include <utility> | |
| #include "flutter/common/constants.h" | |
| #include "flutter/shell/platform/embedder/embedder_layers.h" | |
| #include "flutter/shell/platform/embedder/embedder_render_target.h" | |
| #include "third_party/skia/include/gpu/GrDirectContext.h" | |
| namespace flutter { | |
| static const auto kRootViewIdentifier = EmbedderExternalView::ViewIdentifier{}; | |
| EmbedderExternalViewEmbedder::EmbedderExternalViewEmbedder( | |
| bool avoid_backing_store_cache, | |
| const CreateRenderTargetCallback& create_render_target_callback, | |
| const PresentCallback& present_callback) | |
| : avoid_backing_store_cache_(avoid_backing_store_cache), | |
| create_render_target_callback_(create_render_target_callback), | |
| present_callback_(present_callback) { | |
| FML_DCHECK(create_render_target_callback_); | |
| FML_DCHECK(present_callback_); | |
| } | |
| EmbedderExternalViewEmbedder::~EmbedderExternalViewEmbedder() = default; | |
| void EmbedderExternalViewEmbedder::SetSurfaceTransformationCallback( | |
| SurfaceTransformationCallback surface_transformation_callback) { | |
| surface_transformation_callback_ = std::move(surface_transformation_callback); | |
| } | |
| SkMatrix EmbedderExternalViewEmbedder::GetSurfaceTransformation() const { | |
| if (!surface_transformation_callback_) { | |
| return SkMatrix{}; | |
| } | |
| return surface_transformation_callback_(); | |
| } | |
| void EmbedderExternalViewEmbedder::Reset() { | |
| pending_views_.clear(); | |
| composition_order_.clear(); | |
| } | |
| // |ExternalViewEmbedder| | |
| void EmbedderExternalViewEmbedder::CancelFrame() { | |
| Reset(); | |
| } | |
| // |ExternalViewEmbedder| | |
| void EmbedderExternalViewEmbedder::BeginFrame( | |
| GrDirectContext* context, | |
| const fml::RefPtr<fml::RasterThreadMerger>& raster_thread_merger) {} | |
| // |ExternalViewEmbedder| | |
| void EmbedderExternalViewEmbedder::PrepareFlutterView( | |
| int64_t flutter_view_id, | |
| SkISize frame_size, | |
| double device_pixel_ratio) { | |
| // TODO(dkwingsmt): This class only supports rendering into the implicit | |
| // view. Properly support multi-view in the future. | |
| // https://github.com/flutter/flutter/issues/135530 item 4 | |
| FML_DCHECK(flutter_view_id == kFlutterImplicitViewId); | |
| Reset(); | |
| pending_frame_size_ = frame_size; | |
| pending_device_pixel_ratio_ = device_pixel_ratio; | |
| pending_surface_transformation_ = GetSurfaceTransformation(); | |
| pending_views_[kRootViewIdentifier] = std::make_unique<EmbedderExternalView>( | |
| pending_frame_size_, pending_surface_transformation_); | |
| composition_order_.push_back(kRootViewIdentifier); | |
| } | |
| // |ExternalViewEmbedder| | |
| void EmbedderExternalViewEmbedder::PrerollCompositeEmbeddedView( | |
| int64_t view_id, | |
| std::unique_ptr<EmbeddedViewParams> params) { | |
| auto vid = EmbedderExternalView::ViewIdentifier(view_id); | |
| FML_DCHECK(pending_views_.count(vid) == 0); | |
| pending_views_[vid] = std::make_unique<EmbedderExternalView>( | |
| pending_frame_size_, // frame size | |
| pending_surface_transformation_, // surface xformation | |
| vid, // view identifier | |
| std::move(params) // embedded view params | |
| ); | |
| composition_order_.push_back(vid); | |
| } | |
| // |ExternalViewEmbedder| | |
| DlCanvas* EmbedderExternalViewEmbedder::GetRootCanvas() { | |
| 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 " | |
| "indicates that the external view embedder did not receive the " | |
| "notification to begin the frame."; | |
| return nullptr; | |
| } | |
| return found->second->GetCanvas(); | |
| } | |
| // |ExternalViewEmbedder| | |
| DlCanvas* EmbedderExternalViewEmbedder::CompositeEmbeddedView(int64_t view_id) { | |
| auto vid = EmbedderExternalView::ViewIdentifier(view_id); | |
| auto found = pending_views_.find(vid); | |
| if (found == pending_views_.end()) { | |
| FML_DCHECK(false) << "Attempted to composite a view that was not " | |
| "pre-rolled."; | |
| return nullptr; | |
| } | |
| return found->second->GetCanvas(); | |
| } | |
| static FlutterBackingStoreConfig MakeBackingStoreConfig( | |
| const SkISize& backing_store_size) { | |
| FlutterBackingStoreConfig config = {}; | |
| config.struct_size = sizeof(config); | |
| config.size.width = backing_store_size.width(); | |
| config.size.height = backing_store_size.height(); | |
| return config; | |
| } | |
| 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) { | |
| FML_DCHECK(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; | |
| switch (m->GetType()) { | |
| case kClipRect: { | |
| auto rect = transform.mapRect(m->GetRect()); | |
| if (!clipped_frame.intersect(rect)) { | |
| clipped_frame = SkRect::MakeEmpty(); | |
| } | |
| break; | |
| } | |
| case kClipRRect: { | |
| auto rect = transform.mapRect(m->GetRRect().getBounds()); | |
| if (!clipped_frame.intersect(rect)) { | |
| clipped_frame = SkRect::MakeEmpty(); | |
| } | |
| break; | |
| } | |
| 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; | |
| } | |
| } | |
| } | |
| }; | |
| /// 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 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 | |
| /// this layer. | |
| bool IntersectsPlatformView(const SkRect& rect) { | |
| for (auto& platform_view : platform_views_) { | |
| if (platform_view.clipped_frame.intersects(rect)) { | |
| return true; | |
| } | |
| } | |
| 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())) { | |
| return true; | |
| } | |
| } | |
| 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); | |
| flutter_contents_region_ = | |
| DlRegion::MakeUnion(flutter_contents_region_, contents_region); | |
| } | |
| bool has_flutter_contents() const { return !flutter_contents_.empty(); } | |
| void SetRenderTarget(std::unique_ptr<EmbedderRenderTarget> target) { | |
| FML_DCHECK(render_target_ == nullptr); | |
| FML_DCHECK(has_flutter_contents()); | |
| 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_) { | |
| bool clear_surface = true; | |
| for (auto c : flutter_contents_) { | |
| c->Render(*render_target_, clear_surface); | |
| clear_surface = false; | |
| } | |
| } | |
| } | |
| /// Returns platform views for this layer. In Z-order the platform views are | |
| /// positioned *below* this layer's Flutter contents. | |
| const std::vector<PlatformView>& platform_views() const { | |
| return platform_views_; | |
| } | |
| EmbedderRenderTarget* render_target() { return render_target_.get(); } | |
| std::vector<SkIRect> coverage() { | |
| return flutter_contents_region_.getRects(); | |
| } | |
| private: | |
| std::vector<PlatformView> platform_views_; | |
| std::vector<EmbedderExternalView*> flutter_contents_; | |
| DlRegion flutter_contents_region_; | |
| std::unique_ptr<EmbedderRenderTarget> render_target_; | |
| friend class LayerBuilder; | |
| }; | |
| /// 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 | |
| /// ('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 { | |
| 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); | |
| AddPlatformView(platform_view); | |
| } | |
| if (view->HasEngineRenderedContents()) { | |
| AddFlutterContents(view); | |
| } | |
| } | |
| /// Prepares the render targets for all layers that have Flutter contents. | |
| void PrepareBackingStore( | |
| const std::function<std::unique_ptr<EmbedderRenderTarget>( | |
| FlutterBackingStoreConfig)>& target_provider) { | |
| auto config = MakeBackingStoreConfig(frame_size_); | |
| for (auto& layer : layers_) { | |
| if (layer.has_flutter_contents()) { | |
| layer.SetRenderTarget(target_provider(config)); | |
| } | |
| } | |
| } | |
| /// Renders all layers with Flutter contents to their respective render | |
| /// targets. | |
| void Render() { | |
| for (auto& layer : layers_) { | |
| if (layer.has_flutter_contents()) { | |
| layer.RenderFlutterContents(); | |
| } | |
| } | |
| } | |
| /// Populates EmbedderLayers from layer builder's layers. | |
| 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()); | |
| } | |
| } | |
| } | |
| /// Removes the render targets from layers and returns them for collection. | |
| std::vector<std::unique_ptr<EmbedderRenderTarget>> | |
| ClearAndCollectRenderTargets() { | |
| std::vector<std::unique_ptr<EmbedderRenderTarget>> 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) { | |
| FML_DCHECK(contents->HasEngineRenderedContents()); | |
| DlRegion region = contents->GetDlRegion(); | |
| GetLayerForFlutterContentsRegion(region).AddFlutterContents(contents, | |
| 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(); | |
| return layers_.back(); | |
| } else { | |
| --iter; | |
| return *iter; | |
| } | |
| } | |
| if (iter->IntersectsPlatformView(view.clipped_frame)) { | |
| return *iter; | |
| } | |
| } | |
| 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) || | |
| iter->IntersectsFlutterContents(region)) { | |
| return *iter; | |
| } | |
| } | |
| return layers_.front(); | |
| } | |
| std::vector<Layer> layers_; | |
| SkISize frame_size_; | |
| }; | |
| }; // namespace | |
| void EmbedderExternalViewEmbedder::SubmitFlutterView( | |
| GrDirectContext* context, | |
| const std::shared_ptr<impeller::AiksContext>& aiks_context, | |
| std::unique_ptr<SurfaceFrame> frame) { | |
| 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<EmbedderRenderTarget> target; | |
| if (!avoid_backing_store_cache_) { | |
| target = render_target_cache_.GetRenderTarget( | |
| EmbedderExternalView::RenderTargetDescriptor( | |
| SkISize{static_cast<int32_t>(config.size.width), | |
| static_cast<int32_t>(config.size.height)})); | |
| } | |
| if (target != nullptr) { | |
| return target; | |
| } | |
| 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 | |
| // OpenGL context. | |
| // | |
| // For optimum performance, we should tell the render target cache to clear | |
| // its unused entries before allocating new ones. This collection step | |
| // before allocating new render targets ameliorates peak memory usage within | |
| // the frame. But, this causes an issue in a known internal embedder. To | |
| // work around this issue while that embedder migrates, collection of render | |
| // targets is deferred after the presentation. | |
| // | |
| // @warning: Embedder may trample on our OpenGL context here. | |
| auto deferred_cleanup_render_targets = | |
| render_target_cache_.ClearAllRenderTargetsInCache(); | |
| // 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. | |
| if (context) { | |
| context->resetContext(kAll_GrBackendState); | |
| } | |
| 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 rendering API. | |
| // | |
| // @warning: Embedder may trample on our OpenGL context here. | |
| if (context) { | |
| context->flushAndSubmit(); | |
| } | |
| { | |
| auto presentation_time_optional = frame->submit_info().presentation_time; | |
| uint64_t presentation_time = | |
| presentation_time_optional.has_value() | |
| ? presentation_time_optional->ToEpochDelta().ToNanoseconds() | |
| : 0; | |
| // 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_, presentation_time); | |
| builder.PushLayers(presented_layers); | |
| // TODO(loic-sharma): Currently only supports a single view. | |
| // See https://github.com/flutter/flutter/issues/135530. | |
| presented_layers.InvokePresentCallback(kFlutterImplicitViewId, | |
| present_callback_); | |
| } | |
| // See why this is necessary in the comment where this collection in | |
| // realized. | |
| // | |
| // @warning: Embedder may trample on our OpenGL context here. | |
| deferred_cleanup_render_targets.clear(); | |
| auto render_targets = builder.ClearAndCollectRenderTargets(); | |
| for (auto& render_target : render_targets) { | |
| if (!avoid_backing_store_cache_) { | |
| render_target_cache_.CacheRenderTarget(std::move(render_target)); | |
| } | |
| } | |
| frame->Submit(); | |
| } | |
| } // namespace flutter |