From 85953615ebd5a3b534bc1cc76c82445a2fea339e Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Wed, 11 Dec 2019 15:10:55 -0800 Subject: [PATCH] Conditionally use offscreen root surface only when needed Currently helps primarily on iOS when no BackdropFilter is present by lowering energy usage --- flow/compositor_context.cc | 18 +++++- flow/compositor_context.h | 5 ++ flow/layers/backdrop_filter_layer.cc | 7 +++ flow/layers/backdrop_filter_layer.h | 2 + .../layers/backdrop_filter_layer_unittests.cc | 31 +++++++++ flow/layers/clip_path_layer.cc | 6 +- flow/layers/clip_path_layer.h | 4 ++ flow/layers/clip_path_layer_unittests.cc | 60 ++++++++++++++++++ flow/layers/clip_rect_layer.cc | 6 +- flow/layers/clip_rect_layer.h | 4 ++ flow/layers/clip_rect_layer_unittests.cc | 59 +++++++++++++++++ flow/layers/clip_rrect_layer.cc | 6 +- flow/layers/clip_rrect_layer.h | 4 ++ flow/layers/clip_rrect_layer_unittests.cc | 60 ++++++++++++++++++ flow/layers/color_filter_layer.cc | 7 +++ flow/layers/color_filter_layer.h | 2 + flow/layers/color_filter_layer_unittests.cc | 19 ++++++ flow/layers/layer.cc | 28 +++++++++ flow/layers/layer.h | 27 ++++++++ flow/layers/layer_tree.cc | 7 ++- flow/layers/layer_tree.h | 9 ++- flow/layers/layer_tree_unittests.cc | 1 + flow/layers/opacity_layer.cc | 2 + flow/layers/opacity_layer_unittests.cc | 19 ++++++ flow/layers/physical_shape_layer.cc | 4 +- flow/layers/physical_shape_layer.h | 4 ++ flow/layers/physical_shape_layer_unittests.cc | 63 +++++++++++++++++++ flow/layers/shader_mask_layer.cc | 6 ++ flow/layers/shader_mask_layer.h | 2 + flow/layers/shader_mask_layer_unittests.cc | 22 +++++++ flow/testing/layer_test.h | 1 + flow/testing/mock_layer.cc | 9 ++- flow/testing/mock_layer.h | 4 +- shell/common/rasterizer.cc | 5 +- shell/common/surface.cc | 6 +- shell/common/surface.h | 7 ++- shell/gpu/gpu_surface_gl.cc | 41 ++---------- shell/gpu/gpu_surface_gl.h | 1 - shell/gpu/gpu_surface_gl_delegate.cc | 4 +- shell/gpu/gpu_surface_gl_delegate.h | 5 +- shell/gpu/gpu_surface_metal.mm | 2 +- shell/gpu/gpu_surface_software.cc | 4 +- shell/gpu/gpu_surface_vulkan.cc | 2 +- shell/platform/darwin/ios/ios_surface_gl.h | 2 +- shell/platform/darwin/ios/ios_surface_gl.mm | 12 ++-- .../fuchsia/flutter/compositor_context.cc | 2 + .../fuchsia/flutter/compositor_context.h | 1 + shell/platform/fuchsia/flutter/surface.cc | 6 +- 48 files changed, 536 insertions(+), 72 deletions(-) diff --git a/flow/compositor_context.cc b/flow/compositor_context.cc index 0fafe0f9564..68f308fbf2f 100644 --- a/flow/compositor_context.cc +++ b/flow/compositor_context.cc @@ -36,10 +36,11 @@ std::unique_ptr CompositorContext::AcquireFrame( ExternalViewEmbedder* view_embedder, const SkMatrix& root_surface_transformation, bool instrumentation_enabled, + bool surface_supports_readback, fml::RefPtr gpu_thread_merger) { return std::make_unique( *this, gr_context, canvas, view_embedder, root_surface_transformation, - instrumentation_enabled, gpu_thread_merger); + instrumentation_enabled, surface_supports_readback, gpu_thread_merger); } CompositorContext::ScopedFrame::ScopedFrame( @@ -49,6 +50,7 @@ CompositorContext::ScopedFrame::ScopedFrame( ExternalViewEmbedder* view_embedder, const SkMatrix& root_surface_transformation, bool instrumentation_enabled, + bool surface_supports_readback, fml::RefPtr gpu_thread_merger) : context_(context), gr_context_(gr_context), @@ -56,6 +58,7 @@ CompositorContext::ScopedFrame::ScopedFrame( view_embedder_(view_embedder), root_surface_transformation_(root_surface_transformation), instrumentation_enabled_(instrumentation_enabled), + surface_supports_readback_(surface_supports_readback), gpu_thread_merger_(gpu_thread_merger) { context_.BeginFrame(*this, instrumentation_enabled_); } @@ -67,7 +70,8 @@ CompositorContext::ScopedFrame::~ScopedFrame() { RasterStatus CompositorContext::ScopedFrame::Raster( flutter::LayerTree& layer_tree, bool ignore_raster_cache) { - layer_tree.Preroll(*this, ignore_raster_cache); + bool root_needs_readback = layer_tree.Preroll(*this, ignore_raster_cache); + bool needs_save_layer = root_needs_readback && !surface_supports_readback(); PostPrerollResult post_preroll_result = PostPrerollResult::kSuccess; if (view_embedder_ && gpu_thread_merger_) { post_preroll_result = view_embedder_->PostPrerollAction(gpu_thread_merger_); @@ -79,9 +83,19 @@ RasterStatus CompositorContext::ScopedFrame::Raster( // Clearing canvas after preroll reduces one render target switch when preroll // paints some raster cache. if (canvas()) { + if (needs_save_layer) { + FML_LOG(INFO) << "Using SaveLayer to protect non-readback surface"; + SkRect bounds = SkRect::Make(layer_tree.frame_size()); + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); + canvas()->saveLayer(&bounds, &paint); + } canvas()->clear(SK_ColorTRANSPARENT); } layer_tree.Paint(*this, ignore_raster_cache); + if (canvas() && needs_save_layer) { + canvas()->restore(); + } return RasterStatus::kSuccess; } diff --git a/flow/compositor_context.h b/flow/compositor_context.h index 2872b5aa161..6698d3f41a2 100644 --- a/flow/compositor_context.h +++ b/flow/compositor_context.h @@ -45,6 +45,7 @@ class CompositorContext { ExternalViewEmbedder* view_embedder, const SkMatrix& root_surface_transformation, bool instrumentation_enabled, + bool surface_supports_readback, fml::RefPtr gpu_thread_merger); virtual ~ScopedFrame(); @@ -59,6 +60,8 @@ class CompositorContext { return root_surface_transformation_; } + bool surface_supports_readback() { return surface_supports_readback_; } + GrContext* gr_context() const { return gr_context_; } virtual RasterStatus Raster(LayerTree& layer_tree, @@ -71,6 +74,7 @@ class CompositorContext { ExternalViewEmbedder* view_embedder_; const SkMatrix& root_surface_transformation_; const bool instrumentation_enabled_; + const bool surface_supports_readback_; fml::RefPtr gpu_thread_merger_; FML_DISALLOW_COPY_AND_ASSIGN(ScopedFrame); @@ -86,6 +90,7 @@ class CompositorContext { ExternalViewEmbedder* view_embedder, const SkMatrix& root_surface_transformation, bool instrumentation_enabled, + bool surface_supports_readback, fml::RefPtr gpu_thread_merger); void OnGrContextCreated(); diff --git a/flow/layers/backdrop_filter_layer.cc b/flow/layers/backdrop_filter_layer.cc index d5799cc4f95..ce86db3deaa 100644 --- a/flow/layers/backdrop_filter_layer.cc +++ b/flow/layers/backdrop_filter_layer.cc @@ -9,6 +9,13 @@ namespace flutter { BackdropFilterLayer::BackdropFilterLayer(sk_sp filter) : filter_(std::move(filter)) {} +void BackdropFilterLayer::Preroll(PrerollContext* context, + const SkMatrix& matrix) { + Layer::AutoPrerollSaveLayerState save = + Layer::AutoPrerollSaveLayerState::Create(context, true, bool(filter_)); + ContainerLayer::Preroll(context, matrix); +} + void BackdropFilterLayer::Paint(PaintContext& context) const { TRACE_EVENT0("flutter", "BackdropFilterLayer::Paint"); FML_DCHECK(needs_painting()); diff --git a/flow/layers/backdrop_filter_layer.h b/flow/layers/backdrop_filter_layer.h index 732a1ac27e8..e3f34252b4b 100644 --- a/flow/layers/backdrop_filter_layer.h +++ b/flow/layers/backdrop_filter_layer.h @@ -15,6 +15,8 @@ class BackdropFilterLayer : public ContainerLayer { public: BackdropFilterLayer(sk_sp filter); + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + void Paint(PaintContext& context) const override; private: diff --git a/flow/layers/backdrop_filter_layer_unittests.cc b/flow/layers/backdrop_filter_layer_unittests.cc index e02745adcc8..50afecbdc78 100644 --- a/flow/layers/backdrop_filter_layer_unittests.cc +++ b/flow/layers/backdrop_filter_layer_unittests.cc @@ -182,5 +182,36 @@ TEST_F(BackdropFilterLayerTest, Nested) { MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); } +TEST_F(BackdropFilterLayerTest, Readback) { + sk_sp no_filter; + auto layer_filter = SkImageFilters::Paint(SkPaint(SkColors::kMagenta)); + auto initial_transform = SkMatrix(); + + // BDF with filter always reads from surface + auto layer1 = std::make_shared(layer_filter); + preroll_context()->surface_needs_readback = false; + layer1->Preroll(preroll_context(), initial_transform); + EXPECT_TRUE(preroll_context()->surface_needs_readback); + + // BDF with no filter does not read from surface itself + auto layer2 = std::make_shared(no_filter); + preroll_context()->surface_needs_readback = false; + layer2->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->surface_needs_readback); + + // BDF with no filter does not block prior readback value + preroll_context()->surface_needs_readback = true; + layer2->Preroll(preroll_context(), initial_transform); + EXPECT_TRUE(preroll_context()->surface_needs_readback); + + // BDF with no filter blocks child with readback + auto mock_layer = + std::make_shared(SkPath(), SkPaint(), false, false, true); + layer2->Add(mock_layer); + preroll_context()->surface_needs_readback = false; + layer2->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->surface_needs_readback); +} + } // namespace testing } // namespace flutter diff --git a/flow/layers/clip_path_layer.cc b/flow/layers/clip_path_layer.cc index 4353debfc09..3d420657bf0 100644 --- a/flow/layers/clip_path_layer.cc +++ b/flow/layers/clip_path_layer.cc @@ -22,6 +22,8 @@ void ClipPathLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { SkRect clip_path_bounds = clip_path_.getBounds(); children_inside_clip_ = context->cull_rect.intersect(clip_path_bounds); if (children_inside_clip_) { + Layer::AutoPrerollSaveLayerState save = + Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer()); context->mutators_stack.PushClipPath(clip_path_); SkRect child_paint_bounds = SkRect::MakeEmpty(); PrerollChildren(context, matrix, &child_paint_bounds); @@ -57,11 +59,11 @@ void ClipPathLayer::Paint(PaintContext& context) const { context.internal_nodes_canvas->clipPath(clip_path_, clip_behavior_ != Clip::hardEdge); - if (clip_behavior_ == Clip::antiAliasWithSaveLayer) { + if (UsesSaveLayer()) { context.internal_nodes_canvas->saveLayer(paint_bounds(), nullptr); } PaintChildren(context); - if (clip_behavior_ == Clip::antiAliasWithSaveLayer) { + if (UsesSaveLayer()) { context.internal_nodes_canvas->restore(); } } diff --git a/flow/layers/clip_path_layer.h b/flow/layers/clip_path_layer.h index aac23d6935b..ac7440625bb 100644 --- a/flow/layers/clip_path_layer.h +++ b/flow/layers/clip_path_layer.h @@ -17,6 +17,10 @@ class ClipPathLayer : public ContainerLayer { void Paint(PaintContext& context) const override; + bool UsesSaveLayer() const { + return clip_behavior_ == Clip::antiAliasWithSaveLayer; + } + #if defined(OS_FUCHSIA) void UpdateScene(SceneUpdateContext& context) override; #endif // defined(OS_FUCHSIA) diff --git a/flow/layers/clip_path_layer_unittests.cc b/flow/layers/clip_path_layer_unittests.cc index fb91d81f725..8c327332baf 100644 --- a/flow/layers/clip_path_layer_unittests.cc +++ b/flow/layers/clip_path_layer_unittests.cc @@ -192,5 +192,65 @@ TEST_F(ClipPathLayerTest, PartiallyContainedChild) { MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); } +static bool ReadbackResult(PrerollContext* context, + Clip clip_behavior, + std::shared_ptr child, + bool before) { + const SkMatrix initial_matrix = SkMatrix(); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath layer_path = SkPath().addRect(layer_bounds); + auto layer = std::make_shared(layer_path, clip_behavior); + if (child != nullptr) { + layer->Add(child); + } + context->surface_needs_readback = before; + layer->Preroll(context, initial_matrix); + return context->surface_needs_readback; +} + +TEST_F(ClipPathLayerTest, Readback) { + PrerollContext* context = preroll_context(); + SkPath path; + SkPaint paint; + + const Clip hard = Clip::hardEdge; + const Clip soft = Clip::antiAlias; + const Clip save_layer = Clip::antiAliasWithSaveLayer; + + std::shared_ptr nochild; + auto reader = std::make_shared(path, paint, false, false, true); + auto nonreader = std::make_shared(path, paint); + + // No children, no prior readback -> no readback after + EXPECT_FALSE(ReadbackResult(context, hard, nochild, false)); + EXPECT_FALSE(ReadbackResult(context, soft, nochild, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, nochild, false)); + + // No children, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, nochild, true)); + EXPECT_TRUE(ReadbackResult(context, soft, nochild, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, nochild, true)); + + // Non readback child, no prior readback -> no readback after + EXPECT_FALSE(ReadbackResult(context, hard, nonreader, false)); + EXPECT_FALSE(ReadbackResult(context, soft, nonreader, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, nonreader, false)); + + // Non readback child, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, nonreader, true)); + EXPECT_TRUE(ReadbackResult(context, soft, nonreader, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, nonreader, true)); + + // Readback child, no prior readback -> readback after unless SaveLayer + EXPECT_TRUE(ReadbackResult(context, hard, reader, false)); + EXPECT_TRUE(ReadbackResult(context, soft, reader, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, reader, false)); + + // Readback child, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, reader, true)); + EXPECT_TRUE(ReadbackResult(context, soft, reader, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true)); +} + } // namespace testing } // namespace flutter diff --git a/flow/layers/clip_rect_layer.cc b/flow/layers/clip_rect_layer.cc index 9e12839aedc..af6c490f27a 100644 --- a/flow/layers/clip_rect_layer.cc +++ b/flow/layers/clip_rect_layer.cc @@ -15,6 +15,8 @@ void ClipRectLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { SkRect previous_cull_rect = context->cull_rect; children_inside_clip_ = context->cull_rect.intersect(clip_rect_); if (children_inside_clip_) { + Layer::AutoPrerollSaveLayerState save = + Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer()); context->mutators_stack.PushClipRect(clip_rect_); SkRect child_paint_bounds = SkRect::MakeEmpty(); PrerollChildren(context, matrix, &child_paint_bounds); @@ -50,11 +52,11 @@ void ClipRectLayer::Paint(PaintContext& context) const { context.internal_nodes_canvas->clipRect(clip_rect_, clip_behavior_ != Clip::hardEdge); - if (clip_behavior_ == Clip::antiAliasWithSaveLayer) { + if (UsesSaveLayer()) { context.internal_nodes_canvas->saveLayer(clip_rect_, nullptr); } PaintChildren(context); - if (clip_behavior_ == Clip::antiAliasWithSaveLayer) { + if (UsesSaveLayer()) { context.internal_nodes_canvas->restore(); } } diff --git a/flow/layers/clip_rect_layer.h b/flow/layers/clip_rect_layer.h index f503a593365..adf43915638 100644 --- a/flow/layers/clip_rect_layer.h +++ b/flow/layers/clip_rect_layer.h @@ -16,6 +16,10 @@ class ClipRectLayer : public ContainerLayer { void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; + bool UsesSaveLayer() const { + return clip_behavior_ == Clip::antiAliasWithSaveLayer; + } + #if defined(OS_FUCHSIA) void UpdateScene(SceneUpdateContext& context) override; #endif // defined(OS_FUCHSIA) diff --git a/flow/layers/clip_rect_layer_unittests.cc b/flow/layers/clip_rect_layer_unittests.cc index 2784f62705b..b63ca0c3e9e 100644 --- a/flow/layers/clip_rect_layer_unittests.cc +++ b/flow/layers/clip_rect_layer_unittests.cc @@ -190,5 +190,64 @@ TEST_F(ClipRectLayerTest, PartiallyContainedChild) { MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); } +static bool ReadbackResult(PrerollContext* context, + Clip clip_behavior, + std::shared_ptr child, + bool before) { + const SkMatrix initial_matrix = SkMatrix(); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + auto layer = std::make_shared(layer_bounds, clip_behavior); + if (child != nullptr) { + layer->Add(child); + } + context->surface_needs_readback = before; + layer->Preroll(context, initial_matrix); + return context->surface_needs_readback; +} + +TEST_F(ClipRectLayerTest, Readback) { + PrerollContext* context = preroll_context(); + SkPath path; + SkPaint paint; + + const Clip hard = Clip::hardEdge; + const Clip soft = Clip::antiAlias; + const Clip save_layer = Clip::antiAliasWithSaveLayer; + + std::shared_ptr nochild; + auto reader = std::make_shared(path, paint, false, false, true); + auto nonreader = std::make_shared(path, paint); + + // No children, no prior readback -> no readback after + EXPECT_FALSE(ReadbackResult(context, hard, nochild, false)); + EXPECT_FALSE(ReadbackResult(context, soft, nochild, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, nochild, false)); + + // No children, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, nochild, true)); + EXPECT_TRUE(ReadbackResult(context, soft, nochild, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, nochild, true)); + + // Non readback child, no prior readback -> no readback after + EXPECT_FALSE(ReadbackResult(context, hard, nonreader, false)); + EXPECT_FALSE(ReadbackResult(context, soft, nonreader, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, nonreader, false)); + + // Non readback child, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, nonreader, true)); + EXPECT_TRUE(ReadbackResult(context, soft, nonreader, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, nonreader, true)); + + // Readback child, no prior readback -> readback after unless SaveLayer + EXPECT_TRUE(ReadbackResult(context, hard, reader, false)); + EXPECT_TRUE(ReadbackResult(context, soft, reader, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, reader, false)); + + // Readback child, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, reader, true)); + EXPECT_TRUE(ReadbackResult(context, soft, reader, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true)); +} + } // namespace testing } // namespace flutter diff --git a/flow/layers/clip_rrect_layer.cc b/flow/layers/clip_rrect_layer.cc index 4a22151d660..d640b8b4717 100644 --- a/flow/layers/clip_rrect_layer.cc +++ b/flow/layers/clip_rrect_layer.cc @@ -16,6 +16,8 @@ void ClipRRectLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { SkRect clip_rrect_bounds = clip_rrect_.getBounds(); children_inside_clip_ = context->cull_rect.intersect(clip_rrect_bounds); if (children_inside_clip_) { + Layer::AutoPrerollSaveLayerState save = + Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer()); context->mutators_stack.PushClipRRect(clip_rrect_); SkRect child_paint_bounds = SkRect::MakeEmpty(); PrerollChildren(context, matrix, &child_paint_bounds); @@ -51,11 +53,11 @@ void ClipRRectLayer::Paint(PaintContext& context) const { context.internal_nodes_canvas->clipRRect(clip_rrect_, clip_behavior_ != Clip::hardEdge); - if (clip_behavior_ == Clip::antiAliasWithSaveLayer) { + if (UsesSaveLayer()) { context.internal_nodes_canvas->saveLayer(paint_bounds(), nullptr); } PaintChildren(context); - if (clip_behavior_ == Clip::antiAliasWithSaveLayer) { + if (UsesSaveLayer()) { context.internal_nodes_canvas->restore(); } } diff --git a/flow/layers/clip_rrect_layer.h b/flow/layers/clip_rrect_layer.h index 45daf48ba6e..c1c83ddbcc6 100644 --- a/flow/layers/clip_rrect_layer.h +++ b/flow/layers/clip_rrect_layer.h @@ -17,6 +17,10 @@ class ClipRRectLayer : public ContainerLayer { void Paint(PaintContext& context) const override; + bool UsesSaveLayer() const { + return clip_behavior_ == Clip::antiAliasWithSaveLayer; + } + #if defined(OS_FUCHSIA) void UpdateScene(SceneUpdateContext& context) override; #endif // defined(OS_FUCHSIA) diff --git a/flow/layers/clip_rrect_layer_unittests.cc b/flow/layers/clip_rrect_layer_unittests.cc index 5c9ed21a3a3..57db2140ca1 100644 --- a/flow/layers/clip_rrect_layer_unittests.cc +++ b/flow/layers/clip_rrect_layer_unittests.cc @@ -195,5 +195,65 @@ TEST_F(ClipRRectLayerTest, PartiallyContainedChild) { MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); } +static bool ReadbackResult(PrerollContext* context, + Clip clip_behavior, + std::shared_ptr child, + bool before) { + const SkMatrix initial_matrix = SkMatrix(); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkRRect layer_rrect = SkRRect::MakeRect(layer_bounds); + auto layer = std::make_shared(layer_rrect, clip_behavior); + if (child != nullptr) { + layer->Add(child); + } + context->surface_needs_readback = before; + layer->Preroll(context, initial_matrix); + return context->surface_needs_readback; +} + +TEST_F(ClipRRectLayerTest, Readback) { + PrerollContext* context = preroll_context(); + SkPath path; + SkPaint paint; + + const Clip hard = Clip::hardEdge; + const Clip soft = Clip::antiAlias; + const Clip save_layer = Clip::antiAliasWithSaveLayer; + + std::shared_ptr nochild; + auto reader = std::make_shared(path, paint, false, false, true); + auto nonreader = std::make_shared(path, paint); + + // No children, no prior readback -> no readback after + EXPECT_FALSE(ReadbackResult(context, hard, nochild, false)); + EXPECT_FALSE(ReadbackResult(context, soft, nochild, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, nochild, false)); + + // No children, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, nochild, true)); + EXPECT_TRUE(ReadbackResult(context, soft, nochild, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, nochild, true)); + + // Non readback child, no prior readback -> no readback after + EXPECT_FALSE(ReadbackResult(context, hard, nonreader, false)); + EXPECT_FALSE(ReadbackResult(context, soft, nonreader, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, nonreader, false)); + + // Non readback child, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, nonreader, true)); + EXPECT_TRUE(ReadbackResult(context, soft, nonreader, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, nonreader, true)); + + // Readback child, no prior readback -> readback after unless SaveLayer + EXPECT_TRUE(ReadbackResult(context, hard, reader, false)); + EXPECT_TRUE(ReadbackResult(context, soft, reader, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, reader, false)); + + // Readback child, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, reader, true)); + EXPECT_TRUE(ReadbackResult(context, soft, reader, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true)); +} + } // namespace testing } // namespace flutter diff --git a/flow/layers/color_filter_layer.cc b/flow/layers/color_filter_layer.cc index 8e5ea19ac84..b212720da58 100644 --- a/flow/layers/color_filter_layer.cc +++ b/flow/layers/color_filter_layer.cc @@ -9,6 +9,13 @@ namespace flutter { ColorFilterLayer::ColorFilterLayer(sk_sp filter) : filter_(std::move(filter)) {} +void ColorFilterLayer::Preroll(PrerollContext* context, + const SkMatrix& matrix) { + Layer::AutoPrerollSaveLayerState save = + Layer::AutoPrerollSaveLayerState::Create(context); + ContainerLayer::Preroll(context, matrix); +} + void ColorFilterLayer::Paint(PaintContext& context) const { TRACE_EVENT0("flutter", "ColorFilterLayer::Paint"); FML_DCHECK(needs_painting()); diff --git a/flow/layers/color_filter_layer.h b/flow/layers/color_filter_layer.h index 628c5b17f6e..989158f704e 100644 --- a/flow/layers/color_filter_layer.h +++ b/flow/layers/color_filter_layer.h @@ -15,6 +15,8 @@ class ColorFilterLayer : public ContainerLayer { public: ColorFilterLayer(sk_sp filter); + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + void Paint(PaintContext& context) const override; private: diff --git a/flow/layers/color_filter_layer_unittests.cc b/flow/layers/color_filter_layer_unittests.cc index 204a02357ad..42a65f255cd 100644 --- a/flow/layers/color_filter_layer_unittests.cc +++ b/flow/layers/color_filter_layer_unittests.cc @@ -195,5 +195,24 @@ TEST_F(ColorFilterLayerTest, Nested) { MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); } +TEST_F(ColorFilterLayerTest, Readback) { + auto layer_filter = SkColorFilters::LinearToSRGBGamma(); + auto initial_transform = SkMatrix(); + + // ColorFilterLayer does not read from surface + auto layer = std::make_shared(layer_filter); + preroll_context()->surface_needs_readback = false; + layer->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->surface_needs_readback); + + // ColorFilterLayer blocks child with readback + auto mock_layer = + std::make_shared(SkPath(), SkPaint(), false, false, true); + layer->Add(mock_layer); + preroll_context()->surface_needs_readback = false; + layer->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->surface_needs_readback); +} + } // namespace testing } // namespace flutter diff --git a/flow/layers/layer.cc b/flow/layers/layer.cc index 5ad6a6bee14..d4bd809d5f2 100644 --- a/flow/layers/layer.cc +++ b/flow/layers/layer.cc @@ -27,6 +27,34 @@ uint64_t Layer::NextUniqueID() { void Layer::Preroll(PrerollContext* context, const SkMatrix& matrix) {} +Layer::AutoPrerollSaveLayerState::AutoPrerollSaveLayerState( + PrerollContext* preroll_context, + bool save_layer_is_active, + bool layer_itself_performs_readback) + : preroll_context_(preroll_context), + save_layer_is_active_(save_layer_is_active), + layer_itself_performs_readback_(layer_itself_performs_readback) { + if (save_layer_is_active_) { + prev_surface_needs_readback_ = preroll_context_->surface_needs_readback; + preroll_context_->surface_needs_readback = false; + } +} + +Layer::AutoPrerollSaveLayerState Layer::AutoPrerollSaveLayerState::Create( + PrerollContext* preroll_context, + bool save_layer_is_active, + bool layer_itself_performs_readback) { + return Layer::AutoPrerollSaveLayerState(preroll_context, save_layer_is_active, + layer_itself_performs_readback); +} + +Layer::AutoPrerollSaveLayerState::~AutoPrerollSaveLayerState() { + if (save_layer_is_active_) { + preroll_context_->surface_needs_readback = + (prev_surface_needs_readback_ || layer_itself_performs_readback_); + } +} + #if defined(OS_FUCHSIA) void Layer::UpdateScene(SceneUpdateContext& context) {} #endif // defined(OS_FUCHSIA) diff --git a/flow/layers/layer.h b/flow/layers/layer.h index 731509876b6..63c7cdb316d 100644 --- a/flow/layers/layer.h +++ b/flow/layers/layer.h @@ -49,6 +49,7 @@ struct PrerollContext { MutatorsStack& mutators_stack; SkColorSpace* dst_color_space; SkRect cull_rect; + bool surface_needs_readback; // These allow us to paint in the end of subtree Preroll. const Stopwatch& raster_time; @@ -76,6 +77,32 @@ class Layer { virtual void Preroll(PrerollContext* context, const SkMatrix& matrix); + // Used during Preroll by layers that employ a saveLayer to manage the + // PrerollContext settings with values affected by the saveLayer mechanism. + // This object must be created before calling Preroll on the children to + // set up the state for the children and then restore the state upon + // destruction. + class AutoPrerollSaveLayerState { + public: + FML_WARN_UNUSED_RESULT static AutoPrerollSaveLayerState Create( + PrerollContext* preroll_context, + bool save_layer_is_active = true, + bool layer_itself_performs_readback = false); + + ~AutoPrerollSaveLayerState(); + + private: + AutoPrerollSaveLayerState(PrerollContext* preroll_context, + bool save_layer_is_active, + bool layer_itself_performs_readback); + + PrerollContext* preroll_context_; + bool save_layer_is_active_; + bool layer_itself_performs_readback_; + + bool prev_surface_needs_readback_; + }; + struct PaintContext { // When splitting the scene into multiple canvases (e.g when embedding // a platform view on iOS) during the paint traversal we apply the non leaf diff --git a/flow/layers/layer_tree.cc b/flow/layers/layer_tree.cc index f3fb1641369..dd9e6b4f02b 100644 --- a/flow/layers/layer_tree.cc +++ b/flow/layers/layer_tree.cc @@ -26,13 +26,13 @@ void LayerTree::RecordBuildTime(fml::TimePoint start) { build_finish_ = fml::TimePoint::Now(); } -void LayerTree::Preroll(CompositorContext::ScopedFrame& frame, +bool LayerTree::Preroll(CompositorContext::ScopedFrame& frame, bool ignore_raster_cache) { TRACE_EVENT0("flutter", "LayerTree::Preroll"); if (!root_layer_) { FML_LOG(ERROR) << "The scene did not specify any layers."; - return; + return false; } SkColorSpace* color_space = @@ -47,6 +47,7 @@ void LayerTree::Preroll(CompositorContext::ScopedFrame& frame, stack, color_space, kGiantRect, + false, frame.context().raster_time(), frame.context().ui_time(), frame.context().texture_registry(), @@ -55,6 +56,7 @@ void LayerTree::Preroll(CompositorContext::ScopedFrame& frame, frame_device_pixel_ratio_}; root_layer_->Preroll(&context, frame.root_surface_transformation()); + return context.surface_needs_readback; } #if defined(OS_FUCHSIA) @@ -152,6 +154,7 @@ sk_sp LayerTree::Flatten(const SkRect& bounds) { unused_stack, // mutator stack nullptr, // SkColorSpace* dst_color_space kGiantRect, // SkRect cull_rect + false, // layer reads from surface unused_stopwatch, // frame time (dont care) unused_stopwatch, // engine time (dont care) unused_texture_registry, // texture registry (not supported) diff --git a/flow/layers/layer_tree.h b/flow/layers/layer_tree.h index 10a10d6711a..21ba509f09a 100644 --- a/flow/layers/layer_tree.h +++ b/flow/layers/layer_tree.h @@ -23,7 +23,14 @@ class LayerTree { float frame_physical_depth, float frame_device_pixel_ratio); - void Preroll(CompositorContext::ScopedFrame& frame, + // Perform a preroll pass on the tree and return information about + // the tree that affects rendering this frame. + // + // Returns: + // - a boolean indicating whether or not the top level of the + // layer tree performs any operations that require readback + // from the root surface. + bool Preroll(CompositorContext::ScopedFrame& frame, bool ignore_raster_cache = false); #if defined(OS_FUCHSIA) diff --git a/flow/layers/layer_tree_unittests.cc b/flow/layers/layer_tree_unittests.cc index 7d4922ff3be..9851cebf26d 100644 --- a/flow/layers/layer_tree_unittests.cc +++ b/flow/layers/layer_tree_unittests.cc @@ -26,6 +26,7 @@ class LayerTreeTest : public CanvasTest { nullptr, root_transform_, false, + true, nullptr)) {} LayerTree& layer_tree() { return layer_tree_; } diff --git a/flow/layers/opacity_layer.cc b/flow/layers/opacity_layer.cc index 5592f40f72f..5a5e2d05c61 100644 --- a/flow/layers/opacity_layer.cc +++ b/flow/layers/opacity_layer.cc @@ -58,6 +58,8 @@ void OpacityLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { child_matrix.postTranslate(offset_.fX, offset_.fY); context->mutators_stack.PushTransform( SkMatrix::MakeTrans(offset_.fX, offset_.fY)); + Layer::AutoPrerollSaveLayerState save = + Layer::AutoPrerollSaveLayerState::Create(context); OpacityLayerBase::Preroll(context, child_matrix); context->mutators_stack.Pop(); diff --git a/flow/layers/opacity_layer_unittests.cc b/flow/layers/opacity_layer_unittests.cc index 8410d7767c8..f7cc77ffe2d 100644 --- a/flow/layers/opacity_layer_unittests.cc +++ b/flow/layers/opacity_layer_unittests.cc @@ -308,5 +308,24 @@ TEST_F(OpacityLayerTest, Nested) { EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls); } +TEST_F(OpacityLayerTest, Readback) { + auto initial_transform = SkMatrix(); + auto layer = std::make_shared(kOpaque_SkAlphaType, SkPoint()); + layer->Add(std::make_shared(SkPath())); + + // OpacityLayer does not read from surface + preroll_context()->surface_needs_readback = false; + layer->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->surface_needs_readback); + + // OpacityLayer blocks child with readback + auto mock_layer = + std::make_shared(SkPath(), SkPaint(), false, false, true); + layer->Add(mock_layer); + preroll_context()->surface_needs_readback = false; + layer->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->surface_needs_readback); +} + } // namespace testing } // namespace flutter diff --git a/flow/layers/physical_shape_layer.cc b/flow/layers/physical_shape_layer.cc index fed7a5fbe71..017224a08d8 100644 --- a/flow/layers/physical_shape_layer.cc +++ b/flow/layers/physical_shape_layer.cc @@ -49,6 +49,8 @@ void PhysicalShapeLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { TRACE_EVENT0("flutter", "PhysicalShapeLayer::Preroll"); + Layer::AutoPrerollSaveLayerState save = + Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer()); PhysicalShapeLayerBase::Preroll(context, matrix); if (elevation() == 0) { @@ -100,7 +102,7 @@ void PhysicalShapeLayer::Paint(PaintContext& context) const { break; } - if (clip_behavior_ == Clip::antiAliasWithSaveLayer) { + if (UsesSaveLayer()) { // If we want to avoid the bleeding edge artifact // (https://github.com/flutter/flutter/issues/18057#issue-328003931) // using saveLayer, we have to call drawPaint instead of drawPath as diff --git a/flow/layers/physical_shape_layer.h b/flow/layers/physical_shape_layer.h index ac54a7bbc67..3d3a2a74b40 100644 --- a/flow/layers/physical_shape_layer.h +++ b/flow/layers/physical_shape_layer.h @@ -46,6 +46,10 @@ class PhysicalShapeLayer : public PhysicalShapeLayerBase { void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; + bool UsesSaveLayer() const { + return clip_behavior_ == Clip::antiAliasWithSaveLayer; + } + private: SkColor shadow_color_; SkPath path_; diff --git a/flow/layers/physical_shape_layer_unittests.cc b/flow/layers/physical_shape_layer_unittests.cc index 36107560fa4..93c860ba6ac 100644 --- a/flow/layers/physical_shape_layer_unittests.cc +++ b/flow/layers/physical_shape_layer_unittests.cc @@ -202,5 +202,68 @@ TEST_F(PhysicalShapeLayerTest, ElevationComplex) { 0, MockCanvas::DrawPathData{layer_path, layer_paint}}})); } +static bool ReadbackResult(PrerollContext* context, + Clip clip_behavior, + std::shared_ptr child, + bool before) { + const SkMatrix initial_matrix = SkMatrix(); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath layer_path = SkPath().addRect(layer_bounds); + auto layer = + std::make_shared(SK_ColorGREEN, SK_ColorBLACK, + 0.0f, // elevation + layer_path, clip_behavior); + if (child != nullptr) { + layer->Add(child); + } + context->surface_needs_readback = before; + layer->Preroll(context, initial_matrix); + return context->surface_needs_readback; +} + +TEST_F(PhysicalShapeLayerTest, Readback) { + PrerollContext* context = preroll_context(); + SkPath path; + SkPaint paint; + + const Clip hard = Clip::hardEdge; + const Clip soft = Clip::antiAlias; + const Clip save_layer = Clip::antiAliasWithSaveLayer; + + std::shared_ptr nochild; + auto reader = std::make_shared(path, paint, false, false, true); + auto nonreader = std::make_shared(path, paint); + + // No children, no prior readback -> no readback after + EXPECT_FALSE(ReadbackResult(context, hard, nochild, false)); + EXPECT_FALSE(ReadbackResult(context, soft, nochild, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, nochild, false)); + + // No children, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, nochild, true)); + EXPECT_TRUE(ReadbackResult(context, soft, nochild, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, nochild, true)); + + // Non readback child, no prior readback -> no readback after + EXPECT_FALSE(ReadbackResult(context, hard, nonreader, false)); + EXPECT_FALSE(ReadbackResult(context, soft, nonreader, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, nonreader, false)); + + // Non readback child, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, nonreader, true)); + EXPECT_TRUE(ReadbackResult(context, soft, nonreader, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, nonreader, true)); + + // Readback child, no prior readback -> readback after unless SaveLayer + EXPECT_TRUE(ReadbackResult(context, hard, reader, false)); + EXPECT_TRUE(ReadbackResult(context, soft, reader, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, reader, false)); + + // Readback child, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, reader, true)); + EXPECT_TRUE(ReadbackResult(context, soft, reader, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true)); +} + } // namespace testing } // namespace flutter diff --git a/flow/layers/shader_mask_layer.cc b/flow/layers/shader_mask_layer.cc index 8e681ec7254..157354f7314 100644 --- a/flow/layers/shader_mask_layer.cc +++ b/flow/layers/shader_mask_layer.cc @@ -11,6 +11,12 @@ ShaderMaskLayer::ShaderMaskLayer(sk_sp shader, SkBlendMode blend_mode) : shader_(shader), mask_rect_(mask_rect), blend_mode_(blend_mode) {} +void ShaderMaskLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { + Layer::AutoPrerollSaveLayerState save = + Layer::AutoPrerollSaveLayerState::Create(context); + ContainerLayer::Preroll(context, matrix); +} + void ShaderMaskLayer::Paint(PaintContext& context) const { TRACE_EVENT0("flutter", "ShaderMaskLayer::Paint"); FML_DCHECK(needs_painting()); diff --git a/flow/layers/shader_mask_layer.h b/flow/layers/shader_mask_layer.h index 7f633c0372d..03b90e40d0e 100644 --- a/flow/layers/shader_mask_layer.h +++ b/flow/layers/shader_mask_layer.h @@ -17,6 +17,8 @@ class ShaderMaskLayer : public ContainerLayer { const SkRect& mask_rect, SkBlendMode blend_mode); + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + void Paint(PaintContext& context) const override; private: diff --git a/flow/layers/shader_mask_layer_unittests.cc b/flow/layers/shader_mask_layer_unittests.cc index d8997c3e65b..895c6a9360c 100644 --- a/flow/layers/shader_mask_layer_unittests.cc +++ b/flow/layers/shader_mask_layer_unittests.cc @@ -253,5 +253,27 @@ TEST_F(ShaderMaskLayerTest, Nested) { MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); } +TEST_F(ShaderMaskLayerTest, Readback) { + auto initial_transform = SkMatrix(); + const SkRect layer_bounds = SkRect::MakeLTRB(2.0f, 4.0f, 20.5f, 20.5f); + auto layer_filter = + SkPerlinNoiseShader::MakeImprovedNoise(1.0f, 1.0f, 1, 1.0f); + auto layer = std::make_shared(layer_filter, layer_bounds, + SkBlendMode::kSrc); + + // ShaderMaskLayer does not read from surface + preroll_context()->surface_needs_readback = false; + layer->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->surface_needs_readback); + + // ShaderMaskLayer blocks child with readback + auto mock_layer = + std::make_shared(SkPath(), SkPaint(), false, false, true); + layer->Add(mock_layer); + preroll_context()->surface_needs_readback = false; + layer->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->surface_needs_readback); +} + } // namespace testing } // namespace flutter diff --git a/flow/testing/layer_test.h b/flow/testing/layer_test.h index ab51af38af5..593dec18368 100644 --- a/flow/testing/layer_test.h +++ b/flow/testing/layer_test.h @@ -37,6 +37,7 @@ class LayerTestBase : public CanvasTestBase { nullptr, /* external_view_embedder */ mutators_stack_, TestT::mock_canvas().imageInfo().colorSpace(), kGiantRect, /* cull_rect */ + false, /* layer reads from surface */ raster_time_, ui_time_, texture_registry_, false, /* checkerboard_offscreen_layers */ 100.0f, /* frame_physical_depth */ diff --git a/flow/testing/mock_layer.cc b/flow/testing/mock_layer.cc index 1065f430546..5fe1b98088a 100644 --- a/flow/testing/mock_layer.cc +++ b/flow/testing/mock_layer.cc @@ -10,11 +10,13 @@ namespace testing { MockLayer::MockLayer(SkPath path, SkPaint paint, bool fake_has_platform_view, - bool fake_needs_system_composite) + bool fake_needs_system_composite, + bool fake_reads_surface) : fake_paint_path_(path), fake_paint_(paint), fake_has_platform_view_(fake_has_platform_view), - fake_needs_system_composite_(fake_needs_system_composite) {} + fake_needs_system_composite_(fake_needs_system_composite), + fake_reads_surface_(fake_reads_surface) {} void MockLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { parent_mutators_ = context->mutators_stack; @@ -26,6 +28,9 @@ void MockLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { context->has_platform_view = fake_has_platform_view_; set_paint_bounds(fake_paint_path_.getBounds()); set_needs_system_composite(fake_needs_system_composite_); + if (fake_reads_surface_) { + context->surface_needs_readback = true; + } } void MockLayer::Paint(PaintContext& context) const { diff --git a/flow/testing/mock_layer.h b/flow/testing/mock_layer.h index b55452a3781..835c3ee9621 100644 --- a/flow/testing/mock_layer.h +++ b/flow/testing/mock_layer.h @@ -19,7 +19,8 @@ class MockLayer : public Layer { MockLayer(SkPath path, SkPaint paint = SkPaint(), bool fake_has_platform_view = false, - bool fake_needs_system_composite = false); + bool fake_needs_system_composite = false, + bool fake_reads_surface = false); void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; @@ -40,6 +41,7 @@ class MockLayer : public Layer { bool parent_has_platform_view_ = false; bool fake_has_platform_view_ = false; bool fake_needs_system_composite_ = false; + bool fake_reads_surface_ = false; FML_DISALLOW_COPY_AND_ASSIGN(MockLayer); }; diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index 11fdd3f6ee6..7d6a7a74e8a 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -333,6 +333,7 @@ RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) { external_view_embedder, // external view embedder root_surface_transformation, // root surface transformation true, // instrumentation enabled + frame->supports_readback(), // surface supports pixel reads gpu_thread_merger_ // thread merger ); @@ -377,7 +378,7 @@ static sk_sp ScreenshotLayerTreeAsPicture( // https://github.com/flutter/flutter/issues/23435 auto frame = compositor_context.AcquireFrame( nullptr, recorder.getRecordingCanvas(), nullptr, - root_surface_transformation, false, nullptr); + root_surface_transformation, false, true, nullptr); frame->Raster(*tree, true); @@ -434,7 +435,7 @@ static sk_sp ScreenshotLayerTreeAsImage( // surface if we don't call the base method. auto frame = compositor_context.flutter::CompositorContext::AcquireFrame( surface_context, canvas, nullptr, root_surface_transformation, false, - nullptr); + true, nullptr); canvas->clear(SK_ColorTRANSPARENT); frame->Raster(*tree, true); canvas->flush(); diff --git a/shell/common/surface.cc b/shell/common/surface.cc index af889885017..6a4f992e913 100644 --- a/shell/common/surface.cc +++ b/shell/common/surface.cc @@ -10,8 +10,12 @@ namespace flutter { SurfaceFrame::SurfaceFrame(sk_sp surface, + bool supports_readback, const SubmitCallback& submit_callback) - : submitted_(false), surface_(surface), submit_callback_(submit_callback) { + : submitted_(false), + surface_(surface), + supports_readback_(supports_readback), + submit_callback_(submit_callback) { FML_DCHECK(submit_callback_); } diff --git a/shell/common/surface.h b/shell/common/surface.h index 14f898fad3f..432f6be5e56 100644 --- a/shell/common/surface.h +++ b/shell/common/surface.h @@ -21,7 +21,9 @@ class SurfaceFrame { using SubmitCallback = std::function; - SurfaceFrame(sk_sp surface, const SubmitCallback& submit_callback); + SurfaceFrame(sk_sp surface, + bool supports_readback, + const SubmitCallback& submit_callback); ~SurfaceFrame(); @@ -31,9 +33,12 @@ class SurfaceFrame { sk_sp SkiaSurface() const; + bool supports_readback() { return supports_readback_; } + private: bool submitted_; sk_sp surface_; + bool supports_readback_; SubmitCallback submit_callback_; bool PerformSubmit(); diff --git a/shell/gpu/gpu_surface_gl.cc b/shell/gpu/gpu_surface_gl.cc index 5f30a48375d..e5fbf00a247 100644 --- a/shell/gpu/gpu_surface_gl.cc +++ b/shell/gpu/gpu_surface_gl.cc @@ -180,19 +180,6 @@ static sk_sp WrapOnscreenSurface(GrContext* context, ); } -static sk_sp CreateOffscreenSurface(GrContext* context, - const SkISize& size) { - const SkImageInfo image_info = SkImageInfo::MakeN32( - size.fWidth, size.fHeight, kOpaque_SkAlphaType, SkColorSpace::MakeSRGB()); - - const SkSurfaceProps surface_props( - SkSurfaceProps::InitType::kLegacyFontHost_InitType); - - return SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, image_info, 0, - kBottomLeft_GrSurfaceOrigin, - &surface_props); -} - bool GPUSurfaceGL::CreateOrUpdateSurfaces(const SkISize& size) { if (onscreen_surface_ != nullptr && size == SkISize::Make(onscreen_surface_->width(), @@ -206,14 +193,13 @@ bool GPUSurfaceGL::CreateOrUpdateSurfaces(const SkISize& size) { // Either way, we need to get rid of previous surface. onscreen_surface_ = nullptr; - offscreen_surface_ = nullptr; if (size.isEmpty()) { FML_LOG(ERROR) << "Cannot create surfaces of empty size."; return false; } - sk_sp onscreen_surface, offscreen_surface; + sk_sp onscreen_surface; onscreen_surface = WrapOnscreenSurface(context_.get(), // GL context @@ -228,16 +214,7 @@ bool GPUSurfaceGL::CreateOrUpdateSurfaces(const SkISize& size) { return false; } - if (delegate_->UseOffscreenSurface()) { - offscreen_surface = CreateOffscreenSurface(context_.get(), size); - if (offscreen_surface == nullptr) { - FML_LOG(ERROR) << "Could not create offscreen surface."; - return false; - } - } - onscreen_surface_ = std::move(onscreen_surface); - offscreen_surface_ = std::move(offscreen_surface); return true; } @@ -263,7 +240,7 @@ std::unique_ptr GPUSurfaceGL::AcquireFrame(const SkISize& size) { // external view embedder may want to render to the root surface. if (!render_to_surface_) { return std::make_unique( - nullptr, [](const SurfaceFrame& surface_frame, SkCanvas* canvas) { + nullptr, true, [](const SurfaceFrame& surface_frame, SkCanvas* canvas) { return true; }); } @@ -285,7 +262,8 @@ std::unique_ptr GPUSurfaceGL::AcquireFrame(const SkISize& size) { return weak ? weak->PresentSurface(canvas) : false; }; - return std::make_unique(surface, submit_callback); + return std::make_unique( + surface, delegate_->SurfaceSupportsReadback(), submit_callback); } bool GPUSurfaceGL::PresentSurface(SkCanvas* canvas) { @@ -293,15 +271,6 @@ bool GPUSurfaceGL::PresentSurface(SkCanvas* canvas) { return false; } - if (offscreen_surface_ != nullptr) { - TRACE_EVENT0("flutter", "CopyTextureOnscreen"); - SkPaint paint; - SkCanvas* onscreen_canvas = onscreen_surface_->getCanvas(); - onscreen_canvas->clear(SK_ColorTRANSPARENT); - onscreen_canvas->drawImage(offscreen_surface_->makeImageSnapshot(), 0, 0, - &paint); - } - { TRACE_EVENT0("flutter", "SkCanvas::Flush"); onscreen_surface_->getCanvas()->flush(); @@ -346,7 +315,7 @@ sk_sp GPUSurfaceGL::AcquireRenderSurface( return nullptr; } - return offscreen_surface_ != nullptr ? offscreen_surface_ : onscreen_surface_; + return onscreen_surface_; } // |Surface| diff --git a/shell/gpu/gpu_surface_gl.h b/shell/gpu/gpu_surface_gl.h index 97325569bfd..d2e62d7e1fe 100644 --- a/shell/gpu/gpu_surface_gl.h +++ b/shell/gpu/gpu_surface_gl.h @@ -50,7 +50,6 @@ class GPUSurfaceGL : public Surface { GPUSurfaceGLDelegate* delegate_; sk_sp context_; sk_sp onscreen_surface_; - sk_sp offscreen_surface_; bool context_owner_; // TODO(38466): Refactor GPU surface APIs take into account the fact that an // external view embedder may want to render to the root surface. This is a diff --git a/shell/gpu/gpu_surface_gl_delegate.cc b/shell/gpu/gpu_surface_gl_delegate.cc index 1ef969fe8ac..df7ad4ef073 100644 --- a/shell/gpu/gpu_surface_gl_delegate.cc +++ b/shell/gpu/gpu_surface_gl_delegate.cc @@ -12,8 +12,8 @@ bool GPUSurfaceGLDelegate::GLContextFBOResetAfterPresent() const { return false; } -bool GPUSurfaceGLDelegate::UseOffscreenSurface() const { - return false; +bool GPUSurfaceGLDelegate::SurfaceSupportsReadback() const { + return true; } SkMatrix GPUSurfaceGLDelegate::GLContextSurfaceTransformation() const { diff --git a/shell/gpu/gpu_surface_gl_delegate.h b/shell/gpu/gpu_surface_gl_delegate.h index dfe0ce7f468..bf51d15c8b7 100644 --- a/shell/gpu/gpu_surface_gl_delegate.h +++ b/shell/gpu/gpu_surface_gl_delegate.h @@ -36,8 +36,9 @@ class GPUSurfaceGLDelegate : public GPUSurfaceDelegate { // subsequent frames. virtual bool GLContextFBOResetAfterPresent() const; - // Create an offscreen surface to render into before onscreen composition. - virtual bool UseOffscreenSurface() const; + // Indicates whether or not the surface supports pixel readback as used in + // circumstances such as a BackdropFilter. + virtual bool SurfaceSupportsReadback() const; // A transformation applied to the onscreen surface before the canvas is // flushed. diff --git a/shell/gpu/gpu_surface_metal.mm b/shell/gpu/gpu_surface_metal.mm index 81abf740d48..bb436d21387 100644 --- a/shell/gpu/gpu_surface_metal.mm +++ b/shell/gpu/gpu_surface_metal.mm @@ -152,7 +152,7 @@ std::unique_ptr GPUSurfaceMetal::AcquireFrame(const SkISize& size) return true; }; - return std::make_unique(std::move(surface), submit_callback); + return std::make_unique(std::move(surface), true, submit_callback); } // |Surface| diff --git a/shell/gpu/gpu_surface_software.cc b/shell/gpu/gpu_surface_software.cc index 346857ac47e..8b2f664d186 100644 --- a/shell/gpu/gpu_surface_software.cc +++ b/shell/gpu/gpu_surface_software.cc @@ -29,7 +29,7 @@ std::unique_ptr GPUSurfaceSoftware::AcquireFrame( // external view embedder may want to render to the root surface. if (!render_to_surface_) { return std::make_unique( - nullptr, [](const SurfaceFrame& surface_frame, SkCanvas* canvas) { + nullptr, true, [](const SurfaceFrame& surface_frame, SkCanvas* canvas) { return true; }); } @@ -69,7 +69,7 @@ std::unique_ptr GPUSurfaceSoftware::AcquireFrame( return self->delegate_->PresentBackingStore(surface_frame.SkiaSurface()); }; - return std::make_unique(backing_store, on_submit); + return std::make_unique(backing_store, true, on_submit); } // |Surface| diff --git a/shell/gpu/gpu_surface_vulkan.cc b/shell/gpu/gpu_surface_vulkan.cc index fbc9696b15d..f7d07f580a5 100644 --- a/shell/gpu/gpu_surface_vulkan.cc +++ b/shell/gpu/gpu_surface_vulkan.cc @@ -40,7 +40,7 @@ std::unique_ptr GPUSurfaceVulkan::AcquireFrame( } return weak_this->window_.SwapBuffers(); }; - return std::make_unique(std::move(surface), + return std::make_unique(std::move(surface), true, std::move(callback)); } diff --git a/shell/platform/darwin/ios/ios_surface_gl.h b/shell/platform/darwin/ios/ios_surface_gl.h index c1019bb442b..906f3be92b2 100644 --- a/shell/platform/darwin/ios/ios_surface_gl.h +++ b/shell/platform/darwin/ios/ios_surface_gl.h @@ -48,7 +48,7 @@ class IOSSurfaceGL final : public IOSSurface, intptr_t GLContextFBO() const override; - bool UseOffscreenSurface() const override; + bool SurfaceSupportsReadback() const override; // |GPUSurfaceGLDelegate| ExternalViewEmbedder* GetExternalViewEmbedder() override; diff --git a/shell/platform/darwin/ios/ios_surface_gl.mm b/shell/platform/darwin/ios/ios_surface_gl.mm index 48e70e00a4e..4f1da5a593a 100644 --- a/shell/platform/darwin/ios/ios_surface_gl.mm +++ b/shell/platform/darwin/ios/ios_surface_gl.mm @@ -49,11 +49,13 @@ intptr_t IOSSurfaceGL::GLContextFBO() const { return IsValid() ? render_target_->framebuffer() : GL_NONE; } -bool IOSSurfaceGL::UseOffscreenSurface() const { - // The onscreen surface wraps a GL renderbuffer, which is extremely slow to read. - // Certain filter effects require making a copy of the current destination, so we - // always render to an offscreen surface, which will be much quicker to read/copy. - return true; +bool IOSSurfaceGL::SurfaceSupportsReadback() const { + // The onscreen surface wraps a GL renderbuffer, which is extremely slow to read on iOS. + // Certain filter effects, in particular BackdropFilter, require making a copy of + // the current destination. For performance, the iOS surface will specify that it + // does not support readback so that the engine compositor can implement a workaround + // such as rendering the scene to an offscreen surface or Skia saveLayer. + return false; } bool IOSSurfaceGL::GLContextMakeCurrent() { diff --git a/shell/platform/fuchsia/flutter/compositor_context.cc b/shell/platform/fuchsia/flutter/compositor_context.cc index 624b237c7a9..e4772099813 100644 --- a/shell/platform/fuchsia/flutter/compositor_context.cc +++ b/shell/platform/fuchsia/flutter/compositor_context.cc @@ -20,6 +20,7 @@ class ScopedFrame final : public flutter::CompositorContext::ScopedFrame { nullptr, root_surface_transformation, instrumentation_enabled, + true, nullptr), session_connection_(session_connection) {} @@ -96,6 +97,7 @@ CompositorContext::AcquireFrame( flutter::ExternalViewEmbedder* view_embedder, const SkMatrix& root_surface_transformation, bool instrumentation_enabled, + bool surface_supports_readback, fml::RefPtr gpu_thread_merger) { // TODO: The AcquireFrame interface is too broad and must be refactored to get // rid of the context and canvas arguments as those seem to be only used for diff --git a/shell/platform/fuchsia/flutter/compositor_context.h b/shell/platform/fuchsia/flutter/compositor_context.h index c5e54df9527..ce7d16d47c4 100644 --- a/shell/platform/fuchsia/flutter/compositor_context.h +++ b/shell/platform/fuchsia/flutter/compositor_context.h @@ -45,6 +45,7 @@ class CompositorContext final : public flutter::CompositorContext { flutter::ExternalViewEmbedder* view_embedder, const SkMatrix& root_surface_transformation, bool instrumentation_enabled, + bool surface_supports_readback, fml::RefPtr gpu_thread_merger) override; FML_DISALLOW_COPY_AND_ASSIGN(CompositorContext); diff --git a/shell/platform/fuchsia/flutter/surface.cc b/shell/platform/fuchsia/flutter/surface.cc index 29fbbc7294c..1dce37fdceb 100644 --- a/shell/platform/fuchsia/flutter/surface.cc +++ b/shell/platform/fuchsia/flutter/surface.cc @@ -26,8 +26,10 @@ bool Surface::IsValid() { std::unique_ptr Surface::AcquireFrame( const SkISize& size) { return std::make_unique( - nullptr, [](const flutter::SurfaceFrame& surface_frame, - SkCanvas* canvas) { return true; }); + nullptr, true, + [](const flutter::SurfaceFrame& surface_frame, SkCanvas* canvas) { + return true; + }); } // |flutter::Surface|