// 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 #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( const CreateRenderTargetCallback& create_render_target_callback, const PresentCallback& present_callback) : 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 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( pending_frame_size_, pending_surface_transformation_); composition_order_.push_back(kRootViewIdentifier); } // |ExternalViewEmbedder| void EmbedderExternalViewEmbedder::PrerollCompositeEmbeddedView( int view_id, std::unique_ptr params) { FML_DCHECK(pending_views_.count(view_id) == 0); pending_views_[view_id] = std::make_unique( pending_frame_size_, // frame size pending_surface_transformation_, // surface xformation view_id, // view identifier std::move(params) // embedded view params ); composition_order_.push_back(view_id); } // |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 EmbedderExternalViewEmbedder::GetCurrentCanvases() { std::vector 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| SkCanvas* EmbedderExternalViewEmbedder::CompositeEmbeddedView(int view_id) { auto found = pending_views_.find(view_id); 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; } // |ExternalViewEmbedder| void EmbedderExternalViewEmbedder::SubmitFrame( GrDirectContext* context, std::unique_ptr 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) { render_target_cache_.CacheRenderTarget(render_target.first, std::move(render_target.second)); } frame->Submit(); } } // namespace flutter