blob: 6f0e4fa8ff330772e15374429bc57b69f541ba77 [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/flow/scene_update_context.h"
#include <lib/ui/scenic/cpp/commands.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include "flutter/flow/layers/layer.h"
#include "flutter/flow/matrix_decomposition.h"
#include "flutter/flow/view_holder.h"
#include "flutter/fml/trace_event.h"
#include "include/core/SkColor.h"
namespace flutter {
namespace {
void SetEntityNodeClipPlanes(scenic::EntityNode& entity_node,
const SkRect& bounds) {
const float top = bounds.top();
const float bottom = bounds.bottom();
const float left = bounds.left();
const float right = bounds.right();
// We will generate 4 oriented planes, one for each edge of the bounding rect.
std::vector<fuchsia::ui::gfx::Plane3> clip_planes;
clip_planes.resize(4);
// Top plane.
clip_planes[0].dist = top;
clip_planes[0].dir.x = 0.f;
clip_planes[0].dir.y = 1.f;
clip_planes[0].dir.z = 0.f;
// Bottom plane.
clip_planes[1].dist = -bottom;
clip_planes[1].dir.x = 0.f;
clip_planes[1].dir.y = -1.f;
clip_planes[1].dir.z = 0.f;
// Left plane.
clip_planes[2].dist = left;
clip_planes[2].dir.x = 1.f;
clip_planes[2].dir.y = 0.f;
clip_planes[2].dir.z = 0.f;
// Right plane.
clip_planes[3].dist = -right;
clip_planes[3].dir.x = -1.f;
clip_planes[3].dir.y = 0.f;
clip_planes[3].dir.z = 0.f;
entity_node.SetClipPlanes(std::move(clip_planes));
}
void SetMaterialColor(scenic::Material& material,
SkColor color,
SkAlpha opacity) {
const SkAlpha color_alpha = static_cast<SkAlpha>(
((float)SkColorGetA(color) * (float)opacity) / 255.0f);
material.SetColor(SkColorGetR(color), SkColorGetG(color), SkColorGetB(color),
color_alpha);
}
} // namespace
SceneUpdateContext::SceneUpdateContext(std::string debug_label,
fuchsia::ui::views::ViewToken view_token,
scenic::ViewRefPair view_ref_pair,
SessionWrapper& session,
bool intercept_all_input)
: session_(session),
root_view_(session_.get(),
std::move(view_token),
std::move(view_ref_pair.control_ref),
std::move(view_ref_pair.view_ref),
debug_label),
metrics_node_(session.get()),
layer_tree_node_(session_.get()) {
layer_tree_node_.SetLabel("Flutter::LayerTree");
metrics_node_.SetLabel("Flutter::MetricsWatcher");
metrics_node_.SetEventMask(fuchsia::ui::gfx::kMetricsEventMask);
metrics_node_.AddChild(layer_tree_node_);
root_view_.AddChild(metrics_node_);
// Set up the input interceptor at the top of the scene, if applicable. It
// will capture all input, and any unwanted input will be reinjected into
// embedded views.
if (intercept_all_input) {
input_interceptor_node_.emplace(session_.get());
input_interceptor_node_->SetLabel("Flutter::InputInterceptor");
input_interceptor_node_->SetHitTestBehavior(
fuchsia::ui::gfx::HitTestBehavior::kDefault);
input_interceptor_node_->SetSemanticVisibility(false);
metrics_node_.AddChild(input_interceptor_node_.value());
}
session_.Present();
}
std::vector<SceneUpdateContext::PaintTask> SceneUpdateContext::GetPaintTasks() {
std::vector<PaintTask> frame_paint_tasks = std::move(paint_tasks_);
paint_tasks_.clear();
return frame_paint_tasks;
}
void SceneUpdateContext::EnableWireframe(bool enable) {
session_.get()->Enqueue(
scenic::NewSetEnableDebugViewBoundsCmd(root_view_.id(), enable));
}
void SceneUpdateContext::Reset(const SkISize& frame_size,
float device_pixel_ratio) {
paint_tasks_.clear();
top_entity_ = nullptr;
top_scale_x_ = 1.f;
top_scale_y_ = 1.f;
top_elevation_ = 0.f;
next_elevation_ = 0.f;
alpha_ = 1.f;
// Adjust scene scaling to match the device pixel ratio.
const float inv_dpr = 1.0f / device_pixel_ratio;
layer_tree_node_.SetScale(inv_dpr, inv_dpr, 1.0f);
// Set up the input interceptor at the top of the scene, if applicable.
if (input_interceptor_node_.has_value()) {
// TODO(fxb/): Don't hardcode elevation.
input_interceptor_node_->SetTranslation(frame_size.width() * 0.5f,
frame_size.height() * 0.5f, -100.f);
input_interceptor_node_->SetShape(scenic::Rectangle(
session_.get(), frame_size.width(), frame_size.height()));
}
// We are going to be sending down a fresh node hierarchy every frame. So just
// enqueue a detach op on the layer tree node.
layer_tree_node_.DetachChildren();
}
void SceneUpdateContext::CreateFrame(scenic::EntityNode& entity_node,
const SkRRect& rrect,
SkColor color,
SkAlpha opacity,
const SkRect& paint_bounds,
std::vector<Layer*> paint_layers) {
// We don't need a shape if the frame is zero size.
if (rrect.isEmpty())
return;
// Frames always clip their children.
SkRect shape_bounds = rrect.getBounds();
SetEntityNodeClipPlanes(entity_node, shape_bounds);
// TODO(SCN-137): Need to be able to express the radii as vectors.
scenic::ShapeNode shape_node(session_.get());
scenic::Rectangle shape(session_.get(), rrect.width(), rrect.height());
shape_node.SetShape(shape);
shape_node.SetTranslation(shape_bounds.width() * 0.5f + shape_bounds.left(),
shape_bounds.height() * 0.5f + shape_bounds.top(),
0.f);
// Check whether the painted layers will be visible.
if (paint_bounds.isEmpty() || !paint_bounds.intersects(shape_bounds))
paint_layers.clear();
scenic::Material material(session_.get());
shape_node.SetMaterial(material);
entity_node.AddChild(shape_node);
// Check whether a solid color will suffice.
if (paint_layers.empty()) {
SetMaterialColor(material, color, opacity);
} else {
// The final shape's color is material_color * texture_color. The passed in
// material color was already used as a background when generating the
// texture, so set the model color to |SK_ColorWHITE| in order to allow
// using the texture's color unmodified.
SetMaterialColor(material, SK_ColorWHITE, opacity);
// Enqueue a paint task for these layers, to apply a texture to the whole
// shape.
//
// The task uses the |shape_bounds| as its rendering bounds instead of the
// |paint_bounds|. If the paint_bounds is large than the shape_bounds it
// will be clipped.
paint_tasks_.emplace_back(PaintTask{.paint_bounds = shape_bounds,
.scale_x = top_scale_x_,
.scale_y = top_scale_y_,
.background_color = color,
.material = std::move(material),
.layers = std::move(paint_layers)});
}
}
void SceneUpdateContext::UpdateView(int64_t view_id,
const SkPoint& offset,
const SkSize& size,
std::optional<bool> override_hit_testable) {
auto* view_holder = ViewHolder::FromId(view_id);
if (view_holder == nullptr) {
FML_LOG(ERROR) << "UpdateView did not find view holder for: " << view_id;
return;
}
if (size.width() > 0.f && size.height() > 0.f) {
view_holder->SetProperties(size.width(), size.height(), 0, 0, 0, 0,
view_holder->focusable());
}
bool hit_testable = override_hit_testable.has_value()
? *override_hit_testable
: view_holder->hit_testable();
view_holder->UpdateScene(session_.get(), top_entity_->embedder_node(), offset,
size, SkScalarRoundToInt(alphaf() * 255),
hit_testable);
// Assume embedded views are 10 "layers" wide.
next_elevation_ += 10 * kScenicZElevationBetweenLayers;
}
void SceneUpdateContext::CreateView(int64_t view_id,
bool hit_testable,
bool focusable) {
FML_LOG(INFO) << "CreateView for view holder: " << view_id;
zx_handle_t handle = (zx_handle_t)view_id;
flutter::ViewHolder::Create(handle, nullptr,
scenic::ToViewHolderToken(zx::eventpair(handle)),
nullptr);
auto* view_holder = ViewHolder::FromId(view_id);
FML_DCHECK(view_holder);
view_holder->set_hit_testable(hit_testable);
view_holder->set_focusable(focusable);
}
void SceneUpdateContext::UpdateView(int64_t view_id,
bool hit_testable,
bool focusable) {
auto* view_holder = ViewHolder::FromId(view_id);
if (view_holder == nullptr) {
FML_LOG(ERROR) << "UpdateView did not find view holder for: " << view_id;
return;
}
view_holder->set_hit_testable(hit_testable);
view_holder->set_focusable(focusable);
}
void SceneUpdateContext::DestroyView(int64_t view_id) {
ViewHolder::Destroy(view_id);
}
SceneUpdateContext::Entity::Entity(std::shared_ptr<SceneUpdateContext> context)
: context_(context),
previous_entity_(context->top_entity_),
entity_node_(context->session_.get()) {
context->top_entity_ = this;
}
SceneUpdateContext::Entity::~Entity() {
if (previous_entity_) {
previous_entity_->embedder_node().AddChild(entity_node_);
} else {
context_->layer_tree_node_.AddChild(entity_node_);
}
FML_DCHECK(context_->top_entity_ == this);
context_->top_entity_ = previous_entity_;
}
SceneUpdateContext::Transform::Transform(
std::shared_ptr<SceneUpdateContext> context,
const SkMatrix& transform)
: Entity(context),
previous_scale_x_(context->top_scale_x_),
previous_scale_y_(context->top_scale_y_) {
entity_node().SetLabel("flutter::Transform");
if (!transform.isIdentity()) {
// TODO(SCN-192): The perspective and shear components in the matrix
// are not handled correctly.
MatrixDecomposition decomposition(transform);
if (decomposition.IsValid()) {
// Don't allow clients to control the z dimension; we control that
// instead to make sure layers appear in proper order.
entity_node().SetTranslation(decomposition.translation().x, //
decomposition.translation().y, //
0.f //
);
entity_node().SetScale(decomposition.scale().x, //
decomposition.scale().y, //
1.f //
);
context->top_scale_x_ *= decomposition.scale().x;
context->top_scale_y_ *= decomposition.scale().y;
entity_node().SetRotation(decomposition.rotation().x, //
decomposition.rotation().y, //
decomposition.rotation().z, //
decomposition.rotation().w //
);
}
}
}
SceneUpdateContext::Transform::Transform(
std::shared_ptr<SceneUpdateContext> context,
float scale_x,
float scale_y,
float scale_z)
: Entity(context),
previous_scale_x_(context->top_scale_x_),
previous_scale_y_(context->top_scale_y_) {
entity_node().SetLabel("flutter::Transform");
if (scale_x != 1.f || scale_y != 1.f || scale_z != 1.f) {
entity_node().SetScale(scale_x, scale_y, scale_z);
context->top_scale_x_ *= scale_x;
context->top_scale_y_ *= scale_y;
}
}
SceneUpdateContext::Transform::~Transform() {
context()->top_scale_x_ = previous_scale_x_;
context()->top_scale_y_ = previous_scale_y_;
}
SceneUpdateContext::Frame::Frame(std::shared_ptr<SceneUpdateContext> context,
const SkRRect& rrect,
SkColor color,
SkAlpha opacity,
std::string label)
: Entity(context),
previous_elevation_(context->top_elevation_),
rrect_(rrect),
color_(color),
opacity_(opacity),
opacity_node_(context->session_.get()),
paint_bounds_(SkRect::MakeEmpty()) {
// Increment elevation trackers before calculating any local elevation.
// |UpdateView| can modify context->next_elevation_, which is why it is
// necessary to track this additional state.
context->top_elevation_ += kScenicZElevationBetweenLayers;
context->next_elevation_ += kScenicZElevationBetweenLayers;
float local_elevation = context->next_elevation_ - previous_elevation_;
entity_node().SetTranslation(0.f, 0.f, -local_elevation);
entity_node().SetLabel(label);
entity_node().AddChild(opacity_node_);
// Scenic currently lacks an API to enable rendering of alpha channel; alpha
// channels are only rendered if there is a OpacityNode higher in the tree
// with opacity != 1. For now, clamp to a infinitesimally smaller value than
// 1, which does not cause visual problems in practice.
opacity_node_.SetOpacity(std::min(kOneMinusEpsilon, opacity_ / 255.0f));
}
SceneUpdateContext::Frame::~Frame() {
context()->top_elevation_ = previous_elevation_;
// Add a part which represents the frame's geometry for clipping purposes
context()->CreateFrame(entity_node(), rrect_, color_, opacity_, paint_bounds_,
std::move(paint_layers_));
}
void SceneUpdateContext::Frame::AddPaintLayer(Layer* layer) {
FML_DCHECK(!layer->is_empty());
paint_layers_.push_back(layer);
paint_bounds_.join(layer->paint_bounds());
}
SceneUpdateContext::Clip::Clip(std::shared_ptr<SceneUpdateContext> context,
const SkRect& shape_bounds)
: Entity(context) {
entity_node().SetLabel("flutter::Clip");
SetEntityNodeClipPlanes(entity_node(), shape_bounds);
}
} // namespace flutter