| // 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 <algorithm> |
| |
| #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 { |
| |
| 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_ = 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( |
| SkISize frame_size, |
| GrDirectContext* context, |
| double device_pixel_ratio, |
| fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) { |
| Reset(); |
| |
| pending_frame_size_ = frame_size; |
| pending_device_pixel_ratio_ = device_pixel_ratio; |
| pending_surface_transformation_ = GetSurfaceTransformation(); |
| |
| static const auto kRootViewIdentifier = |
| EmbedderExternalView::ViewIdentifier{}; |
| |
| pending_views_[kRootViewIdentifier] = std::make_unique<EmbedderExternalView>( |
| pending_frame_size_, pending_surface_transformation_); |
| composition_order_.push_back(kRootViewIdentifier); |
| } |
| |
| // |ExternalViewEmbedder| |
| void EmbedderExternalViewEmbedder::PrerollCompositeEmbeddedView( |
| int 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| |
| SkCanvas* EmbedderExternalViewEmbedder::GetRootCanvas() { |
| auto found = pending_views_.find(EmbedderExternalView::ViewIdentifier{}); |
| 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| |
| std::vector<SkCanvas*> EmbedderExternalViewEmbedder::GetCurrentCanvases() { |
| std::vector<SkCanvas*> canvases; |
| for (const auto& view : pending_views_) { |
| const auto& external_view = view.second; |
| // This method (for legacy reasons) expects non-root current canvases. |
| if (!external_view->IsRootView()) { |
| canvases.push_back(external_view->GetCanvas()); |
| } |
| } |
| return canvases; |
| } |
| |
| // |ExternalViewEmbedder| |
| EmbedderPaintContext EmbedderExternalViewEmbedder::CompositeEmbeddedView( |
| int 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, nullptr}; |
| } |
| return {found->second->GetCanvas(), nullptr}; |
| } |
| |
| 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; |
| } |
| |
| // |ExternalViewEmbedder| |
| void EmbedderExternalViewEmbedder::SubmitFrame( |
| GrDirectContext* context, |
| std::unique_ptr<SurfaceFrame> frame) { |
| auto [matched_render_targets, pending_keys] = |
| render_target_cache_.GetExistingTargetsInCache(pending_views_); |
| |
| // 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(); |
| |
| 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, 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. |
| if (context) { |
| 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; |
| } |
| } |
| |
| // 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(); |
| } |
| |
| // 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( |
| 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); |
| presented_layers.PushBackingStoreLayer( |
| exteral_render_target->GetBackingStore()); |
| } |
| } |
| |
| // Flush the layer description down to the embedder for presentation. |
| // |
| // @warning: Embedder may trample on our OpenGL context here. |
| presented_layers.InvokePresentCallback(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(); |
| |
| // 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) { |
| if (!avoid_backing_store_cache_) { |
| render_target_cache_.CacheRenderTarget(render_target.first, |
| std::move(render_target.second)); |
| } |
| } |
| |
| frame->Submit(); |
| } |
| |
| } // namespace flutter |