// 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 #include #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 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( ((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::GetPaintTasks() { std::vector 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 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 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, ViewHolder::ViewIdCallback on_view_created, 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, std::move(on_view_created), scenic::ToViewHolderToken(zx::eventpair(handle))); 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::ViewIdCallback on_view_destroyed) { ViewHolder::Destroy(view_id, std::move(on_view_destroyed)); } SceneUpdateContext::Entity::Entity(std::shared_ptr 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 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 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 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 context, const SkRect& shape_bounds) : Entity(context) { entity_node().SetLabel("flutter::Clip"); SetEntityNodeClipPlanes(entity_node(), shape_bounds); } } // namespace flutter