in shell/platform/fuchsia/flutter/gfx_external_view_embedder.cc [239:571]
void GfxExternalViewEmbedder::SubmitFrame(
GrDirectContext* context,
std::unique_ptr<flutter::SurfaceFrame> frame) {
TRACE_EVENT0("flutter", "GfxExternalViewEmbedder::SubmitFrame");
std::vector<std::unique_ptr<SurfaceProducerSurface>> frame_surfaces;
std::unordered_map<EmbedderLayerId, size_t> frame_surface_indices;
// Create surfaces for the frame and associate them with layer IDs.
{
TRACE_EVENT0("flutter", "CreateSurfaces");
for (const auto& layer : frame_layers_) {
if (!layer.second.canvas_spy->DidDrawIntoCanvas()) {
continue;
}
auto surface =
surface_producer_->ProduceSurface(layer.second.surface_size);
if (!surface) {
const std::string layer_id_str =
layer.first.has_value() ? std::to_string(layer.first.value())
: "Background";
FML_LOG(ERROR) << "Failed to create surface for layer " << layer_id_str
<< "; size (" << layer.second.surface_size.width()
<< ", " << layer.second.surface_size.height() << ")";
FML_DCHECK(false);
continue;
}
frame_surface_indices.emplace(
std::make_pair(layer.first, frame_surfaces.size()));
frame_surfaces.emplace_back(std::move(surface));
}
}
// Submit layers and platform views to Scenic in composition order.
{
TRACE_EVENT0("flutter", "SubmitLayers");
std::unordered_map<uint64_t, size_t> scenic_rect_indices;
size_t scenic_layer_index = 0;
float embedded_views_height = 0.0f;
// First re-scale everything according to the DPR.
const float inv_dpr = 1.0f / frame_dpr_;
layer_tree_node_.SetScale(inv_dpr, inv_dpr, 1.0f);
bool first_layer = true;
for (const auto& layer_id : frame_composition_order_) {
const auto& layer = frame_layers_.find(layer_id);
FML_CHECK(layer != frame_layers_.end());
// Draw the PlatformView associated with each layer first.
if (layer_id.has_value()) {
FML_CHECK(layer->second.embedded_view_params.has_value());
auto& view_params = layer->second.embedded_view_params.value();
// Get the ScenicView structure corresponding to the platform view.
auto found = scenic_views_.find(layer_id.value());
FML_CHECK(found != scenic_views_.end());
auto& view_holder = found->second;
// Compute mutators, size, and elevation for the platform view.
const ViewMutators view_mutators =
ParseMutatorStack(view_params.mutatorsStack());
const SkSize view_size = view_params.sizePoints();
const float view_elevation =
kScenicZElevationBetweenLayers * scenic_layer_index +
embedded_views_height;
FML_CHECK(view_mutators.total_transform ==
view_params.transformMatrix());
// Set clips for the platform view.
if (view_mutators.clips != view_holder.mutators.clips) {
// Expand the clip_nodes array to fit any new nodes.
while (view_holder.clip_nodes.size() < view_mutators.clips.size()) {
view_holder.clip_nodes.emplace_back(
scenic::EntityNode(session_->get()));
}
FML_CHECK(view_holder.clip_nodes.size() >=
view_mutators.clips.size());
// Adjust and re-parent all clip rects.
for (auto& clip_node : view_holder.clip_nodes) {
clip_node.DetachChildren();
}
for (size_t c = 0; c < view_mutators.clips.size(); c++) {
const SkMatrix& clip_transform = view_mutators.clips[c].transform;
const SkRect& clip_rect = view_mutators.clips[c].rect;
view_holder.clip_nodes[c].SetTranslation(
clip_transform.getTranslateX(), clip_transform.getTranslateY(),
0.f);
view_holder.clip_nodes[c].SetScale(clip_transform.getScaleX(),
clip_transform.getScaleY(), 1.f);
view_holder.clip_nodes[c].SetClipPlanes(
ClipPlanesFromRect(clip_rect));
if (c != (view_mutators.clips.size() - 1)) {
view_holder.clip_nodes[c].AddChild(view_holder.clip_nodes[c + 1]);
} else {
view_holder.clip_nodes[c].AddChild(view_holder.opacity_node);
}
}
view_holder.mutators.clips = view_mutators.clips;
}
// Set transform and elevation for the platform view.
if (view_mutators.transform != view_holder.mutators.transform ||
view_elevation != view_holder.elevation) {
view_holder.transform_node.SetTranslation(
view_mutators.transform.getTranslateX(),
view_mutators.transform.getTranslateY(), -view_elevation);
view_holder.transform_node.SetScale(
view_mutators.transform.getScaleX(),
view_mutators.transform.getScaleY(), 1.f);
view_holder.mutators.transform = view_mutators.transform;
view_holder.elevation = view_elevation;
}
// Set HitTestBehavior for the platform view.
if (view_holder.pending_hit_testable != view_holder.hit_testable) {
view_holder.transform_node.SetHitTestBehavior(
view_holder.pending_hit_testable
? fuchsia::ui::gfx::HitTestBehavior::kDefault
: fuchsia::ui::gfx::HitTestBehavior::kSuppress);
view_holder.hit_testable = view_holder.pending_hit_testable;
}
// Set opacity for the platform view.
if (view_mutators.opacity != view_holder.mutators.opacity) {
view_holder.opacity_node.SetOpacity(view_mutators.opacity);
view_holder.mutators.opacity = view_mutators.opacity;
}
// Set size, occlusion hint, and focusable.
if (view_size != view_holder.size ||
view_holder.pending_occlusion_hint != view_holder.occlusion_hint ||
view_holder.pending_focusable != view_holder.focusable) {
view_holder.view_holder.SetViewProperties({
.bounding_box =
{
.min = {.x = 0.f, .y = 0.f, .z = -1000.f},
.max = {.x = view_size.fWidth,
.y = view_size.fHeight,
.z = 0.f},
},
.inset_from_min = {.x = view_holder.pending_occlusion_hint.fLeft,
.y = view_holder.pending_occlusion_hint.fTop,
.z = 0.f},
.inset_from_max = {.x = view_holder.pending_occlusion_hint.fRight,
.y =
view_holder.pending_occlusion_hint.fBottom,
.z = 0.f},
.focus_change = view_holder.pending_focusable,
});
view_holder.size = view_size;
view_holder.occlusion_hint = view_holder.pending_occlusion_hint;
view_holder.focusable = view_holder.pending_focusable;
}
// Attach the ScenicView to the main scene graph.
if (view_holder.mutators.clips.empty()) {
layer_tree_node_.AddChild(view_holder.opacity_node);
} else {
layer_tree_node_.AddChild(view_holder.clip_nodes[0]);
}
// Account for the ScenicView's height when positioning the next layer.
embedded_views_height += kScenicZElevationForPlatformView;
}
// Acquire the surface associated with the layer.
SurfaceProducerSurface* surface_for_layer = nullptr;
if (layer->second.canvas_spy->DidDrawIntoCanvas()) {
const auto& surface_index = frame_surface_indices.find(layer_id);
if (surface_index != frame_surface_indices.end()) {
FML_CHECK(surface_index->second < frame_surfaces.size());
surface_for_layer = frame_surfaces[surface_index->second].get();
FML_CHECK(surface_for_layer != nullptr);
} else {
const std::string layer_id_str =
layer_id.has_value() ? std::to_string(layer_id.value())
: "Background";
FML_LOG(ERROR) << "Missing surface for layer " << layer_id_str
<< "; skipping scene graph add of layer.";
FML_DCHECK(false);
}
}
// Draw the layer if we acquired a surface for it successfully.
if (surface_for_layer != nullptr) {
// Create a new layer if needed for the surface.
FML_CHECK(scenic_layer_index <= scenic_layers_.size());
if (scenic_layer_index == scenic_layers_.size()) {
ScenicLayer new_layer{
.shape_node = scenic::ShapeNode(session_->get()),
.material = scenic::Material(session_->get()),
};
new_layer.shape_node.SetMaterial(new_layer.material);
scenic_layers_.emplace_back(std::move(new_layer));
}
// Compute a hash and index for the rect.
const uint64_t rect_hash =
(static_cast<uint64_t>(layer->second.surface_size.width()) << 32) +
layer->second.surface_size.height();
size_t rect_index = 0;
auto found_index = scenic_rect_indices.find(rect_hash);
if (found_index == scenic_rect_indices.end()) {
scenic_rect_indices.emplace(std::make_pair(rect_hash, 0));
} else {
rect_index = found_index->second + 1;
scenic_rect_indices[rect_hash] = rect_index;
}
// Create a new rect if needed for the surface.
auto found_rects = scenic_rects_.find(rect_hash);
if (found_rects == scenic_rects_.end()) {
auto [emplaced_rects, success] = scenic_rects_.emplace(
std::make_pair(rect_hash, std::vector<scenic::Rectangle>()));
FML_CHECK(success);
found_rects = std::move(emplaced_rects);
}
FML_CHECK(rect_index <= found_rects->second.size());
if (rect_index == found_rects->second.size()) {
found_rects->second.emplace_back(scenic::Rectangle(
session_->get(), layer->second.surface_size.width(),
layer->second.surface_size.height()));
}
// Set layer shape and texture.
// Scenic currently lacks an API to enable rendering of alpha channel;
// Flutter Embedder also lacks an API to detect if a layer has alpha or
// not. Alpha channels are only rendered if there is a OpacityNode
// higher in the tree with opacity != 1. For now, assume any layer
// beyond the first has alpha and clamp to a infinitesimally smaller
// value than 1. The first layer retains an opacity of 1 to avoid
// blending with anything underneath.
//
// This does not cause visual problems in practice, but probably has
// performance implications.
const SkAlpha layer_opacity =
first_layer ? kBackgroundLayerOpacity : kOverlayLayerOpacity;
const float layer_elevation =
kScenicZElevationBetweenLayers * scenic_layer_index +
embedded_views_height;
auto& scenic_layer = scenic_layers_[scenic_layer_index];
auto& scenic_rect = found_rects->second[rect_index];
scenic_layer.shape_node.SetLabel("Flutter::Layer");
scenic_layer.shape_node.SetShape(scenic_rect);
scenic_layer.shape_node.SetTranslation(
layer->second.surface_size.width() * 0.5f,
layer->second.surface_size.height() * 0.5f, -layer_elevation);
scenic_layer.material.SetColor(SK_AlphaOPAQUE, SK_AlphaOPAQUE,
SK_AlphaOPAQUE, layer_opacity);
scenic_layer.material.SetTexture(surface_for_layer->GetImageId());
// Only the first (i.e. the bottom-most) layer should receive input.
// TODO: Workaround for invisible overlays stealing input. Remove when
// the underlying bug is fixed.
const fuchsia::ui::gfx::HitTestBehavior layer_hit_test_behavior =
first_layer ? fuchsia::ui::gfx::HitTestBehavior::kDefault
: fuchsia::ui::gfx::HitTestBehavior::kSuppress;
scenic_layer.shape_node.SetHitTestBehavior(layer_hit_test_behavior);
// Attach the ScenicLayer to the main scene graph.
layer_tree_node_.AddChild(scenic_layer.shape_node);
}
// Reset for the next pass:
// +The next layer will not be the first layer.
// +Account for the current layer's height when positioning the next.
first_layer = false;
scenic_layer_index++;
}
}
// Present the session to Scenic, along with surface acquire/release fencess.
{
TRACE_EVENT0("flutter", "SessionPresent");
session_->Present();
}
// Render the recorded SkPictures into the surfaces.
{
TRACE_EVENT0("flutter", "RasterizeSurfaces");
for (const auto& surface_index : frame_surface_indices) {
TRACE_EVENT0("flutter", "RasterizeSurface");
FML_CHECK(surface_index.second < frame_surfaces.size());
SurfaceProducerSurface* surface =
frame_surfaces[surface_index.second].get();
FML_CHECK(surface != nullptr);
sk_sp<SkSurface> sk_surface = surface->GetSkiaSurface();
FML_CHECK(sk_surface != nullptr);
FML_CHECK(SkISize::Make(sk_surface->width(), sk_surface->height()) ==
frame_size_);
SkCanvas* canvas = sk_surface->getCanvas();
FML_CHECK(canvas != nullptr);
const auto& layer = frame_layers_.find(surface_index.first);
FML_CHECK(layer != frame_layers_.end());
sk_sp<SkPicture> picture =
layer->second.recorder->finishRecordingAsPicture();
FML_CHECK(picture != nullptr);
canvas->setMatrix(SkMatrix::I());
canvas->clear(SK_ColorTRANSPARENT);
canvas->drawPicture(picture);
canvas->flush();
}
}
// Flush deferred Skia work and inform Scenic that render targets are ready.
{
TRACE_EVENT0("flutter", "PresentSurfaces");
surface_producer_->SubmitSurfaces(std::move(frame_surfaces));
}
// Submit the underlying render-backend-specific frame for processing.
frame->Submit();
}