From cc134bde5d0b15b2ce2d60ec20824fa1253ed695 Mon Sep 17 00:00:00 2001 From: Michael Jurka Date: Mon, 3 Feb 2020 17:21:22 -0800 Subject: [PATCH 1/6] [fuchsia] Add labels to Scenic nodes. --- flow/layers/layer_tree.cc | 2 +- flow/layers/opacity_layer.cc | 7 ++++--- flow/layers/physical_shape_layer.cc | 3 ++- flow/scene_update_context.cc | 5 +++++ flow/scene_update_context.h | 1 + 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/flow/layers/layer_tree.cc b/flow/layers/layer_tree.cc index 37c28e861bf8a..61560abbed5d4 100644 --- a/flow/layers/layer_tree.cc +++ b/flow/layers/layer_tree.cc @@ -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); } diff --git a/flow/layers/opacity_layer.cc b/flow/layers/opacity_layer.cc index e6ca90a66a42f..35eb6a6d52383 100644 --- a/flow/layers/opacity_layer.cc +++ b/flow/layers/opacity_layer.cc @@ -147,9 +147,10 @@ void OpacityLayer::UpdateScene(SceneUpdateContext& 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, alpha_, - kOpacityElevationWhenUsingSystemCompositor, total_elevation_, this); + SceneUpdateContext::Frame frame(context, frameRRect_, SK_ColorTRANSPARENT, + alpha_, "flutter::OpacityLayer", + kOpacityElevationWhenUsingSystemCompositor, + total_elevation_, this); frame.AddPaintLayer(container); UpdateSceneChildren(context); diff --git a/flow/layers/physical_shape_layer.cc b/flow/layers/physical_shape_layer.cc index c64049222a8e7..9f2e926348a1d 100644 --- a/flow/layers/physical_shape_layer.cc +++ b/flow/layers/physical_shape_layer.cc @@ -94,7 +94,8 @@ void PhysicalShapeLayer::UpdateScene(SceneUpdateContext& context) { 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); + "flutter::PhysicalShapeLayer", elevation_, + total_elevation_, this); for (auto& layer : layers()) { if (layer->needs_painting()) { frame.AddPaintLayer(layer.get()); diff --git a/flow/scene_update_context.cc b/flow/scene_update_context.cc index 59524bc9c6e56..cf2eaa1d66366 100644 --- a/flow/scene_update_context.cc +++ b/flow/scene_update_context.cc @@ -244,6 +244,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 (!transform.isIdentity()) { // TODO(SCN-192): The perspective and shear components in the matrix // are not handled correctly. @@ -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,6 +295,7 @@ SceneUpdateContext::Frame::Frame(SceneUpdateContext& context, const SkRRect& rrect, SkColor color, SkAlpha opacity, + std::string label, float local_elevation, float world_elevation, Layer* layer) @@ -318,6 +321,7 @@ SceneUpdateContext::Frame::Frame(SceneUpdateContext& context, if (local_elevation != 0.0) { entity_node().SetTranslation(0.f, 0.f, -local_elevation); } + entity_node().SetLabel(label); entity_node().AddChild(opacity_node_); opacity_node_.SetOpacity(opacity_ / 255.0f); } @@ -348,6 +352,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); } diff --git a/flow/scene_update_context.h b/flow/scene_update_context.h index 77b0925cc6503..c639f6edfd2ee 100644 --- a/flow/scene_update_context.h +++ b/flow/scene_update_context.h @@ -105,6 +105,7 @@ class SceneUpdateContext { const SkRRect& rrect, SkColor color, SkAlpha opacity, + std::string label, float local_elevation = 0.0f, float parent_elevation = 0.0f, Layer* layer = nullptr); From 2dc66a3b45ab89735068c6a51451a85e7446b690 Mon Sep 17 00:00:00 2001 From: Michael Jurka Date: Fri, 6 Mar 2020 21:23:05 -0800 Subject: [PATCH 2/6] [fuchsia] Skip creating Scenic nodes for identity Transforms. --- flow/layers/transform_layer.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/flow/layers/transform_layer.cc b/flow/layers/transform_layer.cc index 5c7bc44073236..dea9b8f18ebd3 100644 --- a/flow/layers/transform_layer.cc +++ b/flow/layers/transform_layer.cc @@ -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) From 5b05181048a9e9d9c69421732552f41bc20ce3ff Mon Sep 17 00:00:00 2001 From: Michael Jurka Date: Fri, 6 Mar 2020 21:34:53 -0800 Subject: [PATCH 3/6] [fuchsia] Assign elevation to Scenic nodes based on paint order. --- flow/layers/opacity_layer.cc | 28 ++++++++++++++---- flow/layers/opacity_layer.h | 5 ++++ flow/layers/physical_shape_layer.cc | 29 +++++++++++++++---- flow/layers/physical_shape_layer.h | 5 ++++ flow/scene_update_context.cc | 27 +++++------------ flow/scene_update_context.h | 22 ++++++++++---- .../platform/fuchsia/flutter/vulkan_surface.h | 4 +-- .../fuchsia/flutter/vulkan_surface_pool.h | 2 +- .../fuchsia/flutter/vulkan_surface_producer.h | 2 +- 9 files changed, 85 insertions(+), 39 deletions(-) diff --git a/flow/layers/opacity_layer.cc b/flow/layers/opacity_layer.cc index 35eb6a6d52383..b347e329783ac 100644 --- a/flow/layers/opacity_layer.cc +++ b/flow/layers/opacity_layer.cc @@ -126,6 +126,12 @@ void OpacityLayer::UpdateScene(SceneUpdateContext& context) { FML_DCHECK(needs_system_composite()); TRACE_EVENT0("flutter", "OpacityLayer::UpdateScene"); + float global_scenic_elevation = + context.GetGlobalElevationForNextScenicLayer(); + float local_scenic_elevation_ = + global_scenic_elevation - context.scenic_elevation(); + float z_translation = -local_scenic_elevation_; + ContainerLayer* container = GetChildContainer(); FML_DCHECK(!container->layers().empty()); // OpacityLayer can't be a leaf. @@ -138,10 +144,14 @@ void OpacityLayer::UpdateScene(SceneUpdateContext& context) { 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); + 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); + FML_DCHECK(retained_node->session() == context.session()); + + // Re-adjust the elevation. + retained_node->SetTranslation(0.f, 0.f, z_translation); + + context.top_entity()->embedder_node().AddChild(*retained_node); return; } @@ -149,13 +159,21 @@ void OpacityLayer::UpdateScene(SceneUpdateContext& context) { // If we can't find an existing retained surface, create one. SceneUpdateContext::Frame frame(context, frameRRect_, SK_ColorTRANSPARENT, alpha_, "flutter::OpacityLayer", - kOpacityElevationWhenUsingSystemCompositor, - total_elevation_, this); + z_translation, this); frame.AddPaintLayer(container); UpdateSceneChildren(context); } +void OpacityLayer::UpdateSceneChildren(SceneUpdateContext& context) { + float scenic_elevation = context.scenic_elevation(); + context.set_scenic_elevation(scenic_elevation + local_scenic_elevation_); + + ContainerLayer::UpdateSceneChildren(context); + + context.set_scenic_elevation(scenic_elevation); +} + #endif // defined(OS_FUCHSIA) ContainerLayer* OpacityLayer::GetChildContainer() const { diff --git a/flow/layers/opacity_layer.h b/flow/layers/opacity_layer.h index efc5dd4f83e04..29d2aa5d9a5b3 100644 --- a/flow/layers/opacity_layer.h +++ b/flow/layers/opacity_layer.h @@ -35,9 +35,14 @@ class OpacityLayer : public ContainerLayer { #if defined(OS_FUCHSIA) void UpdateScene(SceneUpdateContext& context) override; + + void UpdateSceneChildren(SceneUpdateContext& context); #endif // defined(OS_FUCHSIA) private: +#if defined(OS_FUCHSIA) + float local_scenic_elevation_ = 0.0f; +#endif ContainerLayer* GetChildContainer() const; SkAlpha alpha_; diff --git a/flow/layers/physical_shape_layer.cc b/flow/layers/physical_shape_layer.cc index 9f2e926348a1d..b6013a621bd1b 100644 --- a/flow/layers/physical_shape_layer.cc +++ b/flow/layers/physical_shape_layer.cc @@ -78,31 +78,48 @@ void PhysicalShapeLayer::UpdateScene(SceneUpdateContext& context) { FML_DCHECK(needs_system_composite()); TRACE_EVENT0("flutter", "PhysicalShapeLayer::UpdateScene"); + 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"); - const scenic::EntityNode& retained_node = context.GetRetainedNode(key); + 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); + FML_DCHECK(retained_node->session() == context.session()); + + // Re-adjust the elevation. + retained_node->SetTranslation(0.f, 0.f, z_translation); + + context.top_entity()->entity_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_, color_, SK_AlphaOPAQUE, - "flutter::PhysicalShapeLayer", elevation_, - total_elevation_, this); + "flutter::PhysicalShapeLayer", z_translation, + this); for (auto& layer : layers()) { if (layer->needs_painting()) { frame.AddPaintLayer(layer.get()); } } +} + +void PhysicalShapeLayer::UpdateSceneChildren(SceneUpdateContext& context) { + float scenic_elevation = context.scenic_elevation(); + context.set_scenic_elevation(scenic_elevation + local_scenic_elevation_); + + ContainerLayer::UpdateSceneChildren(context); - UpdateSceneChildren(context); + context.set_scenic_elevation(scenic_elevation); } #endif // defined(OS_FUCHSIA) diff --git a/flow/layers/physical_shape_layer.h b/flow/layers/physical_shape_layer.h index 40ff19cfac348..cde43c09adc25 100644 --- a/flow/layers/physical_shape_layer.h +++ b/flow/layers/physical_shape_layer.h @@ -37,11 +37,16 @@ class PhysicalShapeLayer : public ContainerLayer { #if defined(OS_FUCHSIA) void UpdateScene(SceneUpdateContext& context) override; + + void UpdateSceneChildren(SceneUpdateContext& context); #endif // defined(OS_FUCHSIA) float total_elevation() const { return total_elevation_; } private: +#if defined(OS_FUCHSIA) + float local_scenic_elevation_ = 0.0f; +#endif SkColor color_; SkColor shadow_color_; float elevation_ = 0.0f; diff --git a/flow/scene_update_context.cc b/flow/scene_update_context.cc index cf2eaa1d66366..46679fa663165 100644 --- a/flow/scene_update_context.cc +++ b/flow/scene_update_context.cc @@ -222,6 +222,8 @@ SceneUpdateContext::ExecutePaintTasks(CompositorContext::ScopedFrame& frame) { surfaces_to_submit.emplace_back(std::move(task.surface)); } paint_tasks_.clear(); + topmost_global_scenic_elevation_ = 10.f; + scenic_elevation_ = 0.f; return surfaces_to_submit; } @@ -250,14 +252,16 @@ SceneUpdateContext::Transform::Transform(SceneUpdateContext& context, // 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(); @@ -296,8 +300,7 @@ SceneUpdateContext::Frame::Frame(SceneUpdateContext& context, SkColor color, SkAlpha opacity, std::string label, - float local_elevation, - float world_elevation, + float z_translation, Layer* layer) : Entity(context), rrect_(rrect), @@ -306,22 +309,8 @@ 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); } diff --git a/flow/scene_update_context.h b/flow/scene_update_context.h index c639f6edfd2ee..6dded897d8988 100644 --- a/flow/scene_update_context.h +++ b/flow/scene_update_context.h @@ -59,8 +59,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( - const LayerRasterCacheKey& key) = 0; + virtual scenic::EntityNode* GetRetainedNode(const LayerRasterCacheKey& key) = 0; virtual void SubmitSurface( std::unique_ptr surface) = 0; @@ -106,8 +105,7 @@ class SceneUpdateContext { SkColor color, SkAlpha opacity, std::string label, - float local_elevation = 0.0f, - float parent_elevation = 0.0f, + float z_translation = 0.0f, Layer* layer = nullptr); virtual ~Frame(); @@ -176,10 +174,21 @@ 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 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_ += 10.f; + return elevation; + } + private: struct PaintTask { std::unique_ptr surface; @@ -237,6 +246,9 @@ class SceneUpdateContext { float frame_device_pixel_ratio_ = 1.0f; // Ratio between logical and physical pixels. + float scenic_elevation_ = 0.f; + float topmost_global_scenic_elevation_ = 10.f; + std::vector paint_tasks_; FML_DISALLOW_COPY_AND_ASSIGN(SceneUpdateContext); diff --git a/shell/platform/fuchsia/flutter/vulkan_surface.h b/shell/platform/fuchsia/flutter/vulkan_surface.h index 4c67c9d5538c2..64dae4c4f9534 100644 --- a/shell/platform/fuchsia/flutter/vulkan_surface.h +++ b/shell/platform/fuchsia/flutter/vulkan_surface.h @@ -134,9 +134,9 @@ class VulkanSurface final // |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() { + 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 diff --git a/shell/platform/fuchsia/flutter/vulkan_surface_pool.h b/shell/platform/fuchsia/flutter/vulkan_surface_pool.h index 00b40437de612..f3fb6727aed94 100644 --- a/shell/platform/fuchsia/flutter/vulkan_surface_pool.h +++ b/shell/platform/fuchsia/flutter/vulkan_surface_pool.h @@ -43,7 +43,7 @@ class VulkanSurfacePool final { return retained_surfaces_.find(key) != retained_surfaces_.end(); } // For |VulkanSurfaceProducer::GetRetainedNode|. - const scenic::EntityNode& GetRetainedNode( + scenic::EntityNode* GetRetainedNode( const flutter::LayerRasterCacheKey& key) { FML_DCHECK(HasRetainedNode(key)); return retained_surfaces_[key].vk_surface->GetRetainedNode(); diff --git a/shell/platform/fuchsia/flutter/vulkan_surface_producer.h b/shell/platform/fuchsia/flutter/vulkan_surface_producer.h index 77457bac32cdd..5ea1415cf537f 100644 --- a/shell/platform/fuchsia/flutter/vulkan_surface_producer.h +++ b/shell/platform/fuchsia/flutter/vulkan_surface_producer.h @@ -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); } From af31acd4e795dcef5db32643497b2005648aeee9 Mon Sep 17 00:00:00 2001 From: Michael Jurka Date: Fri, 6 Mar 2020 21:49:57 -0800 Subject: [PATCH 4/6] [fuchsia] Create Scenic OpacityNodes at leaf nodes. --- flow/layers/child_scene_layer.cc | 4 +- flow/layers/opacity_layer.cc | 70 ++--------------------------- flow/layers/opacity_layer.h | 6 --- flow/layers/physical_shape_layer.cc | 6 +-- flow/scene_update_context.cc | 1 + flow/scene_update_context.h | 5 +++ flow/view_holder.cc | 9 +++- flow/view_holder.h | 2 + 8 files changed, 25 insertions(+), 78 deletions(-) diff --git a/flow/layers/child_scene_layer.cc b/flow/layers/child_scene_layer.cc index 4a5358053928f..43f7ee5e402c7 100644 --- a/flow/layers/child_scene_layer.cc +++ b/flow/layers/child_scene_layer.cc @@ -49,7 +49,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_, + sk_float_round2int(context.alphaf() * 255), + hit_testable_); } } // namespace flutter diff --git a/flow/layers/opacity_layer.cc b/flow/layers/opacity_layer.cc index b347e329783ac..7899c31784b89 100644 --- a/flow/layers/opacity_layer.cc +++ b/flow/layers/opacity_layer.cc @@ -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,55 +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"); - - float global_scenic_elevation = - context.GetGlobalElevationForNextScenicLayer(); - float local_scenic_elevation_ = - global_scenic_elevation - context.scenic_elevation(); - float z_translation = -local_scenic_elevation_; - - 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"); - 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); - - 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_, "flutter::OpacityLayer", - z_translation, this); - frame.AddPaintLayer(container); - - UpdateSceneChildren(context); -} - -void OpacityLayer::UpdateSceneChildren(SceneUpdateContext& context) { - float scenic_elevation = context.scenic_elevation(); - context.set_scenic_elevation(scenic_elevation + local_scenic_elevation_); - - ContainerLayer::UpdateSceneChildren(context); - - context.set_scenic_elevation(scenic_elevation); + 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) diff --git a/flow/layers/opacity_layer.h b/flow/layers/opacity_layer.h index 29d2aa5d9a5b3..658dd1a7b8488 100644 --- a/flow/layers/opacity_layer.h +++ b/flow/layers/opacity_layer.h @@ -35,20 +35,14 @@ class OpacityLayer : public ContainerLayer { #if defined(OS_FUCHSIA) void UpdateScene(SceneUpdateContext& context) override; - - void UpdateSceneChildren(SceneUpdateContext& context); #endif // defined(OS_FUCHSIA) private: -#if defined(OS_FUCHSIA) - float local_scenic_elevation_ = 0.0f; -#endif ContainerLayer* GetChildContainer() const; SkAlpha alpha_; SkPoint offset_; SkRRect frameRRect_; - float total_elevation_ = 0.0f; FML_DISALLOW_COPY_AND_ASSIGN(OpacityLayer); }; diff --git a/flow/layers/physical_shape_layer.cc b/flow/layers/physical_shape_layer.cc index b6013a621bd1b..c894e275d5e8c 100644 --- a/flow/layers/physical_shape_layer.cc +++ b/flow/layers/physical_shape_layer.cc @@ -103,9 +103,9 @@ void PhysicalShapeLayer::UpdateScene(SceneUpdateContext& context) { 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, - "flutter::PhysicalShapeLayer", z_translation, - this); + SceneUpdateContext::Frame frame( + context, frameRRect_, color_, sk_float_round2int(context.alphaf() * 255), + "flutter::PhysicalShapeLayer", z_translation, this); for (auto& layer : layers()) { if (layer->needs_painting()) { frame.AddPaintLayer(layer.get()); diff --git a/flow/scene_update_context.cc b/flow/scene_update_context.cc index 46679fa663165..b41a521848bc5 100644 --- a/flow/scene_update_context.cc +++ b/flow/scene_update_context.cc @@ -222,6 +222,7 @@ SceneUpdateContext::ExecutePaintTasks(CompositorContext::ScopedFrame& frame) { surfaces_to_submit.emplace_back(std::move(task.surface)); } paint_tasks_.clear(); + alpha_ = 1.f; topmost_global_scenic_elevation_ = 10.f; scenic_elevation_ = 0.f; return surfaces_to_submit; diff --git a/flow/scene_update_context.h b/flow/scene_update_context.h index 6dded897d8988..e56ee28de0866 100644 --- a/flow/scene_update_context.h +++ b/flow/scene_update_context.h @@ -178,6 +178,10 @@ class SceneUpdateContext { 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_; } @@ -246,6 +250,7 @@ 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_ = 10.f; diff --git a/flow/view_holder.cc b/flow/view_holder.cc index 7f8929d933705..6962eeae7cbfa 100644 --- a/flow/view_holder.cc +++ b/flow/view_holder.cc @@ -102,13 +102,16 @@ ViewHolder::ViewHolder(fml::RefPtr 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(context.session()); + opacity_node_ = std::make_unique(context.session()); view_holder_ = std::make_unique( 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 +120,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 diff --git a/flow/view_holder.h b/flow/view_holder.h index 82d43eba826d4..10677b8157680 100644 --- a/flow/view_holder.h +++ b/flow/view_holder.h @@ -57,12 +57,14 @@ class ViewHolder { void UpdateScene(SceneUpdateContext& context, const SkPoint& offset, const SkSize& size, + SkAlpha opacity, bool hit_testable); private: fml::RefPtr ui_task_runner_; std::unique_ptr entity_node_; + std::unique_ptr opacity_node_; std::unique_ptr view_holder_; fuchsia::ui::views::ViewHolderToken pending_view_holder_token_; From b2921e9acb9f6416296ae2f6d797b00472872053 Mon Sep 17 00:00:00 2001 From: Michael Jurka Date: Fri, 6 Mar 2020 21:54:27 -0800 Subject: [PATCH 5/6] [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]. --- flow/layers/child_scene_layer.cc | 1 + flow/layers/layer.h | 5 ++ flow/layers/physical_shape_layer.cc | 107 +++++++++++++++++++--------- flow/layers/physical_shape_layer.h | 2 + flow/scene_update_context.cc | 22 +++--- 5 files changed, 93 insertions(+), 44 deletions(-) diff --git a/flow/layers/child_scene_layer.cc b/flow/layers/child_scene_layer.cc index 43f7ee5e402c7..cec30f7b3d05d 100644 --- a/flow/layers/child_scene_layer.cc +++ b/flow/layers/child_scene_layer.cc @@ -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) { diff --git a/flow/layers/layer.h b/flow/layers/layer.h index 50e2b0f7e68cf..dfd022a350a0f 100644 --- a/flow/layers/layer.h +++ b/flow/layers/layer.h @@ -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 diff --git a/flow/layers/physical_shape_layer.cc b/flow/layers/physical_shape_layer.cc index c894e275d5e8c..af1b6dc14f65b 100644 --- a/flow/layers/physical_shape_layer.cc +++ b/flow/layers/physical_shape_layer.cc @@ -52,23 +52,32 @@ 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) + children_need_system_compositing_ = needs_system_composite(); + 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,37 +87,53 @@ void PhysicalShapeLayer::UpdateScene(SceneUpdateContext& context) { FML_DCHECK(needs_system_composite()); TRACE_EVENT0("flutter", "PhysicalShapeLayer::UpdateScene"); - 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); - - 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_) { + // Reset paint bounds, because shadows are not rendered in this case + // (see comment in Paint()). + set_paint_bounds(path_.getBounds()); - 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_float_round2int(context.alphaf() * 255), - "flutter::PhysicalShapeLayer", z_translation, this); - for (auto& layer : layers()) { - if (layer->needs_painting()) { - frame.AddPaintLayer(layer.get()); + 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); + + context.top_entity()->entity_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, + sk_float_round2int(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. + if (children_need_system_compositing_) { + UpdateSceneChildren(context); + } + } else { + if (children_need_system_compositing_) { + UpdateSceneChildren(context); } } } @@ -128,7 +153,19 @@ void PhysicalShapeLayer::Paint(PaintContext& context) const { TRACE_EVENT0("flutter", "PhysicalShapeLayer::Paint"); FML_DCHECK(needs_painting()); + +#if defined(OS_FUCHSIA) + // TODO: 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); } diff --git a/flow/layers/physical_shape_layer.h b/flow/layers/physical_shape_layer.h index cde43c09adc25..b3ccfc4db1e8c 100644 --- a/flow/layers/physical_shape_layer.h +++ b/flow/layers/physical_shape_layer.h @@ -46,6 +46,8 @@ class PhysicalShapeLayer : public ContainerLayer { private: #if defined(OS_FUCHSIA) float local_scenic_elevation_ = 0.0f; + bool child_layer_exists_below_ = false; + bool children_need_system_compositing_ = false; #endif SkColor color_; SkColor shadow_color_; diff --git a/flow/scene_update_context.cc b/flow/scene_update_context.cc index b41a521848bc5..1d8b536375221 100644 --- a/flow/scene_update_context.cc +++ b/flow/scene_update_context.cc @@ -4,6 +4,7 @@ #include "flutter/flow/scene_update_context.h" +#include #include "flutter/flow/layers/layer.h" #include "flutter/flow/matrix_decomposition.h" #include "flutter/fml/trace_event.h" @@ -72,14 +73,13 @@ 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::RoundedRectangle shape(session_, // session + rrect.width(), // width + rrect.height(), // height + 0.f, // top_left_radius + 0.f, // top_right_radius + 0.f, // bottom_right_radius + 0.f // bottom_left_radius ); shape_node.SetShape(shape); shape_node.SetTranslation(shape_bounds.width() * 0.5f + shape_bounds.left(), @@ -313,7 +313,11 @@ SceneUpdateContext::Frame::Frame(SceneUpdateContext& context, 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; 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. + opacity_node_.SetOpacity(std::min(1 - FLT_EPSILON, opacity_ / 255.0f)); } SceneUpdateContext::Frame::~Frame() { From 4c1b19586c3d1e026c1a2058fe59b9f42a71f3de Mon Sep 17 00:00:00 2001 From: Michael Jurka Date: Wed, 11 Mar 2020 21:16:41 -0700 Subject: [PATCH 6/6] [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. --- ci/licenses_golden/licenses_flutter | 1 + flow/BUILD.gn | 9 + flow/layers/child_scene_layer.cc | 2 +- flow/layers/fuchsia_layer_unittests.cc | 764 ++++++++++++++++++ flow/layers/physical_shape_layer.cc | 52 +- flow/layers/physical_shape_layer.h | 4 - flow/layers/physical_shape_layer_unittests.cc | 12 - flow/scene_update_context.cc | 27 +- flow/scene_update_context.h | 17 +- flow/view_holder.cc | 3 +- .../platform/fuchsia/flutter/vulkan_surface.h | 3 +- .../fuchsia/flutter/vulkan_surface_pool.h | 3 +- 12 files changed, 824 insertions(+), 73 deletions(-) create mode 100644 flow/layers/fuchsia_layer_unittests.cc diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 7a06e1efa04bd..5f02f7592b679 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -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 diff --git a/flow/BUILD.gn b/flow/BUILD.gn index 14ceec4dadc5c..e25b5dc8957a3 100644 --- a/flow/BUILD.gn +++ b/flow/BUILD.gn @@ -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) { diff --git a/flow/layers/child_scene_layer.cc b/flow/layers/child_scene_layer.cc index cec30f7b3d05d..4e533130c1d4b 100644 --- a/flow/layers/child_scene_layer.cc +++ b/flow/layers/child_scene_layer.cc @@ -51,7 +51,7 @@ void ChildSceneLayer::UpdateScene(SceneUpdateContext& context) { FML_DCHECK(view_holder); view_holder->UpdateScene(context, offset_, size_, - sk_float_round2int(context.alphaf() * 255), + SkScalarRoundToInt(context.alphaf() * 255), hit_testable_); } diff --git a/flow/layers/fuchsia_layer_unittests.cc b/flow/layers/fuchsia_layer_unittests.cc new file mode 100644 index 0000000000000..2a859226ab5ed --- /dev/null +++ b/flow/layers/fuchsia_layer_unittests.cc @@ -0,0 +1,764 @@ +// 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 + +#include "gtest/gtest.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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 gfx_cmds) { + std::deque 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 expected_; + size_t num_enqueued_commands_ = 0; + fidl::Binding 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& on_writes_committed) override {} + + scenic::Image* GetImage() override { return &image_; }; + + sk_sp 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 ProduceSurface( + const SkISize& size, + const LayerRasterCacheKey& layer_key, + std::unique_ptr entity_node) override { + return std::make_unique(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 + surface) override {} + + private: + scenic::Session* session_; +}; + +struct TestContext { + // Message loop. + fml::RefPtr loop; + fml::RefPtr task_runner; + + // Session. + MockSession mock_session; + fidl::InterfaceRequest listener_request; + std::unique_ptr session; + + // SceneUpdateContext. + std::unique_ptr mock_surface_producer; + std::unique_ptr scene_update_context; + + // PrerollContext. + MutatorsStack unused_stack; + const Stopwatch unused_stopwatch; + TextureRegistry unused_texture_registry; + std::unique_ptr preroll_context; +}; + +std::unique_ptr InitTest() { + std::unique_ptr context = std::make_unique(); + + // Init message loop. + context->loop = fml::MakeRefCounted(); + context->task_runner = fml::MakeRefCounted(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(std::move(session_ptr)); + + // Init SceneUpdateContext. + context->mock_surface_producer = + std::make_unique(context->session.get()); + context->scene_update_context = std::make_unique( + 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(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(); + SkPath path; + path.addRect(SkRect::MakeWH(10.f, 10.f)); + + // Child #1: PhysicalShapeLayer. + auto physical_shape1 = std::make_shared( + /*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( + 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( + /*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( + /*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( + 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( + /*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 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(); + SkPath path; + path.addRect(SkRect::MakeWH(10.f, 10.f)); + + // OpacityLayer #1 + auto opacity_layer1 = + std::make_shared(127, SkPoint::Make(0, 0)); + root->Add(opacity_layer1); + + // OpacityLayer #2 + auto opacity_layer2 = + std::make_shared(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( + 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( + /*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 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 diff --git a/flow/layers/physical_shape_layer.cc b/flow/layers/physical_shape_layer.cc index af1b6dc14f65b..15bad4f5e1f6b 100644 --- a/flow/layers/physical_shape_layer.cc +++ b/flow/layers/physical_shape_layer.cc @@ -55,13 +55,12 @@ void PhysicalShapeLayer::Preroll(PrerollContext* context, #if defined(OS_FUCHSIA) child_layer_exists_below_ = context->child_scene_layer_exists_below; context->child_scene_layer_exists_below = false; - #endif +#endif SkRect child_paint_bounds; PrerollChildren(context, matrix, &child_paint_bounds); #if defined(OS_FUCHSIA) - children_need_system_compositing_ = needs_system_composite(); if (child_layer_exists_below_) { set_needs_system_composite(true); } @@ -91,14 +90,11 @@ void PhysicalShapeLayer::UpdateScene(SceneUpdateContext& context) { // PhysicalShapeLayers that appear above the embedded content will be turned // into their own Scenic layers. if (child_layer_exists_below_) { - // Reset paint bounds, because shadows are not rendered in this case - // (see comment in Paint()). - set_paint_bounds(path_.getBounds()); - - float global_scenic_elevation = context.GetGlobalElevationForNextScenicLayer(); - float local_scenic_elevation_ = + float global_scenic_elevation = + context.GetGlobalElevationForNextScenicLayer(); + float local_scenic_elevation = global_scenic_elevation - context.scenic_elevation(); - float z_translation = -local_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 @@ -120,48 +116,38 @@ void PhysicalShapeLayer::UpdateScene(SceneUpdateContext& 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, - sk_float_round2int(context.alphaf() * 255), + 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. - if (children_need_system_compositing_) { - UpdateSceneChildren(context); - } + // 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 { - if (children_need_system_compositing_) { - UpdateSceneChildren(context); - } + ContainerLayer::UpdateSceneChildren(context); } } -void PhysicalShapeLayer::UpdateSceneChildren(SceneUpdateContext& context) { - float scenic_elevation = context.scenic_elevation(); - context.set_scenic_elevation(scenic_elevation + local_scenic_elevation_); - - ContainerLayer::UpdateSceneChildren(context); - - context.set_scenic_elevation(scenic_elevation); -} - #endif // defined(OS_FUCHSIA) void PhysicalShapeLayer::Paint(PaintContext& context) const { TRACE_EVENT0("flutter", "PhysicalShapeLayer::Paint"); FML_DCHECK(needs_painting()); - #if defined(OS_FUCHSIA) - // TODO: Re-enable shadow drawing here. + // 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. + // 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) { diff --git a/flow/layers/physical_shape_layer.h b/flow/layers/physical_shape_layer.h index b3ccfc4db1e8c..106327f47ec9c 100644 --- a/flow/layers/physical_shape_layer.h +++ b/flow/layers/physical_shape_layer.h @@ -37,17 +37,13 @@ class PhysicalShapeLayer : public ContainerLayer { #if defined(OS_FUCHSIA) void UpdateScene(SceneUpdateContext& context) override; - - void UpdateSceneChildren(SceneUpdateContext& context); #endif // defined(OS_FUCHSIA) float total_elevation() const { return total_elevation_; } private: #if defined(OS_FUCHSIA) - float local_scenic_elevation_ = 0.0f; bool child_layer_exists_below_ = false; - bool children_need_system_compositing_ = false; #endif SkColor color_; SkColor shadow_color_; diff --git a/flow/layers/physical_shape_layer_unittests.cc b/flow/layers/physical_shape_layer_unittests.cc index d13116c9c44a0..df247e394f1f9 100644 --- a/flow/layers/physical_shape_layer_unittests.cc +++ b/flow/layers/physical_shape_layer_unittests.cc @@ -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]); } diff --git a/flow/scene_update_context.cc b/flow/scene_update_context.cc index 1d8b536375221..407751406bfd6 100644 --- a/flow/scene_update_context.cc +++ b/flow/scene_update_context.cc @@ -4,7 +4,6 @@ #include "flutter/flow/scene_update_context.h" -#include #include "flutter/flow/layers/layer.h" #include "flutter/flow/matrix_decomposition.h" #include "flutter/fml/trace_event.h" @@ -73,13 +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 - 0.f, // top_left_radius - 0.f, // top_right_radius - 0.f, // bottom_right_radius - 0.f // 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(), @@ -223,7 +218,7 @@ SceneUpdateContext::ExecutePaintTasks(CompositorContext::ScopedFrame& frame) { } paint_tasks_.clear(); alpha_ = 1.f; - topmost_global_scenic_elevation_ = 10.f; + topmost_global_scenic_elevation_ = kScenicZElevationBetweenLayers; scenic_elevation_ = 0.f; return surfaces_to_submit; } @@ -257,12 +252,12 @@ SceneUpdateContext::Transform::Transform(SceneUpdateContext& context, // instead to make sure layers appear in proper order. entity_node().SetTranslation(decomposition.translation().x(), // decomposition.translation().y(), // - 0.f // + 0.f // ); entity_node().SetScale(decomposition.scale().x(), // decomposition.scale().y(), // - 0.f // + 0.f // ); context.top_scale_x_ *= decomposition.scale().x(); context.top_scale_y_ *= decomposition.scale().y(); @@ -313,11 +308,11 @@ SceneUpdateContext::Frame::Frame(SceneUpdateContext& context, 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; 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. - opacity_node_.SetOpacity(std::min(1 - FLT_EPSILON, 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() { diff --git a/flow/scene_update_context.h b/flow/scene_update_context.h index e56ee28de0866..a4d1156cc6021 100644 --- a/flow/scene_update_context.h +++ b/flow/scene_update_context.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_FLOW_SCENE_UPDATE_CONTEXT_H_ #define FLUTTER_FLOW_SCENE_UPDATE_CONTEXT_H_ +#include #include #include #include @@ -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,8 @@ class SceneUpdateContext { // Query a retained entity node (owned by a retained surface) for retained // rendering. virtual bool HasRetainedNode(const LayerRasterCacheKey& key) const = 0; - virtual scenic::EntityNode* GetRetainedNode(const LayerRasterCacheKey& key) = 0; + virtual scenic::EntityNode* GetRetainedNode( + const LayerRasterCacheKey& key) = 0; virtual void SubmitSurface( std::unique_ptr surface) = 0; @@ -189,7 +200,7 @@ class SceneUpdateContext { float GetGlobalElevationForNextScenicLayer() { float elevation = topmost_global_scenic_elevation_; - topmost_global_scenic_elevation_ += 10.f; + topmost_global_scenic_elevation_ += kScenicZElevationBetweenLayers; return elevation; } @@ -252,7 +263,7 @@ class SceneUpdateContext { float alpha_ = 1.0f; float scenic_elevation_ = 0.f; - float topmost_global_scenic_elevation_ = 10.f; + float topmost_global_scenic_elevation_ = kScenicZElevationBetweenLayers; std::vector paint_tasks_; diff --git a/flow/view_holder.cc b/flow/view_holder.cc index 6962eeae7cbfa..66ce3672a8431 100644 --- a/flow/view_holder.cc +++ b/flow/view_holder.cc @@ -106,7 +106,8 @@ void ViewHolder::UpdateScene(SceneUpdateContext& context, bool hit_testable) { if (pending_view_holder_token_.value) { entity_node_ = std::make_unique(context.session()); - opacity_node_ = std::make_unique(context.session()); + opacity_node_ = + std::make_unique(context.session()); view_holder_ = std::make_unique( context.session(), std::move(pending_view_holder_token_), "Flutter SceneHost"); diff --git a/shell/platform/fuchsia/flutter/vulkan_surface.h b/shell/platform/fuchsia/flutter/vulkan_surface.h index 64dae4c4f9534..2315e0bcee70e 100644 --- a/shell/platform/fuchsia/flutter/vulkan_surface.h +++ b/shell/platform/fuchsia/flutter/vulkan_surface.h @@ -133,7 +133,8 @@ 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. + // 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_.get(); diff --git a/shell/platform/fuchsia/flutter/vulkan_surface_pool.h b/shell/platform/fuchsia/flutter/vulkan_surface_pool.h index f3fb6727aed94..23f8551356265 100644 --- a/shell/platform/fuchsia/flutter/vulkan_surface_pool.h +++ b/shell/platform/fuchsia/flutter/vulkan_surface_pool.h @@ -43,8 +43,7 @@ class VulkanSurfacePool final { return retained_surfaces_.find(key) != retained_surfaces_.end(); } // For |VulkanSurfaceProducer::GetRetainedNode|. - 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(); }