blob: a2e08644047a70dd2fb400cb7f95e216a0d199bd [file] [log] [blame]
// 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/GrContext.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,
GrContext* context,
double device_pixel_ratio) {
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) {
FML_DCHECK(pending_views_.count(view_id) == 0);
pending_views_[view_id] = std::make_unique<EmbedderExternalView>(
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<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|
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|
bool EmbedderExternalViewEmbedder::SubmitFrame(GrContext* context,
SkCanvas* background_canvas) {
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 false;
}
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 false;
}
}
// 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));
}
return true;
}
// |ExternalViewEmbedder|
void EmbedderExternalViewEmbedder::FinishFrame() {}
} // namespace flutter