mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
* [fuchsia] Add labels to Scenic nodes. * [fuchsia] Skip creating Scenic nodes for identity Transforms. * [fuchsia] Assign elevation to Scenic nodes based on paint order. * [fuchsia] Create Scenic OpacityNodes at leaf nodes. * [fuchsia] Composite PhysicalShapeLayers using Skia, except when they need to float above child views. In that case, they will still need to be pulled into separate Scenic nodes to be composited on top of the child view[s]. * [fuchsia] Add tests for Fuchsia-specific layer behavior. Inspect commands going to Scenic and make sure they match what is expected. Also, restructure code to need less member variables, and other cleanups based on review feedback.
349 lines
13 KiB
C++
349 lines
13 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 "flutter/flow/layers/layer.h"
|
|
#include "flutter/flow/matrix_decomposition.h"
|
|
#include "flutter/fml/trace_event.h"
|
|
#include "include/core/SkColor.h"
|
|
|
|
namespace flutter {
|
|
|
|
// Helper function to generate clip planes for a scenic::EntityNode.
|
|
static 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));
|
|
}
|
|
|
|
SceneUpdateContext::SceneUpdateContext(scenic::Session* session,
|
|
SurfaceProducer* surface_producer)
|
|
: session_(session), surface_producer_(surface_producer) {
|
|
FML_DCHECK(surface_producer_ != nullptr);
|
|
}
|
|
|
|
void SceneUpdateContext::CreateFrame(scenic::EntityNode entity_node,
|
|
const SkRRect& rrect,
|
|
SkColor color,
|
|
SkAlpha opacity,
|
|
const SkRect& paint_bounds,
|
|
std::vector<Layer*> paint_layers,
|
|
Layer* layer) {
|
|
FML_DCHECK(!rrect.isEmpty());
|
|
|
|
// Frames always clip their children.
|
|
SkRect shape_bounds = rrect.getBounds();
|
|
SetEntityNodeClipPlanes(entity_node, shape_bounds);
|
|
|
|
// and possibly for its texture.
|
|
// TODO(SCN-137): Need to be able to express the radii as vectors.
|
|
scenic::ShapeNode shape_node(session());
|
|
scenic::Rectangle shape(session_, // session
|
|
rrect.width(), // width
|
|
rrect.height() // 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());
|
|
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 {
|
|
// Apply current metrics and transformation scale factors.
|
|
const float scale_x = ScaleX();
|
|
const float scale_y = ScaleY();
|
|
|
|
// Apply a texture to the whole shape.
|
|
SetMaterialTextureAndColor(material, color, opacity, scale_x, scale_y,
|
|
shape_bounds, std::move(paint_layers), layer,
|
|
std::move(entity_node));
|
|
}
|
|
}
|
|
|
|
void SceneUpdateContext::SetMaterialTextureAndColor(
|
|
scenic::Material& material,
|
|
SkColor color,
|
|
SkAlpha opacity,
|
|
SkScalar scale_x,
|
|
SkScalar scale_y,
|
|
const SkRect& paint_bounds,
|
|
std::vector<Layer*> paint_layers,
|
|
Layer* layer,
|
|
scenic::EntityNode entity_node) {
|
|
scenic::Image* image = GenerateImageIfNeeded(
|
|
color, scale_x, scale_y, paint_bounds, std::move(paint_layers), layer,
|
|
std::move(entity_node));
|
|
|
|
if (image != nullptr) {
|
|
// 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);
|
|
material.SetTexture(*image);
|
|
} else {
|
|
// No texture was needed, so apply a solid color to the whole shape.
|
|
SetMaterialColor(material, color, opacity);
|
|
}
|
|
}
|
|
|
|
void SceneUpdateContext::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);
|
|
}
|
|
|
|
scenic::Image* SceneUpdateContext::GenerateImageIfNeeded(
|
|
SkColor color,
|
|
SkScalar scale_x,
|
|
SkScalar scale_y,
|
|
const SkRect& paint_bounds,
|
|
std::vector<Layer*> paint_layers,
|
|
Layer* layer,
|
|
scenic::EntityNode entity_node) {
|
|
// Bail if there's nothing to paint.
|
|
if (paint_layers.empty())
|
|
return nullptr;
|
|
|
|
// Bail if the physical bounds are empty after rounding.
|
|
SkISize physical_size = SkISize::Make(paint_bounds.width() * scale_x,
|
|
paint_bounds.height() * scale_y);
|
|
if (physical_size.isEmpty())
|
|
return nullptr;
|
|
|
|
// Acquire a surface from the surface producer and register the paint tasks.
|
|
std::unique_ptr<SurfaceProducerSurface> surface =
|
|
surface_producer_->ProduceSurface(
|
|
physical_size,
|
|
LayerRasterCacheKey(
|
|
// Root frame has a nullptr layer
|
|
layer ? layer->unique_id() : 0, Matrix()),
|
|
std::make_unique<scenic::EntityNode>(std::move(entity_node)));
|
|
|
|
if (!surface) {
|
|
FML_LOG(ERROR) << "Could not acquire a surface from the surface producer "
|
|
"of size: "
|
|
<< physical_size.width() << "x" << physical_size.height();
|
|
return nullptr;
|
|
}
|
|
|
|
auto image = surface->GetImage();
|
|
|
|
// Enqueue the paint task.
|
|
paint_tasks_.push_back({.surface = std::move(surface),
|
|
.left = paint_bounds.left(),
|
|
.top = paint_bounds.top(),
|
|
.scale_x = scale_x,
|
|
.scale_y = scale_y,
|
|
.background_color = color,
|
|
.layers = std::move(paint_layers)});
|
|
return image;
|
|
}
|
|
|
|
std::vector<
|
|
std::unique_ptr<flutter::SceneUpdateContext::SurfaceProducerSurface>>
|
|
SceneUpdateContext::ExecutePaintTasks(CompositorContext::ScopedFrame& frame) {
|
|
TRACE_EVENT0("flutter", "SceneUpdateContext::ExecutePaintTasks");
|
|
std::vector<std::unique_ptr<SurfaceProducerSurface>> surfaces_to_submit;
|
|
for (auto& task : paint_tasks_) {
|
|
FML_DCHECK(task.surface);
|
|
SkCanvas* canvas = task.surface->GetSkiaSurface()->getCanvas();
|
|
Layer::PaintContext context = {canvas,
|
|
canvas,
|
|
frame.gr_context(),
|
|
nullptr,
|
|
frame.context().raster_time(),
|
|
frame.context().ui_time(),
|
|
frame.context().texture_registry(),
|
|
&frame.context().raster_cache(),
|
|
false,
|
|
frame_physical_depth_,
|
|
frame_device_pixel_ratio_};
|
|
canvas->restoreToCount(1);
|
|
canvas->save();
|
|
canvas->clear(task.background_color);
|
|
canvas->scale(task.scale_x, task.scale_y);
|
|
canvas->translate(-task.left, -task.top);
|
|
for (Layer* layer : task.layers) {
|
|
layer->Paint(context);
|
|
}
|
|
surfaces_to_submit.emplace_back(std::move(task.surface));
|
|
}
|
|
paint_tasks_.clear();
|
|
alpha_ = 1.f;
|
|
topmost_global_scenic_elevation_ = kScenicZElevationBetweenLayers;
|
|
scenic_elevation_ = 0.f;
|
|
return surfaces_to_submit;
|
|
}
|
|
|
|
SceneUpdateContext::Entity::Entity(SceneUpdateContext& context)
|
|
: context_(context),
|
|
previous_entity_(context.top_entity_),
|
|
entity_node_(context.session()) {
|
|
if (previous_entity_)
|
|
previous_entity_->embedder_node().AddChild(entity_node_);
|
|
context.top_entity_ = this;
|
|
}
|
|
|
|
SceneUpdateContext::Entity::~Entity() {
|
|
FML_DCHECK(context_.top_entity_ == this);
|
|
context_.top_entity_ = previous_entity_;
|
|
}
|
|
|
|
SceneUpdateContext::Transform::Transform(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(), //
|
|
0.f //
|
|
);
|
|
context.top_scale_x_ *= decomposition.scale().x();
|
|
context.top_scale_y_ *= decomposition.scale().y();
|
|
|
|
entity_node().SetRotation(decomposition.rotation().fData[0], //
|
|
decomposition.rotation().fData[1], //
|
|
decomposition.rotation().fData[2], //
|
|
decomposition.rotation().fData[3] //
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
SceneUpdateContext::Transform::Transform(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(SceneUpdateContext& context,
|
|
const SkRRect& rrect,
|
|
SkColor color,
|
|
SkAlpha opacity,
|
|
std::string label,
|
|
float z_translation,
|
|
Layer* layer)
|
|
: Entity(context),
|
|
rrect_(rrect),
|
|
color_(color),
|
|
opacity_(opacity),
|
|
opacity_node_(context.session()),
|
|
paint_bounds_(SkRect::MakeEmpty()),
|
|
layer_(layer) {
|
|
entity_node().SetLabel(label);
|
|
entity_node().SetTranslation(0.f, 0.f, z_translation);
|
|
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() {
|
|
// We don't need a shape if the frame is zero size.
|
|
if (rrect_.isEmpty())
|
|
return;
|
|
|
|
// isEmpty should account for this, but we are adding these experimental
|
|
// checks to validate if this is the root cause for b/144933519.
|
|
if (std::isnan(rrect_.width()) || std::isnan(rrect_.height())) {
|
|
FML_LOG(ERROR) << "Invalid RoundedRectangle";
|
|
return;
|
|
}
|
|
|
|
// Add a part which represents the frame's geometry for clipping purposes
|
|
context().CreateFrame(std::move(entity_node()), rrect_, color_, opacity_,
|
|
paint_bounds_, std::move(paint_layers_), layer_);
|
|
}
|
|
|
|
void SceneUpdateContext::Frame::AddPaintLayer(Layer* layer) {
|
|
FML_DCHECK(layer->needs_painting());
|
|
paint_layers_.push_back(layer);
|
|
paint_bounds_.join(layer->paint_bounds());
|
|
}
|
|
|
|
SceneUpdateContext::Clip::Clip(SceneUpdateContext& context,
|
|
const SkRect& shape_bounds)
|
|
: Entity(context) {
|
|
entity_node().SetLabel("flutter::Clip");
|
|
SetEntityNodeClipPlanes(entity_node(), shape_bounds);
|
|
}
|
|
|
|
} // namespace flutter
|