未验证 提交 ad8ccf47 编写于 作者: M mikejurka 提交者: GitHub

[Fuchsia] Move physical shape layer compositing to Flutter (#17005)

* [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.
上级 c490cb9b
......@@ -49,6 +49,7 @@ FILE: ../../../flutter/flow/layers/color_filter_layer_unittests.cc
FILE: ../../../flutter/flow/layers/container_layer.cc
FILE: ../../../flutter/flow/layers/container_layer.h
FILE: ../../../flutter/flow/layers/container_layer_unittests.cc
FILE: ../../../flutter/flow/layers/fuchsia_layer_unittests.cc
FILE: ../../../flutter/flow/layers/image_filter_layer.cc
FILE: ../../../flutter/flow/layers/image_filter_layer.h
FILE: ../../../flutter/flow/layers/image_filter_layer_unittests.cc
......
......@@ -88,6 +88,7 @@ source_set("flow") {
if (using_fuchsia_sdk) {
public_deps += [
"$fuchsia_sdk_root/fidl:fuchsia.ui.app",
"$fuchsia_sdk_root/fidl:fuchsia.ui.gfx",
"$fuchsia_sdk_root/pkg:scenic_cpp",
]
......@@ -159,6 +160,10 @@ executable("flow_unittests") {
"texture_unittests.cc",
]
if (is_fuchsia) {
sources += [ "layers/fuchsia_layer_unittests.cc" ]
}
deps = [
":flow",
":flow_fixtures",
......@@ -170,6 +175,10 @@ executable("flow_unittests") {
"//third_party/googletest:gtest",
"//third_party/skia",
]
if (is_fuchsia) {
deps += [ "//build/fuchsia/pkg:sys_cpp_testing" ]
}
}
if (is_fuchsia) {
......
......@@ -20,6 +20,7 @@ ChildSceneLayer::ChildSceneLayer(zx_koid_t layer_id,
void ChildSceneLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
TRACE_EVENT0("flutter", "ChildSceneLayer::Preroll");
set_needs_system_composite(true);
context->child_scene_layer_exists_below = true;
// An alpha "hole punch" is required if the frame behind us is not opaque.
if (!context->is_opaque) {
......@@ -49,7 +50,9 @@ void ChildSceneLayer::UpdateScene(SceneUpdateContext& context) {
auto* view_holder = ViewHolder::FromId(layer_id_);
FML_DCHECK(view_holder);
view_holder->UpdateScene(context, offset_, size_, hit_testable_);
view_holder->UpdateScene(context, offset_, size_,
SkScalarRoundToInt(context.alphaf() * 255),
hit_testable_);
}
} // namespace flutter
// 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 <deque>
#include "gtest/gtest.h"
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl_test_base.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async/default.h>
#include <lib/fidl/cpp/optional.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/ui/scenic/cpp/commands.h>
#include <lib/ui/scenic/cpp/id.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include "flutter/flow/layers/child_scene_layer.h"
#include "flutter/flow/layers/container_layer.h"
#include "flutter/flow/layers/opacity_layer.h"
#include "flutter/flow/layers/physical_shape_layer.h"
#include "flutter/flow/view_holder.h"
#include "flutter/fml/platform/fuchsia/message_loop_fuchsia.h"
#include "flutter/fml/task_runner.h"
namespace flutter {
namespace testing {
using FuchsiaLayerTest = ::testing::Test;
class MockSession : public fuchsia::ui::scenic::testing::Session_TestBase {
public:
MockSession() : binding_(this) {}
void NotImplemented_(const std::string& name) final {}
void Bind(fidl::InterfaceRequest<::fuchsia::ui::scenic::Session> request,
::fuchsia::ui::scenic::SessionListenerPtr listener) {
binding_.Bind(std::move(request));
listener_ = std::move(listener);
}
static std::string Vec3ValueToString(fuchsia::ui::gfx::Vector3Value value) {
return "{" + std::to_string(value.value.x) + ", " +
std::to_string(value.value.y) + ", " +
std::to_string(value.value.z) + "}";
}
static std::string GfxCreateResourceCmdToString(
const fuchsia::ui::gfx::CreateResourceCmd& cmd) {
std::string id = " id: " + std::to_string(cmd.id);
switch (cmd.resource.Which()) {
case fuchsia::ui::gfx::ResourceArgs::Tag::kRectangle:
return "Rectangle" + id;
case fuchsia::ui::gfx::ResourceArgs::Tag::kRoundedRectangle:
return "RoundedRectangle" + id;
case fuchsia::ui::gfx::ResourceArgs::Tag::kViewHolder:
return "ViewHolder" + id;
case fuchsia::ui::gfx::ResourceArgs::Tag::kOpacityNode:
return "OpacityNode" + id;
case fuchsia::ui::gfx::ResourceArgs::Tag::kEntityNode:
return "EntityNode" + id;
case fuchsia::ui::gfx::ResourceArgs::Tag::kShapeNode:
return "ShapeNode" + id;
case fuchsia::ui::gfx::ResourceArgs::Tag::kMaterial:
return "Material" + id;
case fuchsia::ui::gfx::ResourceArgs::Tag::kImage:
return "Image" + id + ", memory_id: " +
std::to_string(cmd.resource.image().memory_id) +
", memory_offset: " +
std::to_string(cmd.resource.image().memory_offset);
default:
return "Unhandled CreateResource command" +
std::to_string(cmd.resource.Which());
}
}
static std::string GfxCmdToString(const fuchsia::ui::gfx::Command& cmd) {
switch (cmd.Which()) {
case fuchsia::ui::gfx::Command::Tag::kCreateResource:
return "CreateResource: " +
GfxCreateResourceCmdToString(cmd.create_resource());
case fuchsia::ui::gfx::Command::Tag::kReleaseResource:
return "ReleaseResource id: " +
std::to_string(cmd.release_resource().id);
case fuchsia::ui::gfx::Command::Tag::kAddChild:
return "AddChild id: " + std::to_string(cmd.add_child().node_id) +
" child_id: " + std::to_string(cmd.add_child().child_id);
case fuchsia::ui::gfx::Command::Tag::kSetTranslation:
return "SetTranslation id: " +
std::to_string(cmd.set_translation().id) +
" value: " + Vec3ValueToString(cmd.set_translation().value);
case fuchsia::ui::gfx::Command::Tag::kSetScale:
return "SetScale id: " + std::to_string(cmd.set_scale().id) +
" value: " + Vec3ValueToString(cmd.set_translation().value);
case fuchsia::ui::gfx::Command::Tag::kSetRotation:
return "SetRotation id: " + std::to_string(cmd.set_rotation().id);
case fuchsia::ui::gfx::Command::Tag::kSetOpacity:
return "SetOpacity id: " + std::to_string(cmd.set_opacity().node_id) +
", opacity: " + std::to_string(cmd.set_opacity().opacity);
case fuchsia::ui::gfx::Command::Tag::kSetColor:
return "SetColor id: " + std::to_string(cmd.set_color().material_id) +
", rgba: (" + std::to_string(cmd.set_color().color.value.red) +
", " + std::to_string(cmd.set_color().color.value.green) + ", " +
std::to_string(cmd.set_color().color.value.blue) + ", " +
std::to_string(cmd.set_color().color.value.alpha) + ")";
case fuchsia::ui::gfx::Command::Tag::kSetLabel:
return "SetLabel id: " + std::to_string(cmd.set_label().id) + " " +
cmd.set_label().label;
case fuchsia::ui::gfx::Command::Tag::kSetHitTestBehavior:
return "SetHitTestBehavior node_id: " +
std::to_string(cmd.set_hit_test_behavior().node_id);
case fuchsia::ui::gfx::Command::Tag::kSetClipPlanes:
return "SetClipPlanes node_id: " +
std::to_string(cmd.set_clip_planes().node_id);
case fuchsia::ui::gfx::Command::Tag::kSetShape:
return "SetShape node_id: " + std::to_string(cmd.set_shape().node_id) +
", shape_id: " + std::to_string(cmd.set_shape().shape_id);
case fuchsia::ui::gfx::Command::Tag::kSetMaterial:
return "SetMaterial node_id: " +
std::to_string(cmd.set_material().node_id) + ", material_id: " +
std::to_string(cmd.set_material().material_id);
case fuchsia::ui::gfx::Command::Tag::kSetTexture:
return "SetTexture material_id: " +
std::to_string(cmd.set_texture().material_id) +
", texture_id: " + std::to_string(cmd.set_texture().texture_id);
default:
return "Unhandled gfx command" + std::to_string(cmd.Which());
}
}
static std::string ScenicCmdToString(
const fuchsia::ui::scenic::Command& cmd) {
if (cmd.Which() != fuchsia::ui::scenic::Command::Tag::kGfx) {
return "Unhandled non-gfx command: " + std::to_string(cmd.Which());
}
return GfxCmdToString(cmd.gfx());
}
// |fuchsia::ui::scenic::Session|
void Enqueue(std::vector<fuchsia::ui::scenic::Command> cmds) override {
for (const auto& cmd : cmds) {
num_enqueued_commands_++;
EXPECT_FALSE(expected_.empty())
<< "Received more commands than expected; command: <"
<< ScenicCmdToString(cmd)
<< ">, num_enqueued_commands: " << num_enqueued_commands_;
if (!expected_.empty()) {
EXPECT_TRUE(AreCommandsEqual(expected_.front(), cmd))
<< "actual command: <" << ScenicCmdToString(cmd)
<< ">, expected command: <" << ScenicCmdToString(expected_.front())
<< ">, num_enqueued_commands: " << num_enqueued_commands_;
expected_.pop_front();
}
}
}
void SetExpectedCommands(std::vector<fuchsia::ui::gfx::Command> gfx_cmds) {
std::deque<fuchsia::ui::scenic::Command> scenic_commands;
for (auto it = gfx_cmds.begin(); it != gfx_cmds.end(); it++) {
scenic_commands.push_back(scenic::NewCommand(std::move((*it))));
}
expected_ = std::move(scenic_commands);
num_enqueued_commands_ = 0;
}
size_t num_enqueued_commands() { return num_enqueued_commands_; }
private:
static bool IsGfxCommand(const fuchsia::ui::scenic::Command& cmd,
fuchsia::ui::gfx::Command::Tag tag) {
return cmd.Which() == fuchsia::ui::scenic::Command::Tag::kGfx &&
cmd.gfx().Which() == tag;
}
static bool IsCreateResourceCommand(const fuchsia::ui::scenic::Command& cmd,
fuchsia::ui::gfx::ResourceArgs::Tag tag) {
return IsGfxCommand(cmd, fuchsia::ui::gfx::Command::Tag::kCreateResource) &&
cmd.gfx().create_resource().resource.Which() == tag;
}
static bool AreCommandsEqual(const fuchsia::ui::scenic::Command& command1,
const fuchsia::ui::scenic::Command& command2) {
// For CreateViewHolderCommand, just compare the id and ignore the
// view_holder_token.
if (IsCreateResourceCommand(
command1, fuchsia::ui::gfx::ResourceArgs::Tag::kViewHolder)) {
return IsCreateResourceCommand(
command2, fuchsia::ui::gfx::ResourceArgs::Tag::kViewHolder) &&
command1.gfx().create_resource().id ==
command2.gfx().create_resource().id;
}
// For CreateImageCommand, just compare the id and memory_id.
if (IsCreateResourceCommand(command1,
fuchsia::ui::gfx::ResourceArgs::Tag::kImage)) {
return IsCreateResourceCommand(
command2, fuchsia::ui::gfx::ResourceArgs::Tag::kImage) &&
command1.gfx().create_resource().id ==
command2.gfx().create_resource().id &&
command1.gfx().create_resource().resource.image().memory_id ==
command2.gfx().create_resource().resource.image().memory_id;
}
// For SetHitTestBehaviorCommand, just compare the node_id.
if (IsGfxCommand(command1,
fuchsia::ui::gfx::Command::Tag::kSetHitTestBehavior)) {
return IsGfxCommand(
command2,
fuchsia::ui::gfx::Command::Tag::kSetHitTestBehavior) &&
command1.gfx().set_hit_test_behavior().node_id ==
command2.gfx().set_hit_test_behavior().node_id;
}
// For SetHitTestBehaviorCommand, just compare the node_id.
if (IsGfxCommand(command1,
fuchsia::ui::gfx::Command::Tag::kSetClipPlanes)) {
return IsGfxCommand(command2,
fuchsia::ui::gfx::Command::Tag::kSetClipPlanes) &&
command1.gfx().set_clip_planes().node_id ==
command2.gfx().set_clip_planes().node_id;
}
return fidl::Equals(command1, command2);
}
std::deque<fuchsia::ui::scenic::Command> expected_;
size_t num_enqueued_commands_ = 0;
fidl::Binding<fuchsia::ui::scenic::Session> binding_;
fuchsia::ui::scenic::SessionListenerPtr listener_;
};
class MockSurfaceProducerSurface
: public SceneUpdateContext::SurfaceProducerSurface {
public:
MockSurfaceProducerSurface(scenic::Session* session, const SkISize& size)
: image_(session, 0, 0, {}), size_(size) {}
size_t AdvanceAndGetAge() override { return 0; }
bool FlushSessionAcquireAndReleaseEvents() override { return false; }
bool IsValid() const override { return false; }
SkISize GetSize() const override { return size_; }
void SignalWritesFinished(
const std::function<void(void)>& on_writes_committed) override {}
scenic::Image* GetImage() override { return &image_; };
sk_sp<SkSurface> GetSkiaSurface() const override { return nullptr; };
private:
scenic::Image image_;
SkISize size_;
};
class MockSurfaceProducer : public SceneUpdateContext::SurfaceProducer {
public:
MockSurfaceProducer(scenic::Session* session) : session_(session) {}
std::unique_ptr<SceneUpdateContext::SurfaceProducerSurface> ProduceSurface(
const SkISize& size,
const LayerRasterCacheKey& layer_key,
std::unique_ptr<scenic::EntityNode> entity_node) override {
return std::make_unique<MockSurfaceProducerSurface>(session_, size);
}
// Query a retained entity node (owned by a retained surface) for retained
// rendering.
bool HasRetainedNode(const LayerRasterCacheKey& key) const override {
return false;
}
scenic::EntityNode* GetRetainedNode(const LayerRasterCacheKey& key) override {
return nullptr;
}
void SubmitSurface(std::unique_ptr<SceneUpdateContext::SurfaceProducerSurface>
surface) override {}
private:
scenic::Session* session_;
};
struct TestContext {
// Message loop.
fml::RefPtr<fml::MessageLoopFuchsia> loop;
fml::RefPtr<fml::TaskRunner> task_runner;
// Session.
MockSession mock_session;
fidl::InterfaceRequest<fuchsia::ui::scenic::SessionListener> listener_request;
std::unique_ptr<scenic::Session> session;
// SceneUpdateContext.
std::unique_ptr<MockSurfaceProducer> mock_surface_producer;
std::unique_ptr<SceneUpdateContext> scene_update_context;
// PrerollContext.
MutatorsStack unused_stack;
const Stopwatch unused_stopwatch;
TextureRegistry unused_texture_registry;
std::unique_ptr<PrerollContext> preroll_context;
};
std::unique_ptr<TestContext> InitTest() {
std::unique_ptr<TestContext> context = std::make_unique<TestContext>();
// Init message loop.
context->loop = fml::MakeRefCounted<fml::MessageLoopFuchsia>();
context->task_runner = fml::MakeRefCounted<fml::TaskRunner>(context->loop);
// Init Session.
fuchsia::ui::scenic::SessionPtr session_ptr;
fuchsia::ui::scenic::SessionListenerPtr listener;
context->listener_request = listener.NewRequest();
context->mock_session.Bind(session_ptr.NewRequest(), std::move(listener));
context->session = std::make_unique<scenic::Session>(std::move(session_ptr));
// Init SceneUpdateContext.
context->mock_surface_producer =
std::make_unique<MockSurfaceProducer>(context->session.get());
context->scene_update_context = std::make_unique<SceneUpdateContext>(
context->session.get(), context->mock_surface_producer.get());
context->scene_update_context->set_metrics(
fidl::MakeOptional(fuchsia::ui::gfx::Metrics{1.f, 1.f, 1.f}));
// Init PrerollContext.
context->preroll_context = std::unique_ptr<PrerollContext>(new PrerollContext{
nullptr, // raster_cache (don't consult the cache)
nullptr, // gr_context (used for the raster cache)
nullptr, // external view embedder
context->unused_stack, // mutator stack
nullptr, // SkColorSpace* dst_color_space
kGiantRect, // SkRect cull_rect
false, // layer reads from surface
context->unused_stopwatch, // frame time (dont care)
context->unused_stopwatch, // engine time (dont care)
context->unused_texture_registry, // texture registry (not
// supported)
false, // checkerboard_offscreen_layers
100.f, // maximum depth allowed for rendering
1.f // ratio between logical and physical
});
return context;
}
zx_koid_t GetChildLayerId() {
static zx_koid_t sChildLayerId = 17324;
return sChildLayerId++;
}
class AutoDestroyChildLayerId {
public:
AutoDestroyChildLayerId(zx_koid_t id) : id_(id) {}
~AutoDestroyChildLayerId() { ViewHolder::Destroy(id_); }
private:
zx_koid_t id_;
};
// Create a hierarchy with PhysicalShapeLayers and ChildSceneLayers, and
// inspect the commands sent to Scenic.
//
//
// What we expect:
//
// The Scenic elevations of the PhysicalShapeLayers are monotically
// increasing, even though the elevations we gave them when creating them are
// decreasing. The two should not have any correlation; we're merely mirror
// the paint order using Scenic elevation.
//
// PhysicalShapeLayers created before/below a ChildView do not get their own
// node; PhysicalShapeLayers created afterward do.
//
// Nested PhysicalShapeLayers are collapsed.
TEST_F(FuchsiaLayerTest, PhysicalShapeLayersAndChildSceneLayers) {
auto test_context = InitTest();
// Root.
auto root = std::make_shared<ContainerLayer>();
SkPath path;
path.addRect(SkRect::MakeWH(10.f, 10.f));
// Child #1: PhysicalShapeLayer.
auto physical_shape1 = std::make_shared<PhysicalShapeLayer>(
/*color=*/SK_ColorCYAN,
/*shadow_color=*/SK_ColorBLACK,
/*elevation*/ 23.f, path, Clip::antiAlias);
root->Add(physical_shape1);
// Child #2: ChildSceneLayer.
const zx_koid_t kChildLayerId1 = GetChildLayerId();
auto [unused_view_token1, unused_view_holder_token1] =
scenic::ViewTokenPair::New();
ViewHolder::Create(kChildLayerId1, test_context->task_runner,
std::move(unused_view_holder_token1),
/*bind_callback=*/[](scenic::ResourceId id) {});
// Will destroy only when we go out of scope (i.e. end of the test).
AutoDestroyChildLayerId auto_destroy1(kChildLayerId1);
auto child_view1 = std::make_shared<ChildSceneLayer>(
kChildLayerId1, SkPoint::Make(1, 1), SkSize::Make(10, 10),
/*hit_testable=*/false);
root->Add(child_view1);
// Child #3: PhysicalShapeLayer
auto physical_shape2 = std::make_shared<PhysicalShapeLayer>(
/*color=*/SK_ColorCYAN,
/*shadow_color=*/SK_ColorBLACK,
/*elevation*/ 21.f, path, Clip::antiAlias);
root->Add(physical_shape2);
// Grandchild (child of #3): PhysicalShapeLayer
auto physical_shape3 = std::make_shared<PhysicalShapeLayer>(
/*color=*/SK_ColorCYAN,
/*shadow_color=*/SK_ColorBLACK,
/*elevation*/ 19.f, path, Clip::antiAlias);
physical_shape2->Add(physical_shape3);
// Child #4: ChildSceneLayer
const zx_koid_t kChildLayerId2 = GetChildLayerId();
auto [unused_view_token2, unused_view_holder_token2] =
scenic::ViewTokenPair::New();
ViewHolder::Create(kChildLayerId2, test_context->task_runner,
std::move(unused_view_holder_token2),
/*bind_callback=*/[](scenic::ResourceId id) {});
// Will destroy only when we go out of scope (i.e. end of the test).
AutoDestroyChildLayerId auto_destroy2(kChildLayerId2);
auto child_view2 = std::make_shared<ChildSceneLayer>(
kChildLayerId2, SkPoint::Make(1, 1), SkSize::Make(10, 10),
/*hit_testable=*/false);
root->Add(child_view2);
// Child #5: PhysicalShapeLayer
auto physical_shape4 = std::make_shared<PhysicalShapeLayer>(
/*color=*/SK_ColorCYAN,
/*shadow_color=*/SK_ColorBLACK,
/*elevation*/ 17.f, path, Clip::antiAlias);
root->Add(physical_shape4);
// Preroll.
root->Preroll(test_context->preroll_context.get(), SkMatrix());
// Create another frame to be the "real" root. Required because
// UpdateScene() traversal expects there to already be a top node.
SceneUpdateContext::Frame frame(*(test_context->scene_update_context),
SkRRect::MakeRect(SkRect::MakeWH(100, 100)),
SK_ColorTRANSPARENT, SK_AlphaOPAQUE,
"fuchsia test root");
// Submit the list of command we will expect Scenic to see.
//
// Some things we expect:
//
// The Scenic elevations of the PhysicalShapeLayers are monotically
// increasing, even though the elevations we gave them when creating them are
// decreasing. The two should not have any correlation; we're merely mirror
// the paint order using Scenic elevation.
//
// PhysicalShapeLayers created before/below a ChildView do not get their own
// node; PhysicalShapeLayers created afterward do.
//
// Nested PhysicalShapeLayers are collapsed.
std::vector<fuchsia::ui::gfx::Command> expected;
//
// Test root.
//
expected.push_back(scenic::NewCreateEntityNodeCmd(/*id=*/1));
expected.push_back(scenic::NewCreateOpacityNodeCmdHACK(/*id=*/2));
expected.push_back(scenic::NewSetLabelCmd(/*id=*/1, "fuchsia test root"));
expected.push_back(scenic::NewSetTranslationCmd(/*id=*/1, {0, 0}));
expected.push_back(scenic::NewAddChildCmd(/*id=*/1, /*child_id=*/2));
expected.push_back(scenic::NewSetOpacityCmd(/*id=*/2, kOneMinusEpsilon));
//
// Child #1: PhysicalShapeLayer
//
// Expect no new commands! Should be composited into base layer.
//
// Child #2: ChildSceneLayer.
//
expected.push_back(scenic::NewCreateEntityNodeCmd(/*id=*/3));
expected.push_back(scenic::NewCreateOpacityNodeCmdHACK(/*id=*/4));
auto [view_token1, view_holder_token1] = scenic::ViewTokenPair::New();
expected.push_back(scenic::NewCreateViewHolderCmd(
/*id=*/5, std::move(view_holder_token1), ""));
expected.push_back(scenic::NewAddChildCmd(/*id=*/4, /*child_id=*/3));
expected.push_back(scenic::NewSetLabelCmd(/*id=*/4, "flutter::ViewHolder"));
expected.push_back(scenic::NewAddChildCmd(/*id=*/3, /*child_id=*/5));
expected.push_back(scenic::NewAddChildCmd(/*id=*/2, /*child_id=*/4));
expected.push_back(scenic::NewSetOpacityCmd(/*id=*/4, 1.f));
expected.push_back(scenic::NewSetTranslationCmd(/*id=*/3, {1, 1, -0.1}));
expected.push_back(scenic::NewSetHitTestBehaviorCmd(
/*id=*/3, /*ignored*/ fuchsia::ui::gfx::HitTestBehavior::kSuppress));
//
// Child #3: PhysicalShapeLayer
//
expected.push_back(scenic::NewCreateEntityNodeCmd(/*id=*/6));
expected.push_back(scenic::NewAddChildCmd(/*id=*/2, /*child_id=*/6));
expected.push_back(scenic::NewCreateOpacityNodeCmdHACK(/*id=*/7));
expected.push_back(
scenic::NewSetLabelCmd(/*id=*/6, "flutter::PhysicalShapeLayer"));
expected.push_back(scenic::NewSetTranslationCmd(
/*id=*/6, {0, 0, -kScenicZElevationBetweenLayers}));
expected.push_back(scenic::NewAddChildCmd(/*id=*/6, /*child_id=*/7));
expected.push_back(scenic::NewSetOpacityCmd(/*id=*/7, kOneMinusEpsilon));
expected.push_back(scenic::NewSetClipPlanesCmd(/*id=*/6, /*ignored*/ {}));
expected.push_back(scenic::NewCreateShapeNodeCmd(/*id=*/8));
expected.push_back(scenic::NewCreateRectangleCmd(
/*id=*/9, /*width=*/10, /*height=*/10));
expected.push_back(scenic::NewSetShapeCmd(/*id=*/8, /*shape_id=*/9));
expected.push_back(scenic::NewSetTranslationCmd(/*id=*/8, {5, 5, 0}));
expected.push_back(scenic::NewCreateMaterialCmd(/*id=*/10));
expected.push_back(scenic::NewSetMaterialCmd(/*id=*/8, /*material_id=*/10));
expected.push_back(scenic::NewAddChildCmd(/*id=*/6, /*child_id=*/8));
expected.push_back(scenic::NewCreateImageCmd(/*id=*/11, 0, 0, {}));
expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/6));
expected.push_back(scenic::NewSetColorCmd(/*id=*/10, /*r*/ 255, /*g*/ 255,
/*b*/ 255, /*a*/ 255));
expected.push_back(
scenic::NewSetTextureCmd(/*material_id=*/10, /*texture_id=*/11));
expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/10));
expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/9));
expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/8));
expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/7));
//
// Grandchild (child of #3): PhysicalShapeLayer
//
// Expect no new commands! Should be composited into parent.
//
// Child #4: ChildSceneLayer
//
expected.push_back(scenic::NewCreateEntityNodeCmd(/*id=*/12));
expected.push_back(scenic::NewCreateOpacityNodeCmdHACK(/*id=*/13));
auto [view_token2, view_holder_token2] = scenic::ViewTokenPair::New();
expected.push_back(scenic::NewCreateViewHolderCmd(
/*id=*/14, std::move(view_holder_token2), ""));
expected.push_back(scenic::NewAddChildCmd(/*id=*/13, /*child_id=*/12));
expected.push_back(scenic::NewSetLabelCmd(/*id=*/13, "flutter::ViewHolder"));
expected.push_back(scenic::NewAddChildCmd(/*id=*/12, /*child_id=*/14));
expected.push_back(scenic::NewAddChildCmd(/*id=*/2, /*child_id=*/13));
expected.push_back(scenic::NewSetOpacityCmd(/*id=*/13, 1.f));
expected.push_back(scenic::NewSetTranslationCmd(/*id=*/12, {1, 1, -0.1}));
expected.push_back(scenic::NewSetHitTestBehaviorCmd(
/*id=*/12, /*ignored*/ fuchsia::ui::gfx::HitTestBehavior::kSuppress));
//
// Child #5: PhysicalShapeLayer
//
expected.push_back(scenic::NewCreateEntityNodeCmd(/*id=*/15));
expected.push_back(scenic::NewAddChildCmd(/*id=*/2, /*child_id=*/15));
expected.push_back(scenic::NewCreateOpacityNodeCmdHACK(/*id=*/16));
expected.push_back(
scenic::NewSetLabelCmd(/*id=*/15, "flutter::PhysicalShapeLayer"));
expected.push_back(scenic::NewSetTranslationCmd(
/*id=*/15, {0, 0, -2 * kScenicZElevationBetweenLayers}));
expected.push_back(scenic::NewAddChildCmd(/*id=*/15, /*child_id=*/16));
expected.push_back(scenic::NewSetOpacityCmd(/*id=*/16, kOneMinusEpsilon));
expected.push_back(scenic::NewSetClipPlanesCmd(/*id=*/15, /*ignored*/ {}));
expected.push_back(scenic::NewCreateShapeNodeCmd(/*id=*/17));
expected.push_back(scenic::NewCreateRectangleCmd(
/*id=*/18, /*width=*/10, /*height=*/10));
expected.push_back(scenic::NewSetShapeCmd(/*id=*/17, /*shape_id=*/18));
expected.push_back(scenic::NewSetTranslationCmd(/*id=*/17, {5, 5, 0}));
expected.push_back(scenic::NewCreateMaterialCmd(/*id=*/19));
expected.push_back(scenic::NewSetMaterialCmd(/*id=*/17, /*material_id=*/19));
expected.push_back(scenic::NewAddChildCmd(/*id=*/15, /*child_id=*/17));
expected.push_back(scenic::NewCreateImageCmd(/*id=*/20, 0, 0, {}));
expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/15));
expected.push_back(scenic::NewSetColorCmd(/*id=*/19, /*r*/ 255, /*g*/ 255,
/*b*/ 255, /*a*/ 255));
expected.push_back(
scenic::NewSetTextureCmd(/*material_id=*/19, /*texture_id=*/20));
expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/19));
expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/18));
expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/17));
expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/16));
test_context->mock_session.SetExpectedCommands(std::move(expected));
// Finally, UpdateScene(). The MockSession will check the emitted commands
// against the list above.
root->UpdateScene(*(test_context->scene_update_context));
// Run loop until idle, so that the Session receives and processes
// its method calls.
async_loop_run_until_idle(
async_loop_from_dispatcher(async_get_default_dispatcher()));
// Ensure we saw enough commands.
EXPECT_EQ(72u, test_context->mock_session.num_enqueued_commands());
}
// Create a hierarchy with OpacityLayers, PhysicalShapeLayers and
// ChildSceneLayers, and inspect the commands sent to Scenic.
//
// We are interested in verifying that the opacity values of children are
// correct.
//
TEST_F(FuchsiaLayerTest, Opacity) {
auto test_context = InitTest();
// Root.
auto root = std::make_shared<ContainerLayer>();
SkPath path;
path.addRect(SkRect::MakeWH(10.f, 10.f));
// OpacityLayer #1
auto opacity_layer1 =
std::make_shared<OpacityLayer>(127, SkPoint::Make(0, 0));
root->Add(opacity_layer1);
// OpacityLayer #2
auto opacity_layer2 =
std::make_shared<OpacityLayer>(127, SkPoint::Make(0, 0));
opacity_layer1->Add(opacity_layer2);
// Child #1: ChildSceneLayer.
const zx_koid_t kChildLayerId1 = GetChildLayerId();
auto [unused_view_token1, unused_view_holder_token1] =
scenic::ViewTokenPair::New();
ViewHolder::Create(kChildLayerId1, test_context->task_runner,
std::move(unused_view_holder_token1),
/*bind_callback=*/[](scenic::ResourceId id) {});
// Will destroy only when we go out of scope (i.e. end of the test).
AutoDestroyChildLayerId auto_destroy1(kChildLayerId1);
auto child_view1 = std::make_shared<ChildSceneLayer>(
kChildLayerId1, SkPoint::Make(1, 1), SkSize::Make(10, 10),
/*hit_testable=*/false);
opacity_layer2->Add(child_view1);
// Child #2: PhysicalShapeLayer.
auto physical_shape1 = std::make_shared<PhysicalShapeLayer>(
/*color=*/SK_ColorCYAN,
/*shadow_color=*/SK_ColorBLACK,
/*elevation*/ 23.f, path, Clip::antiAlias);
opacity_layer2->Add(physical_shape1);
// Preroll.
root->Preroll(test_context->preroll_context.get(), SkMatrix());
// Create another frame to be the "real" root. Required because
// UpdateScene() traversal expects there to already be a top node.
SceneUpdateContext::Frame frame(*(test_context->scene_update_context),
SkRRect::MakeRect(SkRect::MakeWH(100, 100)),
SK_ColorTRANSPARENT, SK_AlphaOPAQUE,
"fuchsia test root");
// Submit the list of command we will expect Scenic to see.
//
// We are interested in verifying that the opacity values of children are
// correct.
std::vector<fuchsia::ui::gfx::Command> expected;
//
// Test root.
//
expected.push_back(scenic::NewCreateEntityNodeCmd(/*id=*/1));
expected.push_back(scenic::NewCreateOpacityNodeCmdHACK(/*id=*/2));
expected.push_back(scenic::NewSetLabelCmd(/*id=*/1, "fuchsia test root"));
expected.push_back(scenic::NewSetTranslationCmd(/*id=*/1, {0, 0}));
expected.push_back(scenic::NewAddChildCmd(/*id=*/1, /*child_id=*/2));
expected.push_back(scenic::NewSetOpacityCmd(/*id=*/2, kOneMinusEpsilon));
//
// OpacityLayer #1
//
// Expect no new commands for this.
//
// OpacityLayer #2
//
// Expect no new commands for this.
//
// Child #1: ChildSceneLayer.
//
expected.push_back(scenic::NewCreateEntityNodeCmd(/*id=*/3));
expected.push_back(scenic::NewCreateOpacityNodeCmdHACK(/*id=*/4));
auto [view_token1, view_holder_token1] = scenic::ViewTokenPair::New();
expected.push_back(scenic::NewCreateViewHolderCmd(
/*id=*/5, std::move(view_holder_token1), ""));
expected.push_back(scenic::NewAddChildCmd(/*id=*/4, /*child_id=*/3));
expected.push_back(scenic::NewSetLabelCmd(/*id=*/4, "flutter::ViewHolder"));
expected.push_back(scenic::NewAddChildCmd(/*id=*/3, /*child_id=*/5));
expected.push_back(scenic::NewAddChildCmd(/*id=*/2, /*child_id=*/4));
// Check opacity value. Extra rounding required because we pass alpha as
// a uint/SkAlpha to SceneUpdateContext::Frame.
float opacity1 = kOneMinusEpsilon * (127 / 255.f) * (127 / 255.f);
opacity1 = SkScalarRoundToInt(opacity1 * 255) / 255.f;
expected.push_back(scenic::NewSetOpacityCmd(/*id=*/4, opacity1));
expected.push_back(scenic::NewSetTranslationCmd(/*id=*/3, {1, 1, -0.1}));
expected.push_back(scenic::NewSetHitTestBehaviorCmd(
/*id=*/3, /*ignored*/ fuchsia::ui::gfx::HitTestBehavior::kSuppress));
//
// Child #2: PhysicalShapeLayer
//
expected.push_back(scenic::NewCreateEntityNodeCmd(/*id=*/6));
expected.push_back(scenic::NewAddChildCmd(/*id=*/2, /*child_id=*/6));
expected.push_back(scenic::NewCreateOpacityNodeCmdHACK(/*id=*/7));
expected.push_back(
scenic::NewSetLabelCmd(/*id=*/6, "flutter::PhysicalShapeLayer"));
expected.push_back(scenic::NewSetTranslationCmd(
/*id=*/6, {0, 0, -kScenicZElevationBetweenLayers}));
expected.push_back(scenic::NewAddChildCmd(/*id=*/6, /*child_id=*/7));
// Check opacity value. Extra rounding required because we pass alpha as
// a uint/SkAlpha to SceneUpdateContext::Frame.
float opacity2 = kOneMinusEpsilon * (127 / 255.f) * (127 / 255.f);
opacity2 = SkScalarRoundToInt(opacity2 * 255) / 255.f;
expected.push_back(scenic::NewSetOpacityCmd(/*id=*/7, opacity2));
expected.push_back(scenic::NewSetClipPlanesCmd(/*id=*/6, /*ignored*/ {}));
expected.push_back(scenic::NewCreateShapeNodeCmd(/*id=*/8));
expected.push_back(scenic::NewCreateRectangleCmd(
/*id=*/9, /*width=*/10, /*height=*/10));
expected.push_back(scenic::NewSetShapeCmd(/*id=*/8, /*shape_id=*/9));
expected.push_back(scenic::NewSetTranslationCmd(/*id=*/8, {5, 5, 0}));
expected.push_back(scenic::NewCreateMaterialCmd(/*id=*/10));
expected.push_back(scenic::NewSetMaterialCmd(/*id=*/8,
/*material_id=*/10));
expected.push_back(scenic::NewAddChildCmd(/*id=*/6,
/*child_id=*/8));
expected.push_back(scenic::NewCreateImageCmd(/*id=*/11, 0, 0, {}));
expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/6));
expected.push_back(scenic::NewSetColorCmd(/*id=*/10, /*r*/ 255,
/*g*/ 255,
/*b*/ 255, /*a*/ 63));
expected.push_back(
scenic::NewSetTextureCmd(/*material_id=*/10, /*texture_id=*/11));
expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/10));
expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/9));
expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/8));
expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/7));
test_context->mock_session.SetExpectedCommands(std::move(expected));
// Finally, UpdateScene(). The MockSession will check the emitted
// commands against the list above.
root->UpdateScene(*(test_context->scene_update_context));
// Run loop until idle, so that the Session receives and processes
// its method calls.
async_loop_run_until_idle(
async_loop_from_dispatcher(async_get_default_dispatcher()));
// Ensure we saw enough commands.
EXPECT_EQ(39u, test_context->mock_session.num_enqueued_commands());
}
} // namespace testing
} // namespace flutter
......@@ -66,6 +66,11 @@ struct PrerollContext {
float total_elevation = 0.0f;
bool has_platform_view = false;
bool is_opaque = true;
#if defined(OS_FUCHSIA)
// True if, during the traversal so far, we have seen a child_scene_layer.
// Informs whether a layer needs to be system composited.
bool child_scene_layer_exists_below = false;
#endif // defined(OS_FUCHSIA)
};
// Represents a single composited layer. Created on the UI thread but then
......
......@@ -83,7 +83,7 @@ void LayerTree::UpdateScene(SceneUpdateContext& context,
context,
SkRRect::MakeRect(
SkRect::MakeWH(frame_size_.width(), frame_size_.height())),
SK_ColorTRANSPARENT, SK_AlphaOPAQUE);
SK_ColorTRANSPARENT, SK_AlphaOPAQUE, "flutter::LayerTree");
if (root_layer_->needs_system_composite()) {
root_layer_->UpdateScene(context);
}
......
......@@ -9,11 +9,6 @@
namespace flutter {
// The OpacityLayer has no real "elevation", but we want to avoid Z-fighting
// when using the system compositor. Choose a small but non-zero value for
// this.
constexpr float kOpacityElevationWhenUsingSystemCompositor = 0.01f;
OpacityLayer::OpacityLayer(SkAlpha alpha, const SkPoint& offset)
: alpha_(alpha), offset_(offset) {
// Ensure OpacityLayer has only one direct child.
......@@ -40,8 +35,6 @@ void OpacityLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
SkMatrix child_matrix = matrix;
child_matrix.postTranslate(offset_.fX, offset_.fY);
total_elevation_ = context->total_elevation;
context->total_elevation += kOpacityElevationWhenUsingSystemCompositor;
context->is_opaque = parent_is_opaque && (alpha_ == SK_AlphaOPAQUE);
context->mutators_stack.PushTransform(
SkMatrix::MakeTrans(offset_.fX, offset_.fY));
......@@ -52,17 +45,7 @@ void OpacityLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
context->mutators_stack.Pop();
context->mutators_stack.Pop();
context->is_opaque = parent_is_opaque;
context->total_elevation = total_elevation_;
#if defined(OS_FUCHSIA)
if (needs_system_composite()) {
// When using the system compositor, do not include the offset since we
// are rendering as a separate piece of geometry and the offset will be
// baked into that geometry's transform.
frameRRect_ = SkRRect::MakeRect(paint_bounds());
set_paint_bounds(SkRect::MakeEmpty());
} else
#endif
{
set_paint_bounds(paint_bounds().makeOffset(offset_.fX, offset_.fY));
if (!context->has_platform_view && context->raster_cache &&
......@@ -123,36 +106,10 @@ void OpacityLayer::Paint(PaintContext& context) const {
#if defined(OS_FUCHSIA)
void OpacityLayer::UpdateScene(SceneUpdateContext& context) {
FML_DCHECK(needs_system_composite());
TRACE_EVENT0("flutter", "OpacityLayer::UpdateScene");
ContainerLayer* container = GetChildContainer();
FML_DCHECK(!container->layers().empty()); // OpacityLayer can't be a leaf.
SceneUpdateContext::Transform transform(
context, SkMatrix::MakeTrans(offset_.fX, offset_.fY));
// Retained rendering: speedup by reusing a retained entity node if possible.
// When an entity node is reused, no paint layer is added to the frame so we
// won't call PhysicalShapeLayer::Paint.
LayerRasterCacheKey key(unique_id(), context.Matrix());
if (context.HasRetainedNode(key)) {
TRACE_EVENT_INSTANT0("flutter", "retained layer cache hit");
const scenic::EntityNode& retained_node = context.GetRetainedNode(key);
FML_DCHECK(context.top_entity());
FML_DCHECK(retained_node.session() == context.session());
context.top_entity()->embedder_node().AddChild(retained_node);
return;
}
TRACE_EVENT_INSTANT0("flutter", "cache miss, creating");
// If we can't find an existing retained surface, create one.
SceneUpdateContext::Frame frame(
context, frameRRect_, SK_ColorTRANSPARENT, alpha_,
kOpacityElevationWhenUsingSystemCompositor, total_elevation_, this);
frame.AddPaintLayer(container);
UpdateSceneChildren(context);
float saved_alpha = context.alphaf();
context.set_alphaf(context.alphaf() * (alpha_ / 255.f));
ContainerLayer::UpdateScene(context);
context.set_alphaf(saved_alpha);
}
#endif // defined(OS_FUCHSIA)
......
......@@ -43,7 +43,6 @@ class OpacityLayer : public ContainerLayer {
SkAlpha alpha_;
SkPoint offset_;
SkRRect frameRRect_;
float total_elevation_ = 0.0f;
FML_DISALLOW_COPY_AND_ASSIGN(OpacityLayer);
};
......
......@@ -52,23 +52,31 @@ void PhysicalShapeLayer::Preroll(PrerollContext* context,
context->total_elevation += elevation_;
total_elevation_ = context->total_elevation;
#if defined(OS_FUCHSIA)
child_layer_exists_below_ = context->child_scene_layer_exists_below;
context->child_scene_layer_exists_below = false;
#endif
SkRect child_paint_bounds;
PrerollChildren(context, matrix, &child_paint_bounds);
#if defined(OS_FUCHSIA)
if (child_layer_exists_below_) {
set_needs_system_composite(true);
}
context->child_scene_layer_exists_below =
context->child_scene_layer_exists_below || child_layer_exists_below_;
#endif
context->total_elevation -= elevation_;
if (elevation_ == 0) {
set_paint_bounds(path_.getBounds());
} else {
#if defined(OS_FUCHSIA)
// Let the system compositor draw all shadows for us.
set_needs_system_composite(true);
#else
// We will draw the shadow in Paint(), so add some margin to the paint
// bounds to leave space for the shadow. We fill this whole region and clip
// children to it so we don't need to join the child paint bounds.
set_paint_bounds(ComputeShadowBounds(path_.getBounds(), elevation_,
context->frame_device_pixel_ratio));
#endif // defined(OS_FUCHSIA)
}
}
......@@ -78,30 +86,52 @@ void PhysicalShapeLayer::UpdateScene(SceneUpdateContext& context) {
FML_DCHECK(needs_system_composite());
TRACE_EVENT0("flutter", "PhysicalShapeLayer::UpdateScene");
// Retained rendering: speedup by reusing a retained entity node if possible.
// When an entity node is reused, no paint layer is added to the frame so we
// won't call PhysicalShapeLayer::Paint.
LayerRasterCacheKey key(unique_id(), context.Matrix());
if (context.HasRetainedNode(key)) {
TRACE_EVENT_INSTANT0("flutter", "retained layer cache hit");
const scenic::EntityNode& retained_node = context.GetRetainedNode(key);
FML_DCHECK(context.top_entity());
FML_DCHECK(retained_node.session() == context.session());
context.top_entity()->entity_node().AddChild(retained_node);
return;
}
// If there is embedded Fuchsia content in the scene (a ChildSceneLayer),
// PhysicalShapeLayers that appear above the embedded content will be turned
// into their own Scenic layers.
if (child_layer_exists_below_) {
float global_scenic_elevation =
context.GetGlobalElevationForNextScenicLayer();
float local_scenic_elevation =
global_scenic_elevation - context.scenic_elevation();
float z_translation = -local_scenic_elevation;
// Retained rendering: speedup by reusing a retained entity node if
// possible. When an entity node is reused, no paint layer is added to the
// frame so we won't call PhysicalShapeLayer::Paint.
LayerRasterCacheKey key(unique_id(), context.Matrix());
if (context.HasRetainedNode(key)) {
TRACE_EVENT_INSTANT0("flutter", "retained layer cache hit");
scenic::EntityNode* retained_node = context.GetRetainedNode(key);
FML_DCHECK(context.top_entity());
FML_DCHECK(retained_node->session() == context.session());
// Re-adjust the elevation.
retained_node->SetTranslation(0.f, 0.f, z_translation);
TRACE_EVENT_INSTANT0("flutter", "cache miss, creating");
// If we can't find an existing retained surface, create one.
SceneUpdateContext::Frame frame(context, frameRRect_, color_, SK_AlphaOPAQUE,
elevation_, total_elevation_, this);
for (auto& layer : layers()) {
if (layer->needs_painting()) {
frame.AddPaintLayer(layer.get());
context.top_entity()->entity_node().AddChild(*retained_node);
return;
}
}
UpdateSceneChildren(context);
TRACE_EVENT_INSTANT0("flutter", "cache miss, creating");
// If we can't find an existing retained surface, create one.
SceneUpdateContext::Frame frame(context, frameRRect_, SK_ColorTRANSPARENT,
SkScalarRoundToInt(context.alphaf() * 255),
"flutter::PhysicalShapeLayer",
z_translation, this);
frame.AddPaintLayer(this);
// Node: UpdateSceneChildren needs to be called here so that |frame| is
// still in scope (and therefore alive) while UpdateSceneChildren is being
// called.
float scenic_elevation = context.scenic_elevation();
context.set_scenic_elevation(scenic_elevation + local_scenic_elevation);
ContainerLayer::UpdateSceneChildren(context);
context.set_scenic_elevation(scenic_elevation);
} else {
ContainerLayer::UpdateSceneChildren(context);
}
}
#endif // defined(OS_FUCHSIA)
......@@ -110,7 +140,18 @@ void PhysicalShapeLayer::Paint(PaintContext& context) const {
TRACE_EVENT0("flutter", "PhysicalShapeLayer::Paint");
FML_DCHECK(needs_painting());
#if defined(OS_FUCHSIA)
// TODO(mikejurka,dworsham,liyl): Re-enable shadow drawing here.
// Shadows are not rendered for PhysicalShapeLayers that exist as separate
// system services; this is to maintain compatibility with the previous
// implementation and has the added benefit of requiring smaller textures,
// since extra space is not needed for the shadows. This behavior might change
// after clients adjust their usage of PhysicalShaperLayer to make elevation
// correlate to desired shadow size.
if (false && !child_layer_exists_below_ && elevation_ != 0) {
#else
if (elevation_ != 0) {
#endif
DrawShadow(context.leaf_nodes_canvas, path_, shadow_color_, elevation_,
SkColorGetA(color_) != 0xff, context.frame_device_pixel_ratio);
}
......
......@@ -42,6 +42,9 @@ class PhysicalShapeLayer : public ContainerLayer {
float total_elevation() const { return total_elevation_; }
private:
#if defined(OS_FUCHSIA)
bool child_layer_exists_below_ = false;
#endif
SkColor color_;
SkColor shadow_color_;
float elevation_ = 0.0f;
......
......@@ -126,17 +126,11 @@ TEST_F(PhysicalShapeLayerTest, ElevationSimple) {
layer->Preroll(preroll_context(), SkMatrix());
// The Fuchsia system compositor handles all elevated PhysicalShapeLayers and
// their shadows , so we do not do any painting there.
#if defined(OS_FUCHSIA)
EXPECT_EQ(layer->paint_bounds(), kEmptyRect);
EXPECT_FALSE(layer->needs_painting());
EXPECT_TRUE(layer->needs_system_composite());
#else
EXPECT_EQ(layer->paint_bounds(),
PhysicalShapeLayer::ComputeShadowBounds(layer_path.getBounds(),
initial_elevation, 1.0f));
EXPECT_TRUE(layer->needs_painting());
EXPECT_FALSE(layer->needs_system_composite());
#endif
EXPECT_EQ(layer->total_elevation(), initial_elevation);
// The Fuchsia system compositor handles all elevated PhysicalShapeLayers and
......@@ -187,18 +181,12 @@ TEST_F(PhysicalShapeLayerTest, ElevationComplex) {
// On Fuchsia, the system compositor handles all elevated
// PhysicalShapeLayers and their shadows , so we do not do any painting
// there.
#if defined(OS_FUCHSIA)
EXPECT_EQ(layers[i]->paint_bounds(), kEmptyRect);
EXPECT_FALSE(layers[i]->needs_painting());
EXPECT_TRUE(layers[i]->needs_system_composite());
#else
EXPECT_EQ(layers[i]->paint_bounds(),
(PhysicalShapeLayer::ComputeShadowBounds(
layer_path.getBounds(), initial_elevations[i],
1.0f /* pixel_ratio */)));
EXPECT_TRUE(layers[i]->needs_painting());
EXPECT_FALSE(layers[i]->needs_system_composite());
#endif
EXPECT_EQ(layers[i]->total_elevation(), total_elevations[i]);
}
......
......@@ -56,8 +56,12 @@ void TransformLayer::UpdateScene(SceneUpdateContext& context) {
TRACE_EVENT0("flutter", "TransformLayer::UpdateScene");
FML_DCHECK(needs_system_composite());
SceneUpdateContext::Transform transform(context, transform_);
UpdateSceneChildren(context);
if (!transform_.isIdentity()) {
SceneUpdateContext::Transform transform(context, transform_);
UpdateSceneChildren(context);
} else {
UpdateSceneChildren(context);
}
}
#endif // defined(OS_FUCHSIA)
......
......@@ -72,14 +72,9 @@ void SceneUpdateContext::CreateFrame(scenic::EntityNode entity_node,
// and possibly for its texture.
// TODO(SCN-137): Need to be able to express the radii as vectors.
scenic::ShapeNode shape_node(session());
scenic::RoundedRectangle shape(
session_, // session
rrect.width(), // width
rrect.height(), // height
rrect.radii(SkRRect::kUpperLeft_Corner).x(), // top_left_radius
rrect.radii(SkRRect::kUpperRight_Corner).x(), // top_right_radius
rrect.radii(SkRRect::kLowerRight_Corner).x(), // bottom_right_radius
rrect.radii(SkRRect::kLowerLeft_Corner).x() // bottom_left_radius
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(),
......@@ -222,6 +217,9 @@ SceneUpdateContext::ExecutePaintTasks(CompositorContext::ScopedFrame& frame) {
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;
}
......@@ -244,19 +242,22 @@ SceneUpdateContext::Transform::Transform(SceneUpdateContext& context,
: 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(), //
-decomposition.translation().z() //
0.f //
);
entity_node().SetScale(decomposition.scale().x(), //
decomposition.scale().y(), //
decomposition.scale().z() //
0.f //
);
context.top_scale_x_ *= decomposition.scale().x();
context.top_scale_y_ *= decomposition.scale().y();
......@@ -277,6 +278,7 @@ SceneUpdateContext::Transform::Transform(SceneUpdateContext& context,
: 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;
......@@ -293,8 +295,8 @@ SceneUpdateContext::Frame::Frame(SceneUpdateContext& context,
const SkRRect& rrect,
SkColor color,
SkAlpha opacity,
float local_elevation,
float world_elevation,
std::string label,
float z_translation,
Layer* layer)
: Entity(context),
rrect_(rrect),
......@@ -303,23 +305,14 @@ SceneUpdateContext::Frame::Frame(SceneUpdateContext& context,
opacity_node_(context.session()),
paint_bounds_(SkRect::MakeEmpty()),
layer_(layer) {
const float depth = context.frame_physical_depth();
if (depth > -1 && world_elevation > depth) {
// TODO(mklim): Deal with bounds overflow more elegantly. We'd like to be
// able to have developers specify the behavior here to alternatives besides
// clamping, like normalization on some arbitrary curve.
// Clamp the local z coordinate at our max bound. Take into account the
// parent z position here to fix clamping in cases where the child is
// overflowing because of its parents.
const float parent_elevation = world_elevation - local_elevation;
local_elevation = depth - parent_elevation;
}
if (local_elevation != 0.0) {
entity_node().SetTranslation(0.f, 0.f, -local_elevation);
}
entity_node().SetLabel(label);
entity_node().SetTranslation(0.f, 0.f, z_translation);
entity_node().AddChild(opacity_node_);
opacity_node_.SetOpacity(opacity_ / 255.0f);
// 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() {
......@@ -348,6 +341,7 @@ void SceneUpdateContext::Frame::AddPaintLayer(Layer* layer) {
SceneUpdateContext::Clip::Clip(SceneUpdateContext& context,
const SkRect& shape_bounds)
: Entity(context) {
entity_node().SetLabel("flutter::Clip");
SetEntityNodeClipPlanes(entity_node(), shape_bounds);
}
......
......@@ -5,6 +5,7 @@
#ifndef FLUTTER_FLOW_SCENE_UPDATE_CONTEXT_H_
#define FLUTTER_FLOW_SCENE_UPDATE_CONTEXT_H_
#include <cfloat>
#include <memory>
#include <set>
#include <vector>
......@@ -22,6 +23,15 @@ namespace flutter {
class Layer;
// Scenic currently lacks an API to enable rendering of alpha channel; this only
// happens 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.
constexpr float kOneMinusEpsilon = 1 - FLT_EPSILON;
// How much layers are separated in Scenic z elevation.
constexpr float kScenicZElevationBetweenLayers = 10.f;
class SceneUpdateContext {
public:
class SurfaceProducerSurface {
......@@ -59,7 +69,7 @@ class SceneUpdateContext {
// Query a retained entity node (owned by a retained surface) for retained
// rendering.
virtual bool HasRetainedNode(const LayerRasterCacheKey& key) const = 0;
virtual const scenic::EntityNode& GetRetainedNode(
virtual scenic::EntityNode* GetRetainedNode(
const LayerRasterCacheKey& key) = 0;
virtual void SubmitSurface(
......@@ -105,8 +115,8 @@ class SceneUpdateContext {
const SkRRect& rrect,
SkColor color,
SkAlpha opacity,
float local_elevation = 0.0f,
float parent_elevation = 0.0f,
std::string label,
float z_translation = 0.0f,
Layer* layer = nullptr);
virtual ~Frame();
......@@ -175,10 +185,25 @@ class SceneUpdateContext {
bool HasRetainedNode(const LayerRasterCacheKey& key) const {
return surface_producer_->HasRetainedNode(key);
}
const scenic::EntityNode& GetRetainedNode(const LayerRasterCacheKey& key) {
scenic::EntityNode* GetRetainedNode(const LayerRasterCacheKey& key) {
return surface_producer_->GetRetainedNode(key);
}
// The cumulative alpha value based on all the parent OpacityLayers.
void set_alphaf(float alpha) { alpha_ = alpha; }
float alphaf() { return alpha_; }
// The global scenic elevation at a given point in the traversal.
float scenic_elevation() { return scenic_elevation_; }
void set_scenic_elevation(float elevation) { scenic_elevation_ = elevation; }
float GetGlobalElevationForNextScenicLayer() {
float elevation = topmost_global_scenic_elevation_;
topmost_global_scenic_elevation_ += kScenicZElevationBetweenLayers;
return elevation;
}
private:
struct PaintTask {
std::unique_ptr<SurfaceProducerSurface> surface;
......@@ -236,6 +261,10 @@ class SceneUpdateContext {
float frame_device_pixel_ratio_ =
1.0f; // Ratio between logical and physical pixels.
float alpha_ = 1.0f;
float scenic_elevation_ = 0.f;
float topmost_global_scenic_elevation_ = kScenicZElevationBetweenLayers;
std::vector<PaintTask> paint_tasks_;
FML_DISALLOW_COPY_AND_ASSIGN(SceneUpdateContext);
......
......@@ -102,13 +102,17 @@ ViewHolder::ViewHolder(fml::RefPtr<fml::TaskRunner> ui_task_runner,
void ViewHolder::UpdateScene(SceneUpdateContext& context,
const SkPoint& offset,
const SkSize& size,
SkAlpha opacity,
bool hit_testable) {
if (pending_view_holder_token_.value) {
entity_node_ = std::make_unique<scenic::EntityNode>(context.session());
opacity_node_ =
std::make_unique<scenic::OpacityNodeHACK>(context.session());
view_holder_ = std::make_unique<scenic::ViewHolder>(
context.session(), std::move(pending_view_holder_token_),
"Flutter SceneHost");
opacity_node_->AddChild(*entity_node_);
opacity_node_->SetLabel("flutter::ViewHolder");
entity_node_->Attach(*view_holder_);
ui_task_runner_->PostTask(
[bind_callback = std::move(pending_bind_callback_),
......@@ -117,9 +121,11 @@ void ViewHolder::UpdateScene(SceneUpdateContext& context,
});
}
FML_DCHECK(entity_node_);
FML_DCHECK(opacity_node_);
FML_DCHECK(view_holder_);
context.top_entity()->embedder_node().AddChild(*entity_node_);
context.top_entity()->embedder_node().AddChild(*opacity_node_);
opacity_node_->SetOpacity(opacity / 255.0f);
entity_node_->SetTranslation(offset.x(), offset.y(), -0.1f);
entity_node_->SetHitTestBehavior(
hit_testable ? fuchsia::ui::gfx::HitTestBehavior::kDefault
......
......@@ -57,12 +57,14 @@ class ViewHolder {
void UpdateScene(SceneUpdateContext& context,
const SkPoint& offset,
const SkSize& size,
SkAlpha opacity,
bool hit_testable);
private:
fml::RefPtr<fml::TaskRunner> ui_task_runner_;
std::unique_ptr<scenic::EntityNode> entity_node_;
std::unique_ptr<scenic::OpacityNodeHACK> opacity_node_;
std::unique_ptr<scenic::ViewHolder> view_holder_;
fuchsia::ui::views::ViewHolderToken pending_view_holder_token_;
......
......@@ -133,10 +133,11 @@ class VulkanSurface final
// For better safety in retained rendering, Flutter uses a retained
// |EntityNode| associated with the retained surface instead of using the
// retained surface directly. Hence Flutter can't modify the surface during
// retained rendering.
const scenic::EntityNode& GetRetainedNode() {
// retained rendering. However, the node itself is modifiable to be able
// to adjust its position.
scenic::EntityNode* GetRetainedNode() {
used_in_retained_rendering_ = true;
return *retained_node_;
return retained_node_.get();
}
// Check whether the retained surface (and its associated |EntityNode|) is
......
......@@ -43,8 +43,7 @@ class VulkanSurfacePool final {
return retained_surfaces_.find(key) != retained_surfaces_.end();
}
// For |VulkanSurfaceProducer::GetRetainedNode|.
const scenic::EntityNode& GetRetainedNode(
const flutter::LayerRasterCacheKey& key) {
scenic::EntityNode* GetRetainedNode(const flutter::LayerRasterCacheKey& key) {
FML_DCHECK(HasRetainedNode(key));
return retained_surfaces_[key].vk_surface->GetRetainedNode();
}
......
......@@ -49,7 +49,7 @@ class VulkanSurfaceProducer final
}
// |flutter::SceneUpdateContext::GetRetainedNode|
const scenic::EntityNode& GetRetainedNode(
scenic::EntityNode* GetRetainedNode(
const flutter::LayerRasterCacheKey& key) override {
return surface_pool_->GetRetainedNode(key);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册