From cfa1b8e58ba4e72db3b1a20750a8196fef24e367 Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Thu, 18 Feb 2021 23:41:01 +0100 Subject: [PATCH] Add DiffContext (#21824) --- BUILD.gn | 4 + ci/licenses_golden/licenses_flutter | 4 + common/graphics/persistent_cache.h | 4 +- flow/BUILD.gn | 6 + flow/diff_context.cc | 161 ++++++++++++++ flow/diff_context.h | 208 ++++++++++++++++++ flow/layers/backdrop_filter_layer.cc | 34 +++ flow/layers/backdrop_filter_layer.h | 6 + .../layers/backdrop_filter_layer_unittests.cc | 63 ++++++ flow/layers/clip_path_layer.cc | 20 ++ flow/layers/clip_path_layer.h | 6 + flow/layers/clip_rect_layer.cc | 20 ++ flow/layers/clip_rect_layer.h | 6 + flow/layers/clip_rrect_layer.cc | 20 ++ flow/layers/clip_rrect_layer.h | 6 + flow/layers/color_filter_layer.cc | 19 ++ flow/layers/color_filter_layer.h | 6 + flow/layers/container_layer.cc | 102 +++++++++ flow/layers/container_layer.h | 15 ++ flow/layers/container_layer_unittests.cc | 208 ++++++++++++++++++ flow/layers/image_filter_layer.cc | 38 ++++ flow/layers/image_filter_layer.h | 6 + flow/layers/image_filter_layer_unittests.cc | 54 +++++ flow/layers/layer.cc | 1 + flow/layers/layer.h | 52 +++++ flow/layers/layer_tree.h | 11 + flow/layers/opacity_layer.cc | 18 ++ flow/layers/opacity_layer.h | 6 + flow/layers/performance_overlay_layer.cc | 16 ++ flow/layers/performance_overlay_layer.h | 14 ++ flow/layers/physical_shape_layer.cc | 32 +++ flow/layers/physical_shape_layer.h | 6 + flow/layers/picture_layer.cc | 89 ++++++++ flow/layers/picture_layer.h | 20 ++ flow/layers/picture_layer_unittests.cc | 59 +++++ flow/layers/shader_mask_layer.cc | 20 ++ flow/layers/shader_mask_layer.h | 6 + flow/layers/texture_layer.cc | 18 ++ flow/layers/texture_layer.h | 12 + flow/layers/transform_layer.cc | 18 ++ flow/layers/transform_layer.h | 6 + flow/layers/transform_layer_unittests.cc | 111 ++++++++++ flow/paint_region.cc | 17 ++ flow/paint_region.h | 57 +++++ flow/testing/diff_context_test.cc | 53 +++++ flow/testing/diff_context_test.h | 64 ++++++ flow/testing/mock_layer.cc | 19 ++ flow/testing/mock_layer.h | 8 + lib/ui/compositing.dart | 70 +++--- lib/ui/compositing/scene_builder.cc | 79 ++++++- lib/ui/compositing/scene_builder.h | 39 +++- 51 files changed, 1882 insertions(+), 55 deletions(-) create mode 100644 flow/diff_context.cc create mode 100644 flow/diff_context.h create mode 100644 flow/paint_region.cc create mode 100644 flow/paint_region.h create mode 100644 flow/testing/diff_context_test.cc create mode 100644 flow/testing/diff_context_test.h diff --git a/BUILD.gn b/BUILD.gn index c174f50169..2f2cd3826e 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -28,6 +28,10 @@ config("config") { if (is_fuchsia && flutter_enable_legacy_fuchsia_embedder) { defines = [ "LEGACY_FUCHSIA_EMBEDDER" ] } + + if (is_debug) { + defines = [ "FLUTTER_ENABLE_DIFF_CONTEXT" ] + } } config("export_dynamic_symbols") { diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 5d8852e578..416dfd279b 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -34,6 +34,8 @@ FILE: ../../../flutter/common/task_runners.cc FILE: ../../../flutter/common/task_runners.h FILE: ../../../flutter/flow/compositor_context.cc FILE: ../../../flutter/flow/compositor_context.h +FILE: ../../../flutter/flow/diff_context.cc +FILE: ../../../flutter/flow/diff_context.h FILE: ../../../flutter/flow/embedded_view_params_unittests.cc FILE: ../../../flutter/flow/embedded_views.cc FILE: ../../../flutter/flow/embedded_views.h @@ -101,6 +103,8 @@ FILE: ../../../flutter/flow/matrix_decomposition.cc FILE: ../../../flutter/flow/matrix_decomposition.h FILE: ../../../flutter/flow/matrix_decomposition_unittests.cc FILE: ../../../flutter/flow/mutators_stack_unittests.cc +FILE: ../../../flutter/flow/paint_region.cc +FILE: ../../../flutter/flow/paint_region.h FILE: ../../../flutter/flow/paint_utils.cc FILE: ../../../flutter/flow/paint_utils.h FILE: ../../../flutter/flow/raster_cache.cc diff --git a/common/graphics/persistent_cache.h b/common/graphics/persistent_cache.h index 8585b9d671..efb3935c58 100644 --- a/common/graphics/persistent_cache.h +++ b/common/graphics/persistent_cache.h @@ -15,12 +15,12 @@ #include "flutter/fml/unique_fd.h" #include "third_party/skia/include/gpu/GrContextOptions.h" +namespace flutter { + namespace testing { class ShellTest; } -namespace flutter { - /// A cache of SkData that gets stored to disk. /// /// This is mainly used for Shaders but is also written to by Dart. It is diff --git a/flow/BUILD.gn b/flow/BUILD.gn index 94b7827bb7..ebb8873d26 100644 --- a/flow/BUILD.gn +++ b/flow/BUILD.gn @@ -10,6 +10,8 @@ source_set("flow") { sources = [ "compositor_context.cc", "compositor_context.h", + "diff_context.cc", + "diff_context.h", "embedded_views.cc", "embedded_views.h", "instrumentation.cc", @@ -50,6 +52,8 @@ source_set("flow") { "layers/transform_layer.h", "matrix_decomposition.cc", "matrix_decomposition.h", + "paint_region.cc", + "paint_region.h", "paint_utils.cc", "paint_utils.h", "raster_cache.cc", @@ -103,6 +107,8 @@ if (enable_unittests) { testonly = true sources = [ + "testing/diff_context_test.cc", + "testing/diff_context_test.h", "testing/gl_context_switch_test.cc", "testing/gl_context_switch_test.h", "testing/layer_test.h", diff --git a/flow/diff_context.cc b/flow/diff_context.cc new file mode 100644 index 0000000000..b0e9892ce6 --- /dev/null +++ b/flow/diff_context.cc @@ -0,0 +1,161 @@ +#include "flutter/flow/diff_context.h" +#include "flutter/flow/layers/layer.h" + +namespace flutter { + +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +DiffContext::DiffContext(SkISize frame_size, + double frame_device_pixel_ratio, + PaintRegionMap& this_frame_paint_region_map, + const PaintRegionMap& last_frame_paint_region_map) + : rects_(std::make_shared>()), + frame_size_(frame_size), + frame_device_pixel_ratio_(frame_device_pixel_ratio), + this_frame_paint_region_map_(this_frame_paint_region_map), + last_frame_paint_region_map_(last_frame_paint_region_map) {} + +void DiffContext::BeginSubtree() { + state_stack_.push_back(state_); + state_.rect_index_ = rects_->size(); +} + +void DiffContext::EndSubtree() { + FML_DCHECK(!state_stack_.empty()); + state_ = std::move(state_stack_.back()); + state_stack_.pop_back(); +} + +DiffContext::State::State() + : dirty(false), cull_rect(kGiantRect), rect_index_(0) {} + +void DiffContext::PushTransform(const SkMatrix& transform) { + state_.transform.preConcat(transform); + SkMatrix inverse_transform; + // Perspective projections don't produce rectangles that are useful for + // culling for some reason. + if (!transform.hasPerspective() && transform.invert(&inverse_transform)) { + inverse_transform.mapRect(&state_.cull_rect); + } else { + state_.cull_rect = kGiantRect; + } +} + +Damage DiffContext::ComputeDamage( + const SkIRect& accumulated_buffer_damage) const { + SkRect buffer_damage = SkRect::Make(accumulated_buffer_damage); + buffer_damage.join(damage_); + SkRect frame_damage(damage_); + + for (const auto& r : readbacks_) { + SkRect rect = SkRect::Make(r.rect); + if (rect.intersects(frame_damage)) { + frame_damage.join(rect); + } + if (rect.intersects(buffer_damage)) { + buffer_damage.join(rect); + } + } + + Damage res; + buffer_damage.roundOut(&res.buffer_damage); + frame_damage.roundOut(&res.frame_damage); + + SkIRect frame_clip = SkIRect::MakeSize(frame_size_); + res.buffer_damage.intersect(frame_clip); + res.frame_damage.intersect(frame_clip); + return res; +} + +bool DiffContext::PushCullRect(const SkRect& clip) { + return state_.cull_rect.intersect(clip); +} + +void DiffContext::MarkSubtreeDirty(const PaintRegion& previous_paint_region) { + FML_DCHECK(!IsSubtreeDirty()); + if (previous_paint_region.is_valid()) { + AddDamage(previous_paint_region); + } + state_.dirty = true; +} + +void DiffContext::AddLayerBounds(const SkRect& rect) { + SkRect r(rect); + if (r.intersect(state_.cull_rect)) { + state_.transform.mapRect(&r); + if (!r.isEmpty()) { + rects_->push_back(r); + if (IsSubtreeDirty()) { + AddDamage(r); + } + } + } +} + +void DiffContext::AddExistingPaintRegion(const PaintRegion& region) { + // Adding paint region for retained layer implies that current subtree is not + // dirty, so we know, for example, that the inherited transforms must match + FML_DCHECK(!IsSubtreeDirty()); + if (region.is_valid()) { + rects_->insert(rects_->end(), region.begin(), region.end()); + } +} + +void DiffContext::AddReadbackRegion(const SkIRect& rect) { + Readback readback; + readback.rect = rect; + readback.position = rects_->size(); + // Push empty rect as a placeholder for position in current subtree + rects_->push_back(SkRect::MakeEmpty()); + readbacks_.push_back(std::move(readback)); +} + +PaintRegion DiffContext::CurrentSubtreeRegion() const { + bool has_readback = std::any_of( + readbacks_.begin(), readbacks_.end(), + [&](const Readback& r) { return r.position >= state_.rect_index_; }); + return PaintRegion(rects_, state_.rect_index_, rects_->size(), has_readback); +} + +void DiffContext::AddDamage(const PaintRegion& damage) { + FML_DCHECK(damage.is_valid()); + for (const auto& r : damage) { + damage_.join(r); + } +} + +void DiffContext::AddDamage(const SkRect& rect) { + damage_.join(rect); +} + +void DiffContext::SetLayerPaintRegion(const Layer* layer, + const PaintRegion& region) { + this_frame_paint_region_map_[layer->unique_id()] = region; +} + +PaintRegion DiffContext::GetOldLayerPaintRegion(const Layer* layer) const { + auto i = last_frame_paint_region_map_.find(layer->unique_id()); + if (i != last_frame_paint_region_map_.end()) { + return i->second; + } else { + // This is valid when Layer::PreservePaintRegion is called for retained + // layer with zero sized parent clip (these layers are not diffed) + return PaintRegion(); + } +} + +void DiffContext::Statistics::LogStatistics() { +#if !FLUTTER_RELEASE + FML_TRACE_COUNTER("flutter", "DiffContext", reinterpret_cast(this), + "NewPictures", new_pictures_, "PicturesTooComplexToCompare", + pictures_too_complex_to_compare_, "DeepComparePictures", + deep_compare_pictures_, "SameInstancePictures", + same_instance_pictures_, + "DifferentInstanceButEqualPictures", + different_instance_but_equal_pictures_); +#endif // !FLUTTER_RELEASE +} + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + +} // namespace flutter diff --git a/flow/diff_context.h b/flow/diff_context.h new file mode 100644 index 0000000000..14a5b72092 --- /dev/null +++ b/flow/diff_context.h @@ -0,0 +1,208 @@ +#ifndef FLUTTER_FLOW_DIFF_CONTEXT_H_ +#define FLUTTER_FLOW_DIFF_CONTEXT_H_ + +#include +#include +#include "flutter/flow/paint_region.h" +#include "flutter/fml/logging.h" +#include "flutter/fml/macros.h" +#include "third_party/skia/include/core/SkMatrix.h" +#include "third_party/skia/include/core/SkRect.h" + +namespace flutter { + +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +class Layer; + +// Represents area that needs to be updated in front buffer (frame_damage) and +// area that is going to be painted to in back buffer (buffer_damage). +struct Damage { + // This is the damage between current and previous frame; + // If embedder supports partial update, this is the region that needs to be + // repainted. + // Corresponds to "surface damage" from EGL_KHR_partial_update. + SkIRect frame_damage; + + // Reflects actual change to target framebuffer; This is frame_damage + + // damage previously acumulated for target framebuffer. + // All drawing will be clipped to this region. Knowing the affected area + // upfront may be useful for tile based GPUs. + // Corresponds to "buffer damage" from EGL_KHR_partial_update. + SkIRect buffer_damage; +}; + +// Layer Unique Id to PaintRegion +using PaintRegionMap = std::map; + +// Tracks state during tree diffing process and computes resulting damage +class DiffContext { + public: + explicit DiffContext(SkISize frame_size, + double device_pixel_aspect_ratio, + PaintRegionMap& this_frame_paint_region_map, + const PaintRegionMap& last_frame_paint_region_map); + + // Starts a new subtree. + void BeginSubtree(); + + // Ends current subtree; All modifications to state (transform, cullrect, + // dirty) will be restored + void EndSubtree(); + + // Creates subtree in current scope and closes it on scope exit + class AutoSubtreeRestore { + FML_DISALLOW_COPY_ASSIGN_AND_MOVE(AutoSubtreeRestore); + + public: + explicit AutoSubtreeRestore(DiffContext* context) : context_(context) { + context->BeginSubtree(); + } + ~AutoSubtreeRestore() { context_->EndSubtree(); } + + private: + DiffContext* context_; + }; + + // Pushes additional transform for current subtree + void PushTransform(const SkMatrix& transform); + + // Pushes cull rect for current subtree + bool PushCullRect(const SkRect& clip); + + // Returns transform matrix for current subtree + const SkMatrix& GetTransform() const { return state_.transform; } + + // Return cull rect for current subtree (in local coordinates) + const SkRect& GetCullRect() const { return state_.cull_rect; } + + // Sets the dirty flag on current subtree; + // + // previous_paint_region, which should represent region of previous subtree + // at this level will be added to damage area. + // + // Each paint region added to dirty subtree (through AddPaintRegion) is also + // added to damage. + void MarkSubtreeDirty( + const PaintRegion& previous_paint_region = PaintRegion()); + + bool IsSubtreeDirty() const { return state_.dirty; } + + // Add layer bounds to current paint region; rect is in "local" (layer) + // coordinates. + void AddLayerBounds(const SkRect& rect); + + // Add entire paint region of retained layer for current subtree. This can + // only be used in subtrees that are not dirty, otherwise ancestor transforms + // or clips may result in different paint region. + void AddExistingPaintRegion(const PaintRegion& region); + + // The idea of readback region is that if any part of the readback region + // needs to be repainted, then the whole readback region must be repainted; + // + // Readback rect is in screen coordinates. + void AddReadbackRegion(const SkIRect& rect); + + // Returns the paint region for current subtree; Each rect in paint region is + // in screen coordinates; Once a layer accumulates the paint regions of its + // children, this PaintRegion value can be associated with the current layer + // using DiffContext::SetLayerPaintRegion. + PaintRegion CurrentSubtreeRegion() const; + + // Computes final damage + // + // additional_damage is the previously accumulated frame_damage for + // current framebuffer + Damage ComputeDamage(const SkIRect& additional_damage) const; + + double frame_device_pixel_ratio() const { return frame_device_pixel_ratio_; }; + + // Adds the region to current damage. Used for removed layers, where instead + // of diffing the layer its paint region is direcly added to damage. + void AddDamage(const PaintRegion& damage); + + // Associates the paint region with specified layer and current layer tree. + // The paint region can not be stored directly in layer itself, because same + // retained layer instance can possibly paint in different locations depending + // on ancestor layers. + void SetLayerPaintRegion(const Layer* layer, const PaintRegion& region); + + // Retrieves the paint region associated with specified layer and previous + // frame layer tree. + PaintRegion GetOldLayerPaintRegion(const Layer* layer) const; + + class Statistics { + public: + // Picture replaced by different picture + void AddNewPicture() { ++new_pictures_; } + + // Picture that would require deep comparison but was considered too complex + // to serialize and thus was treated as new picture + void AddPictureTooComplexToCompare() { ++pictures_too_complex_to_compare_; } + + // Picture that has identical instance between frames + void AddSameInstancePicture() { ++same_instance_pictures_; }; + + // Picture that had to be serialized to compare for equality + void AddDeepComparePicture() { ++deep_compare_pictures_; } + + // Picture that had to be serialized to compare (different instances), + // but were equal + void AddDifferentInstanceButEqualPicture() { + ++different_instance_but_equal_pictures_; + }; + + // Logs the statistics to trace counter + void LogStatistics(); + + private: + int new_pictures_ = 0; + int pictures_too_complex_to_compare_ = 0; + int same_instance_pictures_ = 0; + int deep_compare_pictures_ = 0; + int different_instance_but_equal_pictures_ = 0; + }; + + Statistics& statistics() { return statistics_; } + + private: + struct State { + State(); + + bool dirty; + SkRect cull_rect; + SkMatrix transform; + size_t rect_index_; + }; + + std::shared_ptr> rects_; + State state_; + SkISize frame_size_; + double frame_device_pixel_ratio_; + std::vector state_stack_; + + SkRect damage_ = SkRect::MakeEmpty(); + + PaintRegionMap& this_frame_paint_region_map_; + const PaintRegionMap& last_frame_paint_region_map_; + + void AddDamage(const SkRect& rect); + + struct Readback { + // Index of rects_ entry that this readback belongs to. Used to + // determine if subtree has any readback + size_t position; + + // readback area, in screen coordinates + SkIRect rect; + }; + + std::vector readbacks_; + Statistics statistics_; +}; + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + +} // namespace flutter + +#endif // FLUTTER_FLOW_DIFF_CONTEXT_H_ diff --git a/flow/layers/backdrop_filter_layer.cc b/flow/layers/backdrop_filter_layer.cc index e853a7783f..9dfd84105b 100644 --- a/flow/layers/backdrop_filter_layer.cc +++ b/flow/layers/backdrop_filter_layer.cc @@ -9,6 +9,40 @@ namespace flutter { BackdropFilterLayer::BackdropFilterLayer(sk_sp filter) : filter_(std::move(filter)) {} +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +void BackdropFilterLayer::Diff(DiffContext* context, const Layer* old_layer) { + DiffContext::AutoSubtreeRestore subtree(context); + auto* prev = static_cast(old_layer); + if (!context->IsSubtreeDirty()) { + FML_DCHECK(prev); + if (filter_ != prev->filter_) { + context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer)); + } + } + + // Backdrop filter paints everywhere in cull rect + auto paint_bounds = context->GetCullRect(); + context->AddLayerBounds(paint_bounds); + + // convert paint bounds and filter to screen coordinates + context->GetTransform().mapRect(&paint_bounds); + auto input_filter_bounds = paint_bounds.roundOut(); + auto filter = filter_->makeWithLocalMatrix(context->GetTransform()); + + auto filter_bounds = // in screen coordinates + filter->filterBounds(input_filter_bounds, SkMatrix::I(), + SkImageFilter::kReverse_MapDirection); + + context->AddReadbackRegion(filter_bounds); + + DiffChildren(context, prev); + + context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); +} + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void BackdropFilterLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { Layer::AutoPrerollSaveLayerState save = diff --git a/flow/layers/backdrop_filter_layer.h b/flow/layers/backdrop_filter_layer.h index e1fd667d71..0788a683c9 100644 --- a/flow/layers/backdrop_filter_layer.h +++ b/flow/layers/backdrop_filter_layer.h @@ -14,6 +14,12 @@ class BackdropFilterLayer : public ContainerLayer { public: BackdropFilterLayer(sk_sp filter); +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + void Diff(DiffContext* context, const Layer* old_layer) override; + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/backdrop_filter_layer_unittests.cc b/flow/layers/backdrop_filter_layer_unittests.cc index 5fff33ecc1..53cb5d151c 100644 --- a/flow/layers/backdrop_filter_layer_unittests.cc +++ b/flow/layers/backdrop_filter_layer_unittests.cc @@ -4,6 +4,9 @@ #include "flutter/flow/layers/backdrop_filter_layer.h" +#include "flutter/flow/layers/clip_rect_layer.h" +#include "flutter/flow/layers/transform_layer.h" +#include "flutter/flow/testing/diff_context_test.h" #include "flutter/flow/testing/layer_test.h" #include "flutter/flow/testing/mock_layer.h" #include "flutter/fml/macros.h" @@ -213,5 +216,65 @@ TEST_F(BackdropFilterLayerTest, Readback) { EXPECT_FALSE(preroll_context()->surface_needs_readback); } +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +using BackdropLayerDiffTest = DiffContextTest; + +TEST_F(BackdropLayerDiffTest, BackdropLayer) { + auto filter = SkImageFilters::Blur(10, 10, SkTileMode::kClamp, nullptr); + + { + // tests later assume 30px readback area, fail early if that's not the case + auto readback = filter->filterBounds(SkIRect::MakeWH(10, 10), SkMatrix::I(), + SkImageFilter::kReverse_MapDirection); + EXPECT_EQ(readback, SkIRect::MakeLTRB(-30, -30, 40, 40)); + } + + MockLayerTree l1(SkISize::Make(100, 100)); + l1.root()->Add(std::make_shared(filter)); + + // no clip, effect over entire surface + auto damage = DiffLayerTree(l1, MockLayerTree(SkISize::Make(100, 100))); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeWH(100, 100)); + + MockLayerTree l2(SkISize::Make(100, 100)); + + auto clip = std::make_shared(SkRect::MakeLTRB(20, 20, 60, 60), + Clip::hardEdge); + clip->Add(std::make_shared(filter)); + l2.root()->Add(clip); + damage = DiffLayerTree(l2, MockLayerTree(SkISize::Make(100, 100))); + + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(0, 0, 90, 90)); + + MockLayerTree l3; + auto scale = std::make_shared(SkMatrix::Scale(2.0, 2.0)); + scale->Add(clip); + l3.root()->Add(scale); + + damage = DiffLayerTree(l3, MockLayerTree()); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(0, 0, 180, 180)); + + MockLayerTree l4; + l4.root()->Add(scale); + + // path just outside of readback region, doesn't affect blur + auto path1 = SkPath().addRect(SkRect::MakeLTRB(180, 180, 190, 190)); + l4.root()->Add(std::make_shared(path1)); + damage = DiffLayerTree(l4, l3); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(180, 180, 190, 190)); + + MockLayerTree l5; + l5.root()->Add(scale); + + // path just inside of readback region, must trigger backdrop repaint + auto path2 = SkPath().addRect(SkRect::MakeLTRB(179, 179, 189, 189)); + l5.root()->Add(std::make_shared(path2)); + damage = DiffLayerTree(l5, l4); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(0, 0, 190, 190)); +} + +#endif + } // namespace testing } // namespace flutter diff --git a/flow/layers/clip_path_layer.cc b/flow/layers/clip_path_layer.cc index 3e6cf4570a..79488dbf34 100644 --- a/flow/layers/clip_path_layer.cc +++ b/flow/layers/clip_path_layer.cc @@ -16,6 +16,26 @@ ClipPathLayer::ClipPathLayer(const SkPath& clip_path, Clip clip_behavior) FML_DCHECK(clip_behavior != Clip::none); } +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +void ClipPathLayer::Diff(DiffContext* context, const Layer* old_layer) { + DiffContext::AutoSubtreeRestore subtree(context); + auto* prev = static_cast(old_layer); + if (!context->IsSubtreeDirty()) { + FML_DCHECK(prev); + if (clip_behavior_ != prev->clip_behavior_ || + clip_path_ != prev->clip_path_) { + context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer)); + } + } + if (context->PushCullRect(clip_path_.getBounds())) { + DiffChildren(context, prev); + } + context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); +} + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void ClipPathLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { TRACE_EVENT0("flutter", "ClipPathLayer::Preroll"); diff --git a/flow/layers/clip_path_layer.h b/flow/layers/clip_path_layer.h index c2af6ce3ff..b22bed9570 100644 --- a/flow/layers/clip_path_layer.h +++ b/flow/layers/clip_path_layer.h @@ -13,6 +13,12 @@ class ClipPathLayer : public ContainerLayer { public: ClipPathLayer(const SkPath& clip_path, Clip clip_behavior = Clip::antiAlias); +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + void Diff(DiffContext* context, const Layer* old_layer) override; + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/clip_rect_layer.cc b/flow/layers/clip_rect_layer.cc index b95a340f33..078a9d6a0d 100644 --- a/flow/layers/clip_rect_layer.cc +++ b/flow/layers/clip_rect_layer.cc @@ -12,6 +12,26 @@ ClipRectLayer::ClipRectLayer(const SkRect& clip_rect, Clip clip_behavior) FML_DCHECK(clip_behavior != Clip::none); } +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +void ClipRectLayer::Diff(DiffContext* context, const Layer* old_layer) { + DiffContext::AutoSubtreeRestore subtree(context); + auto* prev = static_cast(old_layer); + if (!context->IsSubtreeDirty()) { + FML_DCHECK(prev); + if (clip_behavior_ != prev->clip_behavior_ || + clip_rect_ != prev->clip_rect_) { + context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer)); + } + } + if (context->PushCullRect(clip_rect_)) { + DiffChildren(context, prev); + } + context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); +} + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void ClipRectLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { TRACE_EVENT0("flutter", "ClipRectLayer::Preroll"); diff --git a/flow/layers/clip_rect_layer.h b/flow/layers/clip_rect_layer.h index 17917b2329..54c319ef1d 100644 --- a/flow/layers/clip_rect_layer.h +++ b/flow/layers/clip_rect_layer.h @@ -13,6 +13,12 @@ class ClipRectLayer : public ContainerLayer { public: ClipRectLayer(const SkRect& clip_rect, Clip clip_behavior); +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + void Diff(DiffContext* context, const Layer* old_layer) override; + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/clip_rrect_layer.cc b/flow/layers/clip_rrect_layer.cc index 39b838be28..2fb346a78f 100644 --- a/flow/layers/clip_rrect_layer.cc +++ b/flow/layers/clip_rrect_layer.cc @@ -12,6 +12,26 @@ ClipRRectLayer::ClipRRectLayer(const SkRRect& clip_rrect, Clip clip_behavior) FML_DCHECK(clip_behavior != Clip::none); } +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +void ClipRRectLayer::Diff(DiffContext* context, const Layer* old_layer) { + DiffContext::AutoSubtreeRestore subtree(context); + auto* prev = static_cast(old_layer); + if (!context->IsSubtreeDirty()) { + FML_DCHECK(prev); + if (clip_behavior_ != prev->clip_behavior_ || + clip_rrect_ != prev->clip_rrect_) { + context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer)); + } + } + if (context->PushCullRect(clip_rrect_.getBounds())) { + DiffChildren(context, prev); + } + context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); +} + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void ClipRRectLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { TRACE_EVENT0("flutter", "ClipRRectLayer::Preroll"); diff --git a/flow/layers/clip_rrect_layer.h b/flow/layers/clip_rrect_layer.h index c308f21c7b..e5045bfa45 100644 --- a/flow/layers/clip_rrect_layer.h +++ b/flow/layers/clip_rrect_layer.h @@ -13,6 +13,12 @@ class ClipRRectLayer : public ContainerLayer { public: ClipRRectLayer(const SkRRect& clip_rrect, Clip clip_behavior); +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + void Diff(DiffContext* context, const Layer* old_layer) override; + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/color_filter_layer.cc b/flow/layers/color_filter_layer.cc index 17ff3e6d55..7d662f07f4 100644 --- a/flow/layers/color_filter_layer.cc +++ b/flow/layers/color_filter_layer.cc @@ -9,6 +9,25 @@ namespace flutter { ColorFilterLayer::ColorFilterLayer(sk_sp filter) : filter_(std::move(filter)) {} +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +void ColorFilterLayer::Diff(DiffContext* context, const Layer* old_layer) { + DiffContext::AutoSubtreeRestore subtree(context); + auto* prev = static_cast(old_layer); + if (!context->IsSubtreeDirty()) { + FML_DCHECK(prev); + if (filter_ != prev->filter_) { + context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer)); + } + } + + DiffChildren(context, prev); + + context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); +} + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void ColorFilterLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { Layer::AutoPrerollSaveLayerState save = diff --git a/flow/layers/color_filter_layer.h b/flow/layers/color_filter_layer.h index cd3c584b44..b1fb32acfb 100644 --- a/flow/layers/color_filter_layer.h +++ b/flow/layers/color_filter_layer.h @@ -14,6 +14,12 @@ class ColorFilterLayer : public ContainerLayer { public: ColorFilterLayer(sk_sp filter); +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + void Diff(DiffContext* context, const Layer* old_layer) override; + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/container_layer.cc b/flow/layers/container_layer.cc index 7e09f41737..35aef14590 100644 --- a/flow/layers/container_layer.cc +++ b/flow/layers/container_layer.cc @@ -10,6 +10,102 @@ namespace flutter { ContainerLayer::ContainerLayer() {} +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +void ContainerLayer::Diff(DiffContext* context, const Layer* old_layer) { + auto old_container = static_cast(old_layer); + DiffContext::AutoSubtreeRestore subtree(context); + DiffChildren(context, old_container); + context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); +} + +void ContainerLayer::PreservePaintRegion(DiffContext* context) { + Layer::PreservePaintRegion(context); + for (auto& layer : layers_) { + layer->PreservePaintRegion(context); + } +} + +void ContainerLayer::DiffChildren(DiffContext* context, + const ContainerLayer* old_layer) { + if (context->IsSubtreeDirty()) { + for (auto& layer : layers_) { + layer->Diff(context, nullptr); + } + return; + } + FML_DCHECK(old_layer); + + const auto& prev_layers = old_layer->layers_; + + // first mismatched element + int new_children_top = 0; + int old_children_top = 0; + + // last mismatched element + int new_children_bottom = layers_.size() - 1; + int old_children_bottom = prev_layers.size() - 1; + + while ((old_children_top <= old_children_bottom) && + (new_children_top <= new_children_bottom)) { + if (!layers_[new_children_top]->IsReplacing( + context, prev_layers[old_children_top].get())) { + break; + } + ++new_children_top; + ++old_children_top; + } + + while ((old_children_top <= old_children_bottom) && + (new_children_top <= new_children_bottom)) { + if (!layers_[new_children_bottom]->IsReplacing( + context, prev_layers[old_children_bottom].get())) { + break; + } + --new_children_bottom; + --old_children_bottom; + } + + // old layers that don't match + for (int i = old_children_top; i <= old_children_bottom; ++i) { + auto layer = prev_layers[i]; + context->AddDamage(context->GetOldLayerPaintRegion(layer.get())); + } + + for (int i = 0; i < static_cast(layers_.size()); ++i) { + if (i < new_children_top || i > new_children_bottom) { + int i_prev = + i < new_children_top ? i : prev_layers.size() - (layers_.size() - i); + auto layer = layers_[i]; + auto prev_layer = prev_layers[i_prev]; + auto paint_region = context->GetOldLayerPaintRegion(prev_layer.get()); + if (layer == prev_layer && !paint_region.has_readback()) { + // for retained layers, stop processing the subtree and add existing + // region; We know current subtree is not dirty (every ancestor up to + // here matches) so the retained subtree will render identically to + // previous frame; We can only do this if there is no readback in the + // subtree. Layers that do readback must be able to register readback + // inside Diff + context->AddExistingPaintRegion(paint_region); + + // While we don't need to diff retained layers, we still need to + // associate their paint region with current layer tree so that we can + // retrieve it in next frame diff + layer->PreservePaintRegion(context); + } else { + layer->Diff(context, prev_layer.get()); + } + } else { + DiffContext::AutoSubtreeRestore subtree(context); + context->MarkSubtreeDirty(); + auto layer = layers_[i]; + layer->Diff(context, nullptr); + } + } +} + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void ContainerLayer::Add(std::shared_ptr layer) { layers_.emplace_back(std::move(layer)); } @@ -145,6 +241,12 @@ MergedContainerLayer::MergedContainerLayer() { ContainerLayer::Add(std::make_shared()); } +void MergedContainerLayer::AssignOldLayer(Layer* old_layer) { + ContainerLayer::AssignOldLayer(old_layer); + auto layer = static_cast(old_layer); + GetChildContainer()->AssignOldLayer(layer->GetChildContainer()); +} + void MergedContainerLayer::Add(std::shared_ptr layer) { GetChildContainer()->Add(std::move(layer)); } diff --git a/flow/layers/container_layer.h b/flow/layers/container_layer.h index 11eae5f3fe..e068560ee2 100644 --- a/flow/layers/container_layer.h +++ b/flow/layers/container_layer.h @@ -15,6 +15,13 @@ class ContainerLayer : public Layer { public: ContainerLayer(); +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + void Diff(DiffContext* context, const Layer* old_layer) override; + void PreservePaintRegion(DiffContext* context) override; + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + virtual void Add(std::shared_ptr layer); void Preroll(PrerollContext* context, const SkMatrix& matrix) override; @@ -27,6 +34,12 @@ class ContainerLayer : public Layer { const std::vector>& layers() const { return layers_; } protected: +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + void DiffChildren(DiffContext* context, const ContainerLayer* old_layer); + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void PrerollChildren(PrerollContext* context, const SkMatrix& child_matrix, SkRect* child_paint_bounds); @@ -101,6 +114,8 @@ class MergedContainerLayer : public ContainerLayer { public: MergedContainerLayer(); + void AssignOldLayer(Layer* old_layer) override; + void Add(std::shared_ptr layer) override; protected: diff --git a/flow/layers/container_layer_unittests.cc b/flow/layers/container_layer_unittests.cc index a8111c2920..53af6275d1 100644 --- a/flow/layers/container_layer_unittests.cc +++ b/flow/layers/container_layer_unittests.cc @@ -4,6 +4,7 @@ #include "flutter/flow/layers/container_layer.h" +#include "flutter/flow/testing/diff_context_test.h" #include "flutter/flow/testing/layer_test.h" #include "flutter/flow/testing/mock_layer.h" #include "flutter/fml/macros.h" @@ -268,5 +269,212 @@ TEST_F(ContainerLayerTest, MergedMultipleChildren) { child_path2, child_paint2}}})); } +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +using ContainerLayerDiffTest = DiffContextTest; + +// Insert PictureLayer amongst container layers +TEST_F(ContainerLayerDiffTest, PictureLayerInsertion) { + auto pic1 = CreatePicture(SkRect::MakeLTRB(0, 0, 50, 50), 1); + auto pic2 = CreatePicture(SkRect::MakeLTRB(100, 0, 150, 50), 1); + auto pic3 = CreatePicture(SkRect::MakeLTRB(200, 0, 250, 50), 1); + + MockLayerTree t1; + + auto t1_c1 = CreateContainerLayer(CreatePictureLayer(pic1)); + t1.root()->Add(t1_c1); + + auto t1_c2 = CreateContainerLayer(CreatePictureLayer(pic2)); + t1.root()->Add(t1_c2); + + auto damage = DiffLayerTree(t1, MockLayerTree()); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(0, 0, 150, 50)); + + // Add in the middle + + MockLayerTree t2; + auto t2_c1 = CreateContainerLayer(CreatePictureLayer(pic1)); + t2_c1->AssignOldLayer(t1_c1.get()); + t2.root()->Add(t2_c1); + + t2.root()->Add(CreatePictureLayer(pic3)); + + auto t2_c2 = CreateContainerLayer(CreatePictureLayer(pic2)); + t2_c2->AssignOldLayer(t1_c2.get()); + t2.root()->Add(t2_c2); + + damage = DiffLayerTree(t2, t1); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(200, 0, 250, 50)); + + // Add in the beginning + + t2 = MockLayerTree(); + t2.root()->Add(CreatePictureLayer(pic3)); + t2.root()->Add(t2_c1); + t2.root()->Add(t2_c2); + damage = DiffLayerTree(t2, t1); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(200, 0, 250, 50)); + + // Add at the end + + t2 = MockLayerTree(); + t2.root()->Add(t2_c1); + t2.root()->Add(t2_c2); + t2.root()->Add(CreatePictureLayer(pic3)); + damage = DiffLayerTree(t2, t1); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(200, 0, 250, 50)); +} + +// Insert picture layer amongst other picture layers +TEST_F(ContainerLayerDiffTest, PictureInsertion) { + auto pic1 = CreatePicture(SkRect::MakeLTRB(0, 0, 50, 50), 1); + auto pic2 = CreatePicture(SkRect::MakeLTRB(100, 0, 150, 50), 1); + auto pic3 = CreatePicture(SkRect::MakeLTRB(200, 0, 250, 50), 1); + + MockLayerTree t1; + t1.root()->Add(CreatePictureLayer(pic1)); + t1.root()->Add(CreatePictureLayer(pic2)); + + auto damage = DiffLayerTree(t1, MockLayerTree()); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(0, 0, 150, 50)); + + MockLayerTree t2; + t2.root()->Add(CreatePictureLayer(pic3)); + t2.root()->Add(CreatePictureLayer(pic1)); + t2.root()->Add(CreatePictureLayer(pic2)); + + damage = DiffLayerTree(t2, t1); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(200, 0, 250, 50)); + + MockLayerTree t3; + t3.root()->Add(CreatePictureLayer(pic1)); + t3.root()->Add(CreatePictureLayer(pic3)); + t3.root()->Add(CreatePictureLayer(pic2)); + + damage = DiffLayerTree(t3, t1); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(200, 0, 250, 50)); + + MockLayerTree t4; + t4.root()->Add(CreatePictureLayer(pic1)); + t4.root()->Add(CreatePictureLayer(pic2)); + t4.root()->Add(CreatePictureLayer(pic3)); + + damage = DiffLayerTree(t4, t1); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(200, 0, 250, 50)); +} + +TEST_F(ContainerLayerDiffTest, LayerDeletion) { + auto path1 = SkPath().addRect(SkRect::MakeLTRB(0, 0, 50, 50)); + auto path2 = SkPath().addRect(SkRect::MakeLTRB(100, 0, 150, 50)); + auto path3 = SkPath().addRect(SkRect::MakeLTRB(200, 0, 250, 50)); + + auto c1 = CreateContainerLayer(std::make_shared(path1)); + auto c2 = CreateContainerLayer(std::make_shared(path2)); + auto c3 = CreateContainerLayer(std::make_shared(path3)); + + MockLayerTree t1; + t1.root()->Add(c1); + t1.root()->Add(c2); + t1.root()->Add(c3); + + auto damage = DiffLayerTree(t1, MockLayerTree()); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(0, 0, 250, 50)); + + MockLayerTree t2; + t2.root()->Add(c2); + t2.root()->Add(c3); + + damage = DiffLayerTree(t2, t1); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(0, 0, 50, 50)); + + MockLayerTree t3; + t3.root()->Add(c1); + t3.root()->Add(c3); + + damage = DiffLayerTree(t3, t1); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(100, 0, 150, 50)); + + MockLayerTree t4; + t4.root()->Add(c1); + t4.root()->Add(c2); + + damage = DiffLayerTree(t4, t1); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(200, 0, 250, 50)); + + MockLayerTree t5; + t5.root()->Add(c1); + + damage = DiffLayerTree(t5, t1); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(100, 0, 250, 50)); + + MockLayerTree t6; + t6.root()->Add(c2); + + damage = DiffLayerTree(t6, t1); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(0, 0, 250, 50)); + + MockLayerTree t7; + t7.root()->Add(c3); + + damage = DiffLayerTree(t7, t1); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(0, 0, 150, 50)); +} + +TEST_F(ContainerLayerDiffTest, ReplaceLayer) { + auto path1 = SkPath().addRect(SkRect::MakeLTRB(0, 0, 50, 50)); + auto path2 = SkPath().addRect(SkRect::MakeLTRB(100, 0, 150, 50)); + auto path3 = SkPath().addRect(SkRect::MakeLTRB(200, 0, 250, 50)); + + auto path1a = SkPath().addRect(SkRect::MakeLTRB(0, 100, 50, 150)); + auto path2a = SkPath().addRect(SkRect::MakeLTRB(100, 100, 150, 150)); + auto path3a = SkPath().addRect(SkRect::MakeLTRB(200, 100, 250, 150)); + + auto c1 = CreateContainerLayer(std::make_shared(path1)); + auto c2 = CreateContainerLayer(std::make_shared(path2)); + auto c3 = CreateContainerLayer(std::make_shared(path3)); + + MockLayerTree t1; + t1.root()->Add(c1); + t1.root()->Add(c2); + t1.root()->Add(c3); + + auto damage = DiffLayerTree(t1, MockLayerTree()); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(0, 0, 250, 50)); + + MockLayerTree t2; + t2.root()->Add(c1); + t2.root()->Add(c2); + t2.root()->Add(c3); + + damage = DiffLayerTree(t2, t1); + EXPECT_TRUE(damage.frame_damage.isEmpty()); + + MockLayerTree t3; + t3.root()->Add(CreateContainerLayer({std::make_shared(path1a)})); + t3.root()->Add(c2); + t3.root()->Add(c3); + + damage = DiffLayerTree(t3, t1); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(0, 0, 50, 150)); + + MockLayerTree t4; + t4.root()->Add(c1); + t4.root()->Add(CreateContainerLayer(std::make_shared(path2a))); + t4.root()->Add(c3); + + damage = DiffLayerTree(t4, t1); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(100, 0, 150, 150)); + + MockLayerTree t5; + t5.root()->Add(c1); + t5.root()->Add(c2); + t5.root()->Add(CreateContainerLayer(std::make_shared(path3a))); + + damage = DiffLayerTree(t5, t1); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(200, 0, 250, 150)); +} + +#endif + } // namespace testing } // namespace flutter diff --git a/flow/layers/image_filter_layer.cc b/flow/layers/image_filter_layer.cc index da6d354d07..a252d457cb 100644 --- a/flow/layers/image_filter_layer.cc +++ b/flow/layers/image_filter_layer.cc @@ -11,6 +11,44 @@ ImageFilterLayer::ImageFilterLayer(sk_sp filter) transformed_filter_(nullptr), render_count_(1) {} +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +void ImageFilterLayer::Diff(DiffContext* context, const Layer* old_layer) { + DiffContext::AutoSubtreeRestore subtree(context); + auto* prev = static_cast(old_layer); + if (!context->IsSubtreeDirty()) { + FML_DCHECK(prev); + if (filter_ != prev->filter_) { + context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer)); + } + } + + DiffChildren(context, prev); + + SkMatrix inverse; + if (context->GetTransform().invert(&inverse)) { + auto screen_bounds = context->CurrentSubtreeRegion().ComputeBounds(); + + auto filter = filter_->makeWithLocalMatrix(context->GetTransform()); + + auto filter_bounds = + filter->filterBounds(screen_bounds.roundOut(), SkMatrix::I(), + SkImageFilter::kForward_MapDirection); + context->AddLayerBounds(inverse.mapRect(SkRect::Make(filter_bounds))); + + // Technically, there is no readback with ImageFilterLayer, but we can't + // clip the filter (because it may sample out of clip rect) so if any part + // of layer is repainted the whole layer needs to be. + // TODO(knopp) There is a room for optimization here - this doesn't need to + // be done if we know for sure that we're using raster cache + context->AddReadbackRegion(filter_bounds); + } + + context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); +} + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void ImageFilterLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { TRACE_EVENT0("flutter", "ImageFilterLayer::Preroll"); diff --git a/flow/layers/image_filter_layer.h b/flow/layers/image_filter_layer.h index 4b274fef8f..635d57a432 100644 --- a/flow/layers/image_filter_layer.h +++ b/flow/layers/image_filter_layer.h @@ -14,6 +14,12 @@ class ImageFilterLayer : public MergedContainerLayer { public: ImageFilterLayer(sk_sp filter); +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + void Diff(DiffContext* context, const Layer* old_layer) override; + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/image_filter_layer_unittests.cc b/flow/layers/image_filter_layer_unittests.cc index c2cab8d3f6..9fd48b851e 100644 --- a/flow/layers/image_filter_layer_unittests.cc +++ b/flow/layers/image_filter_layer_unittests.cc @@ -4,6 +4,8 @@ #include "flutter/flow/layers/image_filter_layer.h" +#include "flutter/flow/layers/transform_layer.h" +#include "flutter/flow/testing/diff_context_test.h" #include "flutter/flow/testing/layer_test.h" #include "flutter/flow/testing/mock_layer.h" #include "flutter/fml/macros.h" @@ -333,5 +335,57 @@ TEST_F(ImageFilterLayerTest, ChildrenNotCached) { EXPECT_FALSE(raster_cache()->Draw(mock_layer2.get(), cache_canvas)); } +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +using ImageFilterLayerDiffTest = DiffContextTest; + +TEST_F(ImageFilterLayerDiffTest, ImageFilterLayer) { + auto filter = SkImageFilters::Blur(10, 10, SkTileMode::kClamp, nullptr); + + { + // tests later assume 30px paint area, fail early if that's not the case + auto paint_rect = + filter->filterBounds(SkIRect::MakeWH(10, 10), SkMatrix::I(), + SkImageFilter::kForward_MapDirection); + EXPECT_EQ(paint_rect, SkIRect::MakeLTRB(-30, -30, 40, 40)); + } + + MockLayerTree l1; + auto filter_layer = std::make_shared(filter); + auto path = SkPath().addRect(SkRect::MakeLTRB(100, 100, 110, 110)); + filter_layer->Add(std::make_shared(path)); + l1.root()->Add(filter_layer); + + auto damage = DiffLayerTree(l1, MockLayerTree()); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(70, 70, 140, 140)); + + MockLayerTree l2; + auto scale = std::make_shared(SkMatrix::Scale(2.0, 2.0)); + scale->Add(filter_layer); + l2.root()->Add(scale); + + damage = DiffLayerTree(l2, MockLayerTree()); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(140, 140, 280, 280)); + + MockLayerTree l3; + l3.root()->Add(scale); + + // path outside of ImageFilterLayer + auto path1 = SkPath().addRect(SkRect::MakeLTRB(130, 130, 140, 140)); + l3.root()->Add(std::make_shared(path1)); + damage = DiffLayerTree(l3, l2); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(130, 130, 140, 140)); + + // path intersecting ImageFilterLayer, should trigger ImageFilterLayer repaint + MockLayerTree l4; + l4.root()->Add(scale); + auto path2 = SkPath().addRect(SkRect::MakeLTRB(130, 130, 141, 141)); + l4.root()->Add(std::make_shared(path2)); + damage = DiffLayerTree(l4, l3); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(130, 130, 280, 280)); +} + +#endif + } // namespace testing } // namespace flutter diff --git a/flow/layers/layer.cc b/flow/layers/layer.cc index 006f5cb84e..a81607b298 100644 --- a/flow/layers/layer.cc +++ b/flow/layers/layer.cc @@ -12,6 +12,7 @@ namespace flutter { Layer::Layer() : paint_bounds_(SkRect::MakeEmpty()), unique_id_(NextUniqueID()), + original_layer_id_(unique_id_), needs_system_composite_(false) {} Layer::~Layer() = default; diff --git a/flow/layers/layer.h b/flow/layers/layer.h index 60c949f9d5..fa2674105c 100644 --- a/flow/layers/layer.h +++ b/flow/layers/layer.h @@ -9,6 +9,7 @@ #include #include "flutter/common/graphics/texture.h" +#include "flutter/flow/diff_context.h" #include "flutter/flow/embedded_views.h" #include "flutter/flow/instrumentation.h" #include "flutter/flow/raster_cache.h" @@ -35,6 +36,10 @@ namespace flutter { +namespace testing { +class MockLayer; +} // namespace testing + static constexpr SkRect kGiantRect = SkRect::MakeLTRB(-1E9F, -1E9F, 1E9F, 1E9F); // This should be an exact copy of the Clip enum in painting.dart. @@ -69,6 +74,10 @@ struct PrerollContext { bool has_texture_layer = false; }; +class PictureLayer; +class PerformanceOverlayLayer; +class TextureLayer; + // Represents a single composited layer. Created on the UI thread but then // subquently used on the Rasterizer thread. class Layer { @@ -76,6 +85,33 @@ class Layer { Layer(); virtual ~Layer(); + virtual void AssignOldLayer(Layer* old_layer) { + original_layer_id_ = old_layer->original_layer_id_; + } + +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + // Used to establish link between old layer and new layer that replaces it. + // If this method returns true, it is assumed that this layer replaces the old + // layer in tree and is able to diff with it. + virtual bool IsReplacing(DiffContext* context, const Layer* old_layer) const { + return original_layer_id_ == old_layer->original_layer_id_; + } + + // Performs diff with given layer + virtual void Diff(DiffContext* context, const Layer* old_layer) {} + + // Used when diffing retained layer; In case the layer is identical, it + // doesn't need to be diffed, but the paint region needs to be stored in diff + // context so that it can be used in next frame + virtual void PreservePaintRegion(DiffContext* context) { + // retained layer means same instance so 'this' is used to index into both + // current and old region + context->SetLayerPaintRegion(this, context->GetOldLayerPaintRegion(this)); + } + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + virtual void Preroll(PrerollContext* context, const SkMatrix& matrix); // Used during Preroll by layers that employ a saveLayer to manage the @@ -201,8 +237,23 @@ class Layer { return !context.leaf_nodes_canvas->quickReject(paint_bounds_); } + // Propagated unique_id of the first layer in "chain" of replacement layers + // that can be diffed. + uint64_t original_layer_id() const { return original_layer_id_; } + uint64_t unique_id() const { return unique_id_; } +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + virtual const PictureLayer* as_picture_layer() const { return nullptr; } + virtual const TextureLayer* as_texture_layer() const { return nullptr; } + virtual const PerformanceOverlayLayer* as_performance_overlay_layer() const { + return nullptr; + } + virtual const testing::MockLayer* as_mock_layer() const { return nullptr; } + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + protected: #if defined(LEGACY_FUCHSIA_EMBEDDER) bool child_layer_exists_below_ = false; @@ -211,6 +262,7 @@ class Layer { private: SkRect paint_bounds_; uint64_t unique_id_; + uint64_t original_layer_id_; bool needs_system_composite_; static uint64_t NextUniqueID(); diff --git a/flow/layers/layer_tree.h b/flow/layers/layer_tree.h index b59f278296..db8a33b450 100644 --- a/flow/layers/layer_tree.h +++ b/flow/layers/layer_tree.h @@ -49,6 +49,13 @@ class LayerTree { const SkISize& frame_size() const { return frame_size_; } float device_pixel_ratio() const { return device_pixel_ratio_; } +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + const PaintRegionMap& paint_region_map() const { return paint_region_map_; } + PaintRegionMap& paint_region_map() { return paint_region_map_; } + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void RecordBuildTime(fml::TimePoint vsync_start, fml::TimePoint build_start, fml::TimePoint target_time); @@ -90,6 +97,10 @@ class LayerTree { bool checkerboard_raster_cache_images_; bool checkerboard_offscreen_layers_; +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + PaintRegionMap paint_region_map_; +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + FML_DISALLOW_COPY_AND_ASSIGN(LayerTree); }; diff --git a/flow/layers/opacity_layer.cc b/flow/layers/opacity_layer.cc index cc9ec4a42f..5ab3f79cde 100644 --- a/flow/layers/opacity_layer.cc +++ b/flow/layers/opacity_layer.cc @@ -12,6 +12,24 @@ namespace flutter { OpacityLayer::OpacityLayer(SkAlpha alpha, const SkPoint& offset) : alpha_(alpha), offset_(offset) {} +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +void OpacityLayer::Diff(DiffContext* context, const Layer* old_layer) { + DiffContext::AutoSubtreeRestore subtree(context); + auto* prev = static_cast(old_layer); + if (!context->IsSubtreeDirty()) { + FML_DCHECK(prev); + if (alpha_ != prev->alpha_ || offset_ != prev->offset_) { + context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer)); + } + } + context->PushTransform(SkMatrix::Translate(offset_.fX, offset_.fY)); + DiffChildren(context, prev); + context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); +} + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void OpacityLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { TRACE_EVENT0("flutter", "OpacityLayer::Preroll"); FML_DCHECK(!GetChildContainer()->layers().empty()); // We can't be a leaf. diff --git a/flow/layers/opacity_layer.h b/flow/layers/opacity_layer.h index 25f1c7944a..3821dba531 100644 --- a/flow/layers/opacity_layer.h +++ b/flow/layers/opacity_layer.h @@ -27,6 +27,12 @@ class OpacityLayer : public MergedContainerLayer { // the propagation as repainting the OpacityLayer is expensive. OpacityLayer(SkAlpha alpha, const SkPoint& offset); +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + void Diff(DiffContext* context, const Layer* old_layer) override; + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/performance_overlay_layer.cc b/flow/layers/performance_overlay_layer.cc index a2e4a33adf..b9ccee84c7 100644 --- a/flow/layers/performance_overlay_layer.cc +++ b/flow/layers/performance_overlay_layer.cc @@ -74,6 +74,22 @@ PerformanceOverlayLayer::PerformanceOverlayLayer(uint64_t options, } } +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +void PerformanceOverlayLayer::Diff(DiffContext* context, + const Layer* old_layer) { + DiffContext::AutoSubtreeRestore subtree(context); + if (!context->IsSubtreeDirty()) { + FML_DCHECK(old_layer); + auto prev = old_layer->as_performance_overlay_layer(); + context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(prev)); + } + context->AddLayerBounds(paint_bounds()); + context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); +} + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void PerformanceOverlayLayer::Paint(PaintContext& context) const { const int padding = 8; diff --git a/flow/layers/performance_overlay_layer.h b/flow/layers/performance_overlay_layer.h index b1434a221e..91b90244e3 100644 --- a/flow/layers/performance_overlay_layer.h +++ b/flow/layers/performance_overlay_layer.h @@ -26,6 +26,20 @@ class PerformanceOverlayLayer : public Layer { const std::string& label_prefix, const std::string& font_path); +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + bool IsReplacing(DiffContext* context, const Layer* layer) const override { + return layer->as_performance_overlay_layer() != nullptr; + } + + void Diff(DiffContext* context, const Layer* old_layer) override; + + const PerformanceOverlayLayer* as_performance_overlay_layer() const override { + return this; + } + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + explicit PerformanceOverlayLayer(uint64_t options, const char* font_path = nullptr); diff --git a/flow/layers/physical_shape_layer.cc b/flow/layers/physical_shape_layer.cc index 8f8ecbb5e9..7a08c12004 100644 --- a/flow/layers/physical_shape_layer.cc +++ b/flow/layers/physical_shape_layer.cc @@ -23,6 +23,38 @@ PhysicalShapeLayer::PhysicalShapeLayer(SkColor color, path_(path), clip_behavior_(clip_behavior) {} +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +void PhysicalShapeLayer::Diff(DiffContext* context, const Layer* old_layer) { + DiffContext::AutoSubtreeRestore subtree(context); + auto* prev = static_cast(old_layer); + if (!context->IsSubtreeDirty()) { + FML_DCHECK(prev); + if (color_ != prev->color_ || shadow_color_ != prev->shadow_color_ || + elevation_ != prev->elevation() || path_ != prev->path_ || + clip_behavior_ != prev->clip_behavior_) { + context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer)); + } + } + + SkRect bounds; + if (elevation_ == 0) { + bounds = path_.getBounds(); + } else { + bounds = ComputeShadowBounds(path_.getBounds(), elevation_, + context->frame_device_pixel_ratio()); + } + + context->AddLayerBounds(bounds); + + if (context->PushCullRect(bounds)) { + DiffChildren(context, prev); + } + context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); +} + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void PhysicalShapeLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { TRACE_EVENT0("flutter", "PhysicalShapeLayer::Preroll"); diff --git a/flow/layers/physical_shape_layer.h b/flow/layers/physical_shape_layer.h index ce49af1a00..a090367db1 100644 --- a/flow/layers/physical_shape_layer.h +++ b/flow/layers/physical_shape_layer.h @@ -27,6 +27,12 @@ class PhysicalShapeLayer : public ContainerLayer { bool transparentOccluder, SkScalar dpr); +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + void Diff(DiffContext* context, const Layer* old_layer) override; + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/picture_layer.cc b/flow/layers/picture_layer.cc index f28c47045c..fcb95933a8 100644 --- a/flow/layers/picture_layer.cc +++ b/flow/layers/picture_layer.cc @@ -5,6 +5,7 @@ #include "flutter/flow/layers/picture_layer.h" #include "flutter/fml/logging.h" +#include "third_party/skia/include/core/SkSerialProcs.h" namespace flutter { @@ -17,6 +18,94 @@ PictureLayer::PictureLayer(const SkPoint& offset, is_complex_(is_complex), will_change_(will_change) {} +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +bool PictureLayer::IsReplacing(DiffContext* context, const Layer* layer) const { + // Only return true for identical pictures; This way + // ContainerLayer::DiffChildren can detect when a picture layer got inserted + // between other picture layers + auto picture_layer = layer->as_picture_layer(); + return picture_layer != nullptr && offset_ == picture_layer->offset_ && + Compare(context->statistics(), this, picture_layer); +} + +void PictureLayer::Diff(DiffContext* context, const Layer* old_layer) { + DiffContext::AutoSubtreeRestore subtree(context); + if (!context->IsSubtreeDirty()) { +#ifndef NDEBUG + FML_DCHECK(old_layer); + auto prev = old_layer->as_picture_layer(); + DiffContext::Statistics dummy_statistics; + // IsReplacing has already determined that the picture is same + FML_DCHECK(prev->offset_ == offset_ && + Compare(dummy_statistics, this, prev)); +#endif + } + context->PushTransform(SkMatrix::Translate(offset_.x(), offset_.y())); + context->AddLayerBounds(picture()->cullRect()); + context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); +} + +bool PictureLayer::Compare(DiffContext::Statistics& statistics, + const PictureLayer* l1, + const PictureLayer* l2) { + const auto& pic1 = l1->picture_.get(); + const auto& pic2 = l2->picture_.get(); + if (pic1.get() == pic2.get()) { + statistics.AddSameInstancePicture(); + return true; + } + auto op_cnt_1 = pic1->approximateOpCount(); + auto op_cnt_2 = pic2->approximateOpCount(); + if (op_cnt_1 != op_cnt_2 || pic1->cullRect() != pic2->cullRect()) { + statistics.AddNewPicture(); + return false; + } + + if (op_cnt_1 > 10) { + statistics.AddPictureTooComplexToCompare(); + return false; + } + + statistics.AddDeepComparePicture(); + + // TODO(knopp) we don't actually need the data; this could be done without + // allocations by implementing stream that calculates SHA hash and + // comparing those hashes + auto d1 = l1->SerializedPicture(); + auto d2 = l2->SerializedPicture(); + auto res = d1->equals(d2.get()); + if (res) { + statistics.AddDifferentInstanceButEqualPicture(); + } else { + statistics.AddNewPicture(); + } + return res; +} + +sk_sp PictureLayer::SerializedPicture() const { + if (!cached_serialized_picture_) { + SkSerialProcs procs = { + nullptr, + nullptr, + [](SkImage* i, void* ctx) { + auto id = i->uniqueID(); + return SkData::MakeWithCopy(&id, sizeof(id)); + }, + nullptr, + [](SkTypeface* tf, void* ctx) { + auto id = tf->uniqueID(); + return SkData::MakeWithCopy(&id, sizeof(id)); + }, + nullptr, + }; + cached_serialized_picture_ = picture_.get()->serialize(&procs); + } + return cached_serialized_picture_; +} + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void PictureLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { TRACE_EVENT0("flutter", "PictureLayer::Preroll"); diff --git a/flow/layers/picture_layer.h b/flow/layers/picture_layer.h index e733e7455c..53fed5706b 100644 --- a/flow/layers/picture_layer.h +++ b/flow/layers/picture_layer.h @@ -22,6 +22,16 @@ class PictureLayer : public Layer { SkPicture* picture() const { return picture_.get().get(); } +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + bool IsReplacing(DiffContext* context, const Layer* layer) const override; + + void Diff(DiffContext* context, const Layer* old_layer) override; + + const PictureLayer* as_picture_layer() const override { return this; } + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void Preroll(PrerollContext* frame, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; @@ -34,6 +44,16 @@ class PictureLayer : public Layer { bool is_complex_ = false; bool will_change_ = false; +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + sk_sp SerializedPicture() const; + mutable sk_sp cached_serialized_picture_; + static bool Compare(DiffContext::Statistics& statistics, + const PictureLayer* l1, + const PictureLayer* l2); + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + FML_DISALLOW_COPY_AND_ASSIGN(PictureLayer); }; diff --git a/flow/layers/picture_layer_unittests.cc b/flow/layers/picture_layer_unittests.cc index 861921e132..f3d9ceddba 100644 --- a/flow/layers/picture_layer_unittests.cc +++ b/flow/layers/picture_layer_unittests.cc @@ -6,6 +6,7 @@ #include "flutter/flow/layers/picture_layer.h" +#include "flutter/flow/testing/diff_context_test.h" #include "flutter/flow/testing/skia_gpu_object_layer_test.h" #include "flutter/fml/macros.h" #include "flutter/testing/mock_canvas.h" @@ -98,5 +99,63 @@ TEST_F(PictureLayerTest, SimplePicture) { EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls); } +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +using PictureLayerDiffTest = DiffContextTest; + +TEST_F(PictureLayerDiffTest, SimplePicture) { + auto picture = CreatePicture(SkRect::MakeLTRB(10, 10, 60, 60), 1); + + MockLayerTree tree1; + tree1.root()->Add(CreatePictureLayer(picture)); + + auto damage = DiffLayerTree(tree1, MockLayerTree()); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(10, 10, 60, 60)); + + MockLayerTree tree2; + tree2.root()->Add(CreatePictureLayer(picture)); + + damage = DiffLayerTree(tree2, tree1); + EXPECT_TRUE(damage.frame_damage.isEmpty()); + + MockLayerTree tree3; + damage = DiffLayerTree(tree3, tree2); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(10, 10, 60, 60)); +} + +TEST_F(PictureLayerDiffTest, PictureCompare) { + MockLayerTree tree1; + auto picture1 = CreatePicture(SkRect::MakeLTRB(10, 10, 60, 60), 1); + tree1.root()->Add(CreatePictureLayer(picture1)); + + auto damage = DiffLayerTree(tree1, MockLayerTree()); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(10, 10, 60, 60)); + + MockLayerTree tree2; + auto picture2 = CreatePicture(SkRect::MakeLTRB(10, 10, 60, 60), 1); + tree2.root()->Add(CreatePictureLayer(picture2)); + + damage = DiffLayerTree(tree2, tree1); + EXPECT_TRUE(damage.frame_damage.isEmpty()); + + MockLayerTree tree3; + auto picture3 = CreatePicture(SkRect::MakeLTRB(10, 10, 60, 60), 1); + // add offset + tree3.root()->Add(CreatePictureLayer(picture3, SkPoint::Make(10, 10))); + + damage = DiffLayerTree(tree3, tree2); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(10, 10, 70, 70)); + + MockLayerTree tree4; + // different color + auto picture4 = CreatePicture(SkRect::MakeLTRB(10, 10, 60, 60), 2); + tree4.root()->Add(CreatePictureLayer(picture4, SkPoint::Make(10, 10))); + + damage = DiffLayerTree(tree4, tree3); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(20, 20, 70, 70)); +} + +#endif + } // namespace testing } // namespace flutter diff --git a/flow/layers/shader_mask_layer.cc b/flow/layers/shader_mask_layer.cc index 091b447ac3..190bf68e06 100644 --- a/flow/layers/shader_mask_layer.cc +++ b/flow/layers/shader_mask_layer.cc @@ -11,6 +11,26 @@ ShaderMaskLayer::ShaderMaskLayer(sk_sp shader, SkBlendMode blend_mode) : shader_(shader), mask_rect_(mask_rect), blend_mode_(blend_mode) {} +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +void ShaderMaskLayer::Diff(DiffContext* context, const Layer* old_layer) { + DiffContext::AutoSubtreeRestore subtree(context); + auto* prev = static_cast(old_layer); + if (!context->IsSubtreeDirty()) { + FML_DCHECK(prev); + if (shader_ != prev->shader_ || mask_rect_ != prev->mask_rect_ || + blend_mode_ != prev->blend_mode_) { + context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer)); + } + } + + DiffChildren(context, prev); + + context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); +} + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void ShaderMaskLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { #if defined(LEGACY_FUCHSIA_EMBEDDER) CheckForChildLayerBelow(context); diff --git a/flow/layers/shader_mask_layer.h b/flow/layers/shader_mask_layer.h index 9bfce9644b..193ba5baf1 100644 --- a/flow/layers/shader_mask_layer.h +++ b/flow/layers/shader_mask_layer.h @@ -16,6 +16,12 @@ class ShaderMaskLayer : public ContainerLayer { const SkRect& mask_rect, SkBlendMode blend_mode); +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + void Diff(DiffContext* context, const Layer* old_layer) override; + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/texture_layer.cc b/flow/layers/texture_layer.cc index 2ddc8a7b40..0608bf2034 100644 --- a/flow/layers/texture_layer.cc +++ b/flow/layers/texture_layer.cc @@ -19,6 +19,24 @@ TextureLayer::TextureLayer(const SkPoint& offset, freeze_(freeze), sampling_(sampling) {} +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +void TextureLayer::Diff(DiffContext* context, const Layer* old_layer) { + DiffContext::AutoSubtreeRestore subtree(context); + if (!context->IsSubtreeDirty()) { + FML_DCHECK(old_layer); + auto prev = old_layer->as_texture_layer(); + // TODO(knopp) It would be nice to be able to determine that a texture is + // dirty + context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(prev)); + } + context->AddLayerBounds(SkRect::MakeXYWH(offset_.x(), offset_.y(), + size_.width(), size_.height())); + context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); +} + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void TextureLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { TRACE_EVENT0("flutter", "TextureLayer::Preroll"); diff --git a/flow/layers/texture_layer.h b/flow/layers/texture_layer.h index ed6f027064..d784ecd79e 100644 --- a/flow/layers/texture_layer.h +++ b/flow/layers/texture_layer.h @@ -20,6 +20,18 @@ class TextureLayer : public Layer { bool freeze, const SkSamplingOptions& sampling); +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + bool IsReplacing(DiffContext* context, const Layer* layer) const override { + return layer->as_texture_layer() != nullptr; + } + + void Diff(DiffContext* context, const Layer* old_layer) override; + + const TextureLayer* as_texture_layer() const override { return this; } + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/transform_layer.cc b/flow/layers/transform_layer.cc index 839fbe05af..8363a55ab3 100644 --- a/flow/layers/transform_layer.cc +++ b/flow/layers/transform_layer.cc @@ -26,6 +26,24 @@ TransformLayer::TransformLayer(const SkMatrix& transform) } } +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +void TransformLayer::Diff(DiffContext* context, const Layer* old_layer) { + DiffContext::AutoSubtreeRestore subtree(context); + auto* prev = static_cast(old_layer); + if (!context->IsSubtreeDirty()) { + FML_DCHECK(prev); + if (transform_ != prev->transform_) { + context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer)); + } + } + context->PushTransform(transform_); + DiffChildren(context, prev); + context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); +} + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void TransformLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { TRACE_EVENT0("flutter", "TransformLayer::Preroll"); diff --git a/flow/layers/transform_layer.h b/flow/layers/transform_layer.h index 7956e3906a..f628edbc34 100644 --- a/flow/layers/transform_layer.h +++ b/flow/layers/transform_layer.h @@ -15,6 +15,12 @@ class TransformLayer : public ContainerLayer { public: TransformLayer(const SkMatrix& transform); +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + void Diff(DiffContext* context, const Layer* old_layer) override; + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/transform_layer_unittests.cc b/flow/layers/transform_layer_unittests.cc index 5a39331907..969fa117eb 100644 --- a/flow/layers/transform_layer_unittests.cc +++ b/flow/layers/transform_layer_unittests.cc @@ -4,6 +4,7 @@ #include "flutter/flow/layers/transform_layer.h" +#include "flutter/flow/testing/diff_context_test.h" #include "flutter/flow/testing/layer_test.h" #include "flutter/flow/testing/mock_layer.h" #include "flutter/fml/macros.h" @@ -224,5 +225,115 @@ TEST_F(TransformLayerTest, NestedSeparated) { MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); } +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +using TransformLayerLayerDiffTest = DiffContextTest; + +TEST_F(TransformLayerLayerDiffTest, Transform) { + auto path1 = SkPath().addRect(SkRect::MakeLTRB(0, 0, 50, 50)); + auto m1 = std::make_shared(path1); + + auto transform1 = + std::make_shared(SkMatrix::Translate(10, 10)); + transform1->Add(m1); + + MockLayerTree t1; + t1.root()->Add(transform1); + + auto damage = DiffLayerTree(t1, MockLayerTree()); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(10, 10, 60, 60)); + + auto transform2 = + std::make_shared(SkMatrix::Translate(20, 20)); + transform2->Add(m1); + transform2->AssignOldLayer(transform1.get()); + + MockLayerTree t2; + t2.root()->Add(transform2); + + damage = DiffLayerTree(t2, t1); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(10, 10, 70, 70)); + + auto transform3 = + std::make_shared(SkMatrix::Translate(20, 20)); + transform3->Add(m1); + transform3->AssignOldLayer(transform2.get()); + + MockLayerTree t3; + t3.root()->Add(transform3); + + damage = DiffLayerTree(t3, t2); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeEmpty()); +} + +TEST_F(TransformLayerLayerDiffTest, TransformNested) { + auto path1 = SkPath().addRect(SkRect::MakeLTRB(0, 0, 50, 50)); + auto m1 = CreateContainerLayer(std::make_shared(path1)); + auto m2 = CreateContainerLayer(std::make_shared(path1)); + auto m3 = CreateContainerLayer(std::make_shared(path1)); + + auto transform1 = std::make_shared(SkMatrix::Scale(2.0, 2.0)); + + auto transform1_1 = + std::make_shared(SkMatrix::Translate(10, 10)); + transform1_1->Add(m1); + transform1->Add(transform1_1); + + auto transform1_2 = + std::make_shared(SkMatrix::Translate(100, 100)); + transform1_2->Add(m2); + transform1->Add(transform1_2); + + auto transform1_3 = + std::make_shared(SkMatrix::Translate(200, 200)); + transform1_3->Add(m3); + transform1->Add(transform1_3); + + MockLayerTree l1; + l1.root()->Add(transform1); + + auto damage = DiffLayerTree(l1, MockLayerTree()); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(20, 20, 500, 500)); + + auto transform2 = std::make_shared(SkMatrix::Scale(2.0, 2.0)); + + auto transform2_1 = + std::make_shared(SkMatrix::Translate(10, 10)); + transform2_1->Add(m1); + transform2_1->AssignOldLayer(transform1_1.get()); + transform2->Add(transform2_1); + + // Offset 1px from transform1_2 so that they're not the same + auto transform2_2 = + std::make_shared(SkMatrix::Translate(100, 101)); + transform2_2->Add(m2); + transform2_2->AssignOldLayer(transform1_2.get()); + transform2->Add(transform2_2); + + auto transform2_3 = + std::make_shared(SkMatrix::Translate(200, 200)); + transform2_3->Add(m3); + transform2_3->AssignOldLayer(transform1_3.get()); + transform2->Add(transform2_3); + + MockLayerTree l2; + l2.root()->Add(transform2); + + damage = DiffLayerTree(l2, l1); + + // transform2 has not transform1 assigned as old layer, so it should be + // invalidated completely + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(20, 20, 500, 500)); + + // now diff the tree properly, the only difference being transform2_2 and + // transform_2_1 + transform2->AssignOldLayer(transform1.get()); + damage = DiffLayerTree(l2, l1); + + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(200, 200, 300, 302)); +} + +#endif + } // namespace testing } // namespace flutter diff --git a/flow/paint_region.cc b/flow/paint_region.cc new file mode 100644 index 0000000000..76c71195a8 --- /dev/null +++ b/flow/paint_region.cc @@ -0,0 +1,17 @@ +#include "flutter/flow/paint_region.h" + +namespace flutter { + +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +SkRect PaintRegion::ComputeBounds() const { + SkRect res = SkRect::MakeEmpty(); + for (const auto& r : *this) { + res.join(r); + } + return res; +} + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + +} // namespace flutter diff --git a/flow/paint_region.h b/flow/paint_region.h new file mode 100644 index 0000000000..e4ad3b3b35 --- /dev/null +++ b/flow/paint_region.h @@ -0,0 +1,57 @@ +#include +#include "flutter/fml/logging.h" +#include "third_party/skia/include/core/SkRect.h" + +namespace flutter { + +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +// Corresponds to area on the screen where the layer subtree has painted to. +// +// The area is used when adding damage of removed or dirty layer to overall +// damage. +// +// Because there is a PaintRegion for each layer, it must be able to represent +// the area with minimal overhead. This is accomplished by having one +// vector shared between all paint regions, and each paint region +// keeping begin and end index of rects relevant to particular subtree. +// +// All rects are in screen coordinates. +class PaintRegion { + public: + PaintRegion() = default; + PaintRegion(std::shared_ptr> rects, + size_t from, + size_t to, + bool has_readback) + : rects_(rects), from_(from), to_(to), has_readback_(has_readback) {} + + std::vector::const_iterator begin() const { + FML_DCHECK(is_valid()); + return rects_->begin() + from_; + } + + std::vector::const_iterator end() const { + FML_DCHECK(is_valid()); + return rects_->begin() + to_; + } + + // Compute bounds for this region + SkRect ComputeBounds() const; + + bool is_valid() const { return rects_ != nullptr; } + + // Returns true if there is a layer in subtree represented by this region + // that performs readback + bool has_readback() const { return has_readback_; } + + private: + std::shared_ptr> rects_; + size_t from_ = 0; + size_t to_ = 0; + bool has_readback_ = false; +}; + +#endif + +} // namespace flutter diff --git a/flow/testing/diff_context_test.cc b/flow/testing/diff_context_test.cc new file mode 100644 index 0000000000..f167d95480 --- /dev/null +++ b/flow/testing/diff_context_test.cc @@ -0,0 +1,53 @@ +#include "diff_context_test.h" + +namespace flutter { +namespace testing { + +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +DiffContextTest::DiffContextTest() + : unref_queue_(fml::MakeRefCounted( + GetCurrentTaskRunner(), + fml::TimeDelta::FromSeconds(0))) {} + +Damage DiffContextTest::DiffLayerTree(MockLayerTree& layer_tree, + const MockLayerTree& old_layer_tree, + const SkIRect& additional_damage) { + FML_CHECK(layer_tree.size() == old_layer_tree.size()); + + DiffContext dc(layer_tree.size(), 1, layer_tree.paint_region_map(), + old_layer_tree.paint_region_map()); + dc.PushCullRect( + SkRect::MakeIWH(layer_tree.size().width(), layer_tree.size().height())); + layer_tree.root()->Diff(&dc, old_layer_tree.root()); + return dc.ComputeDamage(additional_damage); +} + +sk_sp DiffContextTest::CreatePicture(const SkRect& bounds, + uint32_t color) { + SkPictureRecorder recorder; + SkCanvas* recording_canvas = recorder.beginRecording(bounds); + recording_canvas->drawRect(bounds, SkPaint(SkColor4f::FromBytes_RGBA(color))); + return recorder.finishRecordingAsPicture(); +} + +std::shared_ptr DiffContextTest::CreatePictureLayer( + sk_sp picture, + const SkPoint& offset) { + return std::make_shared( + offset, SkiaGPUObject(picture, unref_queue()), false, false); +} + +std::shared_ptr DiffContextTest::CreateContainerLayer( + std::initializer_list> layers) { + auto res = std::make_shared(); + for (const auto& l : layers) { + res->Add(l); + } + return res; +} + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + +} // namespace testing +} // namespace flutter diff --git a/flow/testing/diff_context_test.h b/flow/testing/diff_context_test.h new file mode 100644 index 0000000000..3d1cbab1d3 --- /dev/null +++ b/flow/testing/diff_context_test.h @@ -0,0 +1,64 @@ +#include "flutter/flow/layers/container_layer.h" +#include "flutter/flow/layers/picture_layer.h" +#include "flutter/flow/testing/skia_gpu_object_layer_test.h" +#include "third_party/skia/include/core/SkPicture.h" +#include "third_party/skia/include/core/SkPictureRecorder.h" + +namespace flutter { +namespace testing { + +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +class MockLayerTree { + public: + explicit MockLayerTree(SkISize size = SkISize::Make(1000, 1000)) + : root_(std::make_shared()), size_(size) {} + + ContainerLayer* root() { return root_.get(); } + const ContainerLayer* root() const { return root_.get(); } + + PaintRegionMap& paint_region_map() { return paint_region_map_; } + const PaintRegionMap& paint_region_map() const { return paint_region_map_; } + + const SkISize& size() const { return size_; } + + private: + std::shared_ptr root_; + PaintRegionMap paint_region_map_; + SkISize size_; +}; + +class DiffContextTest : public ThreadTest { + public: + DiffContextTest(); + + Damage DiffLayerTree(MockLayerTree& layer_tree, + const MockLayerTree& old_layer_tree, + const SkIRect& additional_damage = SkIRect::MakeEmpty()); + + // Create picture consisting of filled rect with given color; Being able + // to specify different color is useful to test deep comparison of pictures + sk_sp CreatePicture(const SkRect& bounds, uint32_t color); + + std::shared_ptr CreatePictureLayer( + sk_sp picture, + const SkPoint& offset = SkPoint::Make(0, 0)); + + std::shared_ptr CreateContainerLayer( + std::initializer_list> layers); + + std::shared_ptr CreateContainerLayer( + std::shared_ptr l) { + return CreateContainerLayer({l}); + } + + fml::RefPtr unref_queue() { return unref_queue_; } + + private: + fml::RefPtr unref_queue_; +}; + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + +} // namespace testing +} // namespace flutter diff --git a/flow/testing/mock_layer.cc b/flow/testing/mock_layer.cc index 5fc9f60ee8..c70b86ee4a 100644 --- a/flow/testing/mock_layer.cc +++ b/flow/testing/mock_layer.cc @@ -18,6 +18,25 @@ MockLayer::MockLayer(SkPath path, fake_needs_system_composite_(fake_needs_system_composite), fake_reads_surface_(fake_reads_surface) {} +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +bool MockLayer::IsReplacing(DiffContext* context, const Layer* layer) const { + // Similar to PictureLayer, only return true for identical mock layers; + // That way ContainerLayer::DiffChildren can properly detect mock layer + // insertion + auto mock_layer = layer->as_mock_layer(); + return mock_layer && mock_layer->fake_paint_ == fake_paint_ && + mock_layer->fake_paint_path_ == fake_paint_path_; +} + +void MockLayer::Diff(DiffContext* context, const Layer* old_layer) { + DiffContext::AutoSubtreeRestore subtree(context); + context->AddLayerBounds(fake_paint_path_.getBounds()); + context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); +} + +#endif + void MockLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { parent_mutators_ = context->mutators_stack; parent_matrix_ = matrix; diff --git a/flow/testing/mock_layer.h b/flow/testing/mock_layer.h index b92583f581..ed123f9640 100644 --- a/flow/testing/mock_layer.h +++ b/flow/testing/mock_layer.h @@ -30,6 +30,14 @@ class MockLayer : public Layer { const SkRect& parent_cull_rect() { return parent_cull_rect_; } bool parent_has_platform_view() { return parent_has_platform_view_; } +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + bool IsReplacing(DiffContext* context, const Layer* layer) const override; + void Diff(DiffContext* context, const Layer* old_layer) override; + const MockLayer* as_mock_layer() const override { return this; } + +#endif + private: MutatorsStack parent_mutators_; SkMatrix parent_matrix_; diff --git a/lib/ui/compositing.dart b/lib/ui/compositing.dart index 626e61ef4f..59a6e2fee8 100644 --- a/lib/ui/compositing.dart +++ b/lib/ui/compositing.dart @@ -286,13 +286,14 @@ class SceneBuilder extends NativeFieldWrapperClass2 { assert(_matrix4IsValid(matrix4)); assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushTransform')); final EngineLayer engineLayer = EngineLayer._(); - _pushTransform(engineLayer, matrix4); + _pushTransform(engineLayer, matrix4, oldLayer?._nativeLayer); final TransformEngineLayer layer = TransformEngineLayer._(engineLayer); assert(_debugPushLayer(layer)); return layer; } - void _pushTransform(EngineLayer layer, Float64List matrix4) native 'SceneBuilder_pushTransform'; + void _pushTransform(EngineLayer layer, Float64List matrix4, EngineLayer? oldLayer) + native 'SceneBuilder_pushTransform'; /// Pushes an offset operation onto the operation stack. /// @@ -310,13 +311,14 @@ class SceneBuilder extends NativeFieldWrapperClass2 { }) { assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushOffset')); final EngineLayer engineLayer = EngineLayer._(); - _pushOffset(engineLayer, dx, dy); + _pushOffset(engineLayer, dx, dy, oldLayer?._nativeLayer); final OffsetEngineLayer layer = OffsetEngineLayer._(engineLayer); assert(_debugPushLayer(layer)); return layer; } - void _pushOffset(EngineLayer layer, double dx, double dy) native 'SceneBuilder_pushOffset'; + void _pushOffset(EngineLayer layer, double dx, double dy, EngineLayer? oldLayer) + native 'SceneBuilder_pushOffset'; /// Pushes a rectangular clip operation onto the operation stack. /// @@ -337,14 +339,15 @@ class SceneBuilder extends NativeFieldWrapperClass2 { assert(clipBehavior != Clip.none); assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushClipRect')); final EngineLayer engineLayer = EngineLayer._(); - _pushClipRect(engineLayer, rect.left, rect.right, rect.top, rect.bottom, clipBehavior.index); + _pushClipRect(engineLayer, rect.left, rect.right, rect.top, rect.bottom, clipBehavior.index, + oldLayer?._nativeLayer); final ClipRectEngineLayer layer = ClipRectEngineLayer._(engineLayer); assert(_debugPushLayer(layer)); return layer; } - void _pushClipRect(EngineLayer outEngineLayer, double left, double right, double top, double bottom, int clipBehavior) - native 'SceneBuilder_pushClipRect'; + void _pushClipRect(EngineLayer outEngineLayer, double left, double right, double top, + double bottom, int clipBehavior, EngineLayer? oldLayer) native 'SceneBuilder_pushClipRect'; /// Pushes a rounded-rectangular clip operation onto the operation stack. /// @@ -365,13 +368,13 @@ class SceneBuilder extends NativeFieldWrapperClass2 { assert(clipBehavior != Clip.none); assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushClipRRect')); final EngineLayer engineLayer = EngineLayer._(); - _pushClipRRect(engineLayer, rrect._value32, clipBehavior.index); + _pushClipRRect(engineLayer, rrect._value32, clipBehavior.index, oldLayer?._nativeLayer); final ClipRRectEngineLayer layer = ClipRRectEngineLayer._(engineLayer); assert(_debugPushLayer(layer)); return layer; } - void _pushClipRRect(EngineLayer layer, Float32List rrect, int clipBehavior) + void _pushClipRRect(EngineLayer layer, Float32List rrect, int clipBehavior, EngineLayer? oldLayer) native 'SceneBuilder_pushClipRRect'; /// Pushes a path clip operation onto the operation stack. @@ -393,13 +396,14 @@ class SceneBuilder extends NativeFieldWrapperClass2 { assert(clipBehavior != Clip.none); assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushClipPath')); final EngineLayer engineLayer = EngineLayer._(); - _pushClipPath(engineLayer, path, clipBehavior.index); + _pushClipPath(engineLayer, path, clipBehavior.index, oldLayer?._nativeLayer); final ClipPathEngineLayer layer = ClipPathEngineLayer._(engineLayer); assert(_debugPushLayer(layer)); return layer; } - void _pushClipPath(EngineLayer layer, Path path, int clipBehavior) native 'SceneBuilder_pushClipPath'; + void _pushClipPath(EngineLayer layer, Path path, int clipBehavior, EngineLayer? oldLayer) + native 'SceneBuilder_pushClipPath'; /// Pushes an opacity operation onto the operation stack. /// @@ -420,13 +424,14 @@ class SceneBuilder extends NativeFieldWrapperClass2 { }) { assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushOpacity')); final EngineLayer engineLayer = EngineLayer._(); - _pushOpacity(engineLayer, alpha, offset!.dx, offset.dy); + _pushOpacity(engineLayer, alpha, offset!.dx, offset.dy, oldLayer?._nativeLayer); final OpacityEngineLayer layer = OpacityEngineLayer._(engineLayer); assert(_debugPushLayer(layer)); return layer; } - void _pushOpacity(EngineLayer layer, int alpha, double dx, double dy) native 'SceneBuilder_pushOpacity'; + void _pushOpacity(EngineLayer layer, int alpha, double dx, double dy, EngineLayer? oldLayer) + native 'SceneBuilder_pushOpacity'; /// Pushes a color filter operation onto the operation stack. /// @@ -447,13 +452,14 @@ class SceneBuilder extends NativeFieldWrapperClass2 { final _ColorFilter nativeFilter = filter._toNativeColorFilter()!; assert(nativeFilter != null); // ignore: unnecessary_null_comparison final EngineLayer engineLayer = EngineLayer._(); - _pushColorFilter(engineLayer, nativeFilter); + _pushColorFilter(engineLayer, nativeFilter, oldLayer?._nativeLayer); final ColorFilterEngineLayer layer = ColorFilterEngineLayer._(engineLayer); assert(_debugPushLayer(layer)); return layer; } - void _pushColorFilter(EngineLayer layer, _ColorFilter filter) native 'SceneBuilder_pushColorFilter'; + void _pushColorFilter(EngineLayer layer, _ColorFilter filter, EngineLayer? oldLayer) + native 'SceneBuilder_pushColorFilter'; /// Pushes an image filter operation onto the operation stack. /// @@ -474,13 +480,14 @@ class SceneBuilder extends NativeFieldWrapperClass2 { final _ImageFilter nativeFilter = filter._toNativeImageFilter(); assert(nativeFilter != null); // ignore: unnecessary_null_comparison final EngineLayer engineLayer = EngineLayer._(); - _pushImageFilter(engineLayer, nativeFilter); + _pushImageFilter(engineLayer, nativeFilter, oldLayer?._nativeLayer); final ImageFilterEngineLayer layer = ImageFilterEngineLayer._(engineLayer); assert(_debugPushLayer(layer)); return layer; } - void _pushImageFilter(EngineLayer outEngineLayer, _ImageFilter filter) native 'SceneBuilder_pushImageFilter'; + void _pushImageFilter(EngineLayer outEngineLayer, _ImageFilter filter, EngineLayer? oldLayer) + native 'SceneBuilder_pushImageFilter'; /// Pushes a backdrop filter operation onto the operation stack. /// @@ -498,13 +505,14 @@ class SceneBuilder extends NativeFieldWrapperClass2 { }) { assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushBackdropFilter')); final EngineLayer engineLayer = EngineLayer._(); - _pushBackdropFilter(engineLayer, filter._toNativeImageFilter()); + _pushBackdropFilter(engineLayer, filter._toNativeImageFilter(), oldLayer?._nativeLayer); final BackdropFilterEngineLayer layer = BackdropFilterEngineLayer._(engineLayer); assert(_debugPushLayer(layer)); return layer; } - void _pushBackdropFilter(EngineLayer outEngineLayer, _ImageFilter filter) native 'SceneBuilder_pushBackdropFilter'; + void _pushBackdropFilter(EngineLayer outEngineLayer, _ImageFilter filter, EngineLayer? oldLayer) + native 'SceneBuilder_pushBackdropFilter'; /// Pushes a shader mask operation onto the operation stack. /// @@ -532,6 +540,7 @@ class SceneBuilder extends NativeFieldWrapperClass2 { maskRect.top, maskRect.bottom, blendMode.index, + oldLayer?._nativeLayer, ); final ShaderMaskEngineLayer layer = ShaderMaskEngineLayer._(engineLayer); assert(_debugPushLayer(layer)); @@ -545,7 +554,8 @@ class SceneBuilder extends NativeFieldWrapperClass2 { double maskRectRight, double maskRectTop, double maskRectBottom, - int blendMode) native 'SceneBuilder_pushShaderMask'; + int blendMode, + EngineLayer? oldLayer) native 'SceneBuilder_pushShaderMask'; /// Pushes a physical layer operation for an arbitrary shape onto the /// operation stack. @@ -574,21 +584,21 @@ class SceneBuilder extends NativeFieldWrapperClass2 { }) { assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushPhysicalShape')); final EngineLayer engineLayer = EngineLayer._(); - _pushPhysicalShape( - engineLayer, - path, - elevation, - color.value, - shadowColor?.value ?? 0xFF000000, - clipBehavior.index, - ); + _pushPhysicalShape(engineLayer, path, elevation, color.value, shadowColor?.value ?? 0xFF000000, + clipBehavior.index, oldLayer?._nativeLayer); final PhysicalShapeEngineLayer layer = PhysicalShapeEngineLayer._(engineLayer); assert(_debugPushLayer(layer)); return layer; } - void _pushPhysicalShape(EngineLayer outEngineLayer, Path path, double elevation, int color, int shadowColor, - int clipBehavior) native 'SceneBuilder_pushPhysicalShape'; + void _pushPhysicalShape( + EngineLayer outEngineLayer, + Path path, + double elevation, + int color, + int shadowColor, + int clipBehavior, + EngineLayer? oldLayer) native 'SceneBuilder_pushPhysicalShape'; /// Ends the effect of the most recently pushed operation. /// diff --git a/lib/ui/compositing/scene_builder.cc b/lib/ui/compositing/scene_builder.cc index d1d8f00426..5dda608a0f 100644 --- a/lib/ui/compositing/scene_builder.cc +++ b/lib/ui/compositing/scene_builder.cc @@ -90,20 +90,32 @@ SceneBuilder::SceneBuilder() { SceneBuilder::~SceneBuilder() = default; void SceneBuilder::pushTransform(Dart_Handle layer_handle, - tonic::Float64List& matrix4) { + tonic::Float64List& matrix4, + fml::RefPtr oldLayer) { SkMatrix sk_matrix = ToSkMatrix(matrix4); auto layer = std::make_shared(sk_matrix); PushLayer(layer); // matrix4 has to be released before we can return another Dart object matrix4.Release(); EngineLayer::MakeRetained(layer_handle, layer); + + if (oldLayer && oldLayer->Layer()) { + layer->AssignOldLayer(oldLayer->Layer().get()); + } } -void SceneBuilder::pushOffset(Dart_Handle layer_handle, double dx, double dy) { +void SceneBuilder::pushOffset(Dart_Handle layer_handle, + double dx, + double dy, + fml::RefPtr oldLayer) { SkMatrix sk_matrix = SkMatrix::Translate(dx, dy); auto layer = std::make_shared(sk_matrix); PushLayer(layer); EngineLayer::MakeRetained(layer_handle, layer); + + if (oldLayer && oldLayer->Layer()) { + layer->AssignOldLayer(oldLayer->Layer().get()); + } } void SceneBuilder::pushClipRect(Dart_Handle layer_handle, @@ -111,67 +123,102 @@ void SceneBuilder::pushClipRect(Dart_Handle layer_handle, double right, double top, double bottom, - int clipBehavior) { + int clipBehavior, + fml::RefPtr oldLayer) { SkRect clipRect = SkRect::MakeLTRB(left, top, right, bottom); flutter::Clip clip_behavior = static_cast(clipBehavior); auto layer = std::make_shared(clipRect, clip_behavior); PushLayer(layer); EngineLayer::MakeRetained(layer_handle, layer); + + if (oldLayer && oldLayer->Layer()) { + layer->AssignOldLayer(oldLayer->Layer().get()); + } } void SceneBuilder::pushClipRRect(Dart_Handle layer_handle, const RRect& rrect, - int clipBehavior) { + int clipBehavior, + fml::RefPtr oldLayer) { flutter::Clip clip_behavior = static_cast(clipBehavior); auto layer = std::make_shared(rrect.sk_rrect, clip_behavior); PushLayer(layer); EngineLayer::MakeRetained(layer_handle, layer); + + if (oldLayer && oldLayer->Layer()) { + layer->AssignOldLayer(oldLayer->Layer().get()); + } } void SceneBuilder::pushClipPath(Dart_Handle layer_handle, const CanvasPath* path, - int clipBehavior) { + int clipBehavior, + fml::RefPtr oldLayer) { flutter::Clip clip_behavior = static_cast(clipBehavior); FML_DCHECK(clip_behavior != flutter::Clip::none); auto layer = std::make_shared(path->path(), clip_behavior); PushLayer(layer); EngineLayer::MakeRetained(layer_handle, layer); + + if (oldLayer && oldLayer->Layer()) { + layer->AssignOldLayer(oldLayer->Layer().get()); + } } void SceneBuilder::pushOpacity(Dart_Handle layer_handle, int alpha, double dx, - double dy) { + double dy, + fml::RefPtr oldLayer) { auto layer = std::make_shared(alpha, SkPoint::Make(dx, dy)); PushLayer(layer); EngineLayer::MakeRetained(layer_handle, layer); + + if (oldLayer && oldLayer->Layer()) { + layer->AssignOldLayer(oldLayer->Layer().get()); + } } void SceneBuilder::pushColorFilter(Dart_Handle layer_handle, - const ColorFilter* color_filter) { + const ColorFilter* color_filter, + fml::RefPtr oldLayer) { auto layer = std::make_shared(color_filter->filter()); PushLayer(layer); EngineLayer::MakeRetained(layer_handle, layer); + + if (oldLayer && oldLayer->Layer()) { + layer->AssignOldLayer(oldLayer->Layer().get()); + } } void SceneBuilder::pushImageFilter(Dart_Handle layer_handle, - const ImageFilter* image_filter) { + const ImageFilter* image_filter, + fml::RefPtr oldLayer) { auto layer = std::make_shared(image_filter->filter()); PushLayer(layer); EngineLayer::MakeRetained(layer_handle, layer); + + if (oldLayer && oldLayer->Layer()) { + layer->AssignOldLayer(oldLayer->Layer().get()); + } } void SceneBuilder::pushBackdropFilter(Dart_Handle layer_handle, - ImageFilter* filter) { + ImageFilter* filter, + fml::RefPtr oldLayer) { auto layer = std::make_shared(filter->filter()); PushLayer(layer); EngineLayer::MakeRetained(layer_handle, layer); + + if (oldLayer && oldLayer->Layer()) { + layer->AssignOldLayer(oldLayer->Layer().get()); + } } void SceneBuilder::pushShaderMask(Dart_Handle layer_handle, @@ -180,7 +227,8 @@ void SceneBuilder::pushShaderMask(Dart_Handle layer_handle, double maskRectRight, double maskRectTop, double maskRectBottom, - int blendMode) { + int blendMode, + fml::RefPtr oldLayer) { SkRect rect = SkRect::MakeLTRB(maskRectLeft, maskRectTop, maskRectRight, maskRectBottom); // TODO: should this come from the caller? @@ -189,6 +237,10 @@ void SceneBuilder::pushShaderMask(Dart_Handle layer_handle, shader->shader(quality), rect, static_cast(blendMode)); PushLayer(layer); EngineLayer::MakeRetained(layer_handle, layer); + + if (oldLayer && oldLayer->Layer()) { + layer->AssignOldLayer(oldLayer->Layer().get()); + } } void SceneBuilder::pushPhysicalShape(Dart_Handle layer_handle, @@ -196,13 +248,18 @@ void SceneBuilder::pushPhysicalShape(Dart_Handle layer_handle, double elevation, int color, int shadow_color, - int clipBehavior) { + int clipBehavior, + fml::RefPtr oldLayer) { auto layer = std::make_shared( static_cast(color), static_cast(shadow_color), static_cast(elevation), path->path(), static_cast(clipBehavior)); PushLayer(layer); EngineLayer::MakeRetained(layer_handle, layer); + + if (oldLayer && oldLayer->Layer()) { + layer->AssignOldLayer(oldLayer->Layer().get()); + } } void SceneBuilder::addRetained(fml::RefPtr retainedLayer) { diff --git a/lib/ui/compositing/scene_builder.h b/lib/ui/compositing/scene_builder.h index 37f229429e..51ca025c58 100644 --- a/lib/ui/compositing/scene_builder.h +++ b/lib/ui/compositing/scene_builder.h @@ -37,42 +37,57 @@ class SceneBuilder : public RefCountedDartWrappable { } ~SceneBuilder() override; - void pushTransform(Dart_Handle layer_handle, tonic::Float64List& matrix4); - void pushOffset(Dart_Handle layer_handle, double dx, double dy); + void pushTransform(Dart_Handle layer_handle, + tonic::Float64List& matrix4, + fml::RefPtr oldLayer); + void pushOffset(Dart_Handle layer_handle, + double dx, + double dy, + fml::RefPtr oldLayer); void pushClipRect(Dart_Handle layer_handle, double left, double right, double top, double bottom, - int clipBehavior); + int clipBehavior, + fml::RefPtr oldLayer); void pushClipRRect(Dart_Handle layer_handle, const RRect& rrect, - int clipBehavior); + int clipBehavior, + fml::RefPtr oldLayer); void pushClipPath(Dart_Handle layer_handle, const CanvasPath* path, - int clipBehavior); + int clipBehavior, + fml::RefPtr oldLayer); void pushOpacity(Dart_Handle layer_handle, int alpha, - double dx = 0, - double dy = 0); + double dx, + double dy, + fml::RefPtr oldLayer); void pushColorFilter(Dart_Handle layer_handle, - const ColorFilter* color_filter); + const ColorFilter* color_filter, + fml::RefPtr oldLayer); void pushImageFilter(Dart_Handle layer_handle, - const ImageFilter* image_filter); - void pushBackdropFilter(Dart_Handle layer_handle, ImageFilter* filter); + const ImageFilter* image_filter, + fml::RefPtr oldLayer); + void pushBackdropFilter(Dart_Handle layer_handle, + ImageFilter* filter, + fml::RefPtr oldLayer); void pushShaderMask(Dart_Handle layer_handle, Shader* shader, double maskRectLeft, double maskRectRight, double maskRectTop, double maskRectBottom, - int blendMode); + int blendMode, + fml::RefPtr oldLayer); void pushPhysicalShape(Dart_Handle layer_handle, const CanvasPath* path, double elevation, int color, int shadowColor, - int clipBehavior); + int clipBehavior, + fml::RefPtr oldLayer); void addRetained(fml::RefPtr retainedLayer); -- GitLab