mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
383 lines
14 KiB
C++
383 lines
14 KiB
C++
// 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
|
|
// neccesary to track this addtional 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
|