未验证 提交 cfa1b8e5 编写于 作者: M Matej Knopp 提交者: GitHub

Add DiffContext (#21824)

上级 6e7ac9ba
......@@ -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") {
......
......@@ -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
......
......@@ -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
......
......@@ -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",
......
#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<std::vector<SkRect>>()),
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<int64_t>(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
#ifndef FLUTTER_FLOW_DIFF_CONTEXT_H_
#define FLUTTER_FLOW_DIFF_CONTEXT_H_
#include <map>
#include <vector>
#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<uint64_t, PaintRegion>;
// 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<std::vector<SkRect>> rects_;
State state_;
SkISize frame_size_;
double frame_device_pixel_ratio_;
std::vector<State> 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<Readback> readbacks_;
Statistics statistics_;
};
#endif // FLUTTER_ENABLE_DIFF_CONTEXT
} // namespace flutter
#endif // FLUTTER_FLOW_DIFF_CONTEXT_H_
......@@ -9,6 +9,40 @@ namespace flutter {
BackdropFilterLayer::BackdropFilterLayer(sk_sp<SkImageFilter> 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<const BackdropFilterLayer*>(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 =
......
......@@ -14,6 +14,12 @@ class BackdropFilterLayer : public ContainerLayer {
public:
BackdropFilterLayer(sk_sp<SkImageFilter> 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;
......
......@@ -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<BackdropFilterLayer>(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<ClipRectLayer>(SkRect::MakeLTRB(20, 20, 60, 60),
Clip::hardEdge);
clip->Add(std::make_shared<BackdropFilterLayer>(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<TransformLayer>(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<MockLayer>(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<MockLayer>(path2));
damage = DiffLayerTree(l5, l4);
EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(0, 0, 190, 190));
}
#endif
} // namespace testing
} // namespace flutter
......@@ -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<const ClipPathLayer*>(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");
......
......@@ -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;
......
......@@ -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<const ClipRectLayer*>(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");
......
......@@ -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;
......
......@@ -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<const ClipRRectLayer*>(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");
......
......@@ -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;
......
......@@ -9,6 +9,25 @@ namespace flutter {
ColorFilterLayer::ColorFilterLayer(sk_sp<SkColorFilter> 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<const ColorFilterLayer*>(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 =
......
......@@ -14,6 +14,12 @@ class ColorFilterLayer : public ContainerLayer {
public:
ColorFilterLayer(sk_sp<SkColorFilter> 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;
......
......@@ -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<const ContainerLayer*>(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<int>(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> layer) {
layers_.emplace_back(std::move(layer));
}
......@@ -145,6 +241,12 @@ MergedContainerLayer::MergedContainerLayer() {
ContainerLayer::Add(std::make_shared<ContainerLayer>());
}
void MergedContainerLayer::AssignOldLayer(Layer* old_layer) {
ContainerLayer::AssignOldLayer(old_layer);
auto layer = static_cast<MergedContainerLayer*>(old_layer);
GetChildContainer()->AssignOldLayer(layer->GetChildContainer());
}
void MergedContainerLayer::Add(std::shared_ptr<Layer> layer) {
GetChildContainer()->Add(std::move(layer));
}
......
......@@ -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> layer);
void Preroll(PrerollContext* context, const SkMatrix& matrix) override;
......@@ -27,6 +34,12 @@ class ContainerLayer : public Layer {
const std::vector<std::shared_ptr<Layer>>& 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> layer) override;
protected:
......
......@@ -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<MockLayer>(path1));
auto c2 = CreateContainerLayer(std::make_shared<MockLayer>(path2));
auto c3 = CreateContainerLayer(std::make_shared<MockLayer>(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<MockLayer>(path1));
auto c2 = CreateContainerLayer(std::make_shared<MockLayer>(path2));
auto c3 = CreateContainerLayer(std::make_shared<MockLayer>(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<MockLayer>(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<MockLayer>(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<MockLayer>(path3a)));
damage = DiffLayerTree(t5, t1);
EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(200, 0, 250, 150));
}
#endif
} // namespace testing
} // namespace flutter
......@@ -11,6 +11,44 @@ ImageFilterLayer::ImageFilterLayer(sk_sp<SkImageFilter> 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<const ImageFilterLayer*>(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");
......
......@@ -14,6 +14,12 @@ class ImageFilterLayer : public MergedContainerLayer {
public:
ImageFilterLayer(sk_sp<SkImageFilter> 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;
......
......@@ -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<ImageFilterLayer>(filter);
auto path = SkPath().addRect(SkRect::MakeLTRB(100, 100, 110, 110));
filter_layer->Add(std::make_shared<MockLayer>(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<TransformLayer>(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<MockLayer>(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<MockLayer>(path2));
damage = DiffLayerTree(l4, l3);
EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(130, 130, 280, 280));
}
#endif
} // namespace testing
} // namespace flutter
......@@ -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;
......
......@@ -9,6 +9,7 @@
#include <vector>
#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();
......
......@@ -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);
};
......
......@@ -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<const OpacityLayer*>(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.
......
......@@ -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;
......
......@@ -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;
......
......@@ -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);
......
......@@ -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<const PhysicalShapeLayer*>(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");
......
......@@ -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;
......
......@@ -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<SkData> 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");
......
......@@ -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<SkData> SerializedPicture() const;
mutable sk_sp<SkData> 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);
};
......
......@@ -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
......@@ -11,6 +11,26 @@ ShaderMaskLayer::ShaderMaskLayer(sk_sp<SkShader> 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<const ShaderMaskLayer*>(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);
......
......@@ -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;
......
......@@ -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");
......
......@@ -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;
......
......@@ -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<const TransformLayer*>(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");
......
......@@ -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;
......
......@@ -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<MockLayer>(path1);
auto transform1 =
std::make_shared<TransformLayer>(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<TransformLayer>(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<TransformLayer>(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<MockLayer>(path1));
auto m2 = CreateContainerLayer(std::make_shared<MockLayer>(path1));
auto m3 = CreateContainerLayer(std::make_shared<MockLayer>(path1));
auto transform1 = std::make_shared<TransformLayer>(SkMatrix::Scale(2.0, 2.0));
auto transform1_1 =
std::make_shared<TransformLayer>(SkMatrix::Translate(10, 10));
transform1_1->Add(m1);
transform1->Add(transform1_1);
auto transform1_2 =
std::make_shared<TransformLayer>(SkMatrix::Translate(100, 100));
transform1_2->Add(m2);
transform1->Add(transform1_2);
auto transform1_3 =
std::make_shared<TransformLayer>(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<TransformLayer>(SkMatrix::Scale(2.0, 2.0));
auto transform2_1 =
std::make_shared<TransformLayer>(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<TransformLayer>(SkMatrix::Translate(100, 101));
transform2_2->Add(m2);
transform2_2->AssignOldLayer(transform1_2.get());
transform2->Add(transform2_2);
auto transform2_3 =
std::make_shared<TransformLayer>(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
#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
#include <vector>
#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<SkRect> 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<std::vector<SkRect>> rects,
size_t from,
size_t to,
bool has_readback)
: rects_(rects), from_(from), to_(to), has_readback_(has_readback) {}
std::vector<SkRect>::const_iterator begin() const {
FML_DCHECK(is_valid());
return rects_->begin() + from_;
}
std::vector<SkRect>::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<std::vector<SkRect>> rects_;
size_t from_ = 0;
size_t to_ = 0;
bool has_readback_ = false;
};
#endif
} // namespace flutter
#include "diff_context_test.h"
namespace flutter {
namespace testing {
#ifdef FLUTTER_ENABLE_DIFF_CONTEXT
DiffContextTest::DiffContextTest()
: unref_queue_(fml::MakeRefCounted<SkiaUnrefQueue>(
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<SkPicture> 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<PictureLayer> DiffContextTest::CreatePictureLayer(
sk_sp<SkPicture> picture,
const SkPoint& offset) {
return std::make_shared<PictureLayer>(
offset, SkiaGPUObject(picture, unref_queue()), false, false);
}
std::shared_ptr<ContainerLayer> DiffContextTest::CreateContainerLayer(
std::initializer_list<std::shared_ptr<Layer>> layers) {
auto res = std::make_shared<ContainerLayer>();
for (const auto& l : layers) {
res->Add(l);
}
return res;
}
#endif // FLUTTER_ENABLE_DIFF_CONTEXT
} // namespace testing
} // namespace flutter
#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<ContainerLayer>()), 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<ContainerLayer> 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<SkPicture> CreatePicture(const SkRect& bounds, uint32_t color);
std::shared_ptr<PictureLayer> CreatePictureLayer(
sk_sp<SkPicture> picture,
const SkPoint& offset = SkPoint::Make(0, 0));
std::shared_ptr<ContainerLayer> CreateContainerLayer(
std::initializer_list<std::shared_ptr<Layer>> layers);
std::shared_ptr<ContainerLayer> CreateContainerLayer(
std::shared_ptr<Layer> l) {
return CreateContainerLayer({l});
}
fml::RefPtr<SkiaUnrefQueue> unref_queue() { return unref_queue_; }
private:
fml::RefPtr<SkiaUnrefQueue> unref_queue_;
};
#endif // FLUTTER_ENABLE_DIFF_CONTEXT
} // namespace testing
} // namespace flutter
......@@ -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;
......
......@@ -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_;
......
......@@ -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.
///
......
......@@ -90,20 +90,32 @@ SceneBuilder::SceneBuilder() {
SceneBuilder::~SceneBuilder() = default;
void SceneBuilder::pushTransform(Dart_Handle layer_handle,
tonic::Float64List& matrix4) {
tonic::Float64List& matrix4,
fml::RefPtr<EngineLayer> oldLayer) {
SkMatrix sk_matrix = ToSkMatrix(matrix4);
auto layer = std::make_shared<flutter::TransformLayer>(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<EngineLayer> oldLayer) {
SkMatrix sk_matrix = SkMatrix::Translate(dx, dy);
auto layer = std::make_shared<flutter::TransformLayer>(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<EngineLayer> oldLayer) {
SkRect clipRect = SkRect::MakeLTRB(left, top, right, bottom);
flutter::Clip clip_behavior = static_cast<flutter::Clip>(clipBehavior);
auto layer =
std::make_shared<flutter::ClipRectLayer>(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<EngineLayer> oldLayer) {
flutter::Clip clip_behavior = static_cast<flutter::Clip>(clipBehavior);
auto layer =
std::make_shared<flutter::ClipRRectLayer>(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<EngineLayer> oldLayer) {
flutter::Clip clip_behavior = static_cast<flutter::Clip>(clipBehavior);
FML_DCHECK(clip_behavior != flutter::Clip::none);
auto layer =
std::make_shared<flutter::ClipPathLayer>(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<EngineLayer> oldLayer) {
auto layer =
std::make_shared<flutter::OpacityLayer>(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<EngineLayer> oldLayer) {
auto layer =
std::make_shared<flutter::ColorFilterLayer>(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<EngineLayer> oldLayer) {
auto layer =
std::make_shared<flutter::ImageFilterLayer>(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<EngineLayer> oldLayer) {
auto layer = std::make_shared<flutter::BackdropFilterLayer>(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<EngineLayer> 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<SkBlendMode>(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<EngineLayer> oldLayer) {
auto layer = std::make_shared<flutter::PhysicalShapeLayer>(
static_cast<SkColor>(color), static_cast<SkColor>(shadow_color),
static_cast<float>(elevation), path->path(),
static_cast<flutter::Clip>(clipBehavior));
PushLayer(layer);
EngineLayer::MakeRetained(layer_handle, layer);
if (oldLayer && oldLayer->Layer()) {
layer->AssignOldLayer(oldLayer->Layer().get());
}
}
void SceneBuilder::addRetained(fml::RefPtr<EngineLayer> retainedLayer) {
......
......@@ -37,42 +37,57 @@ class SceneBuilder : public RefCountedDartWrappable<SceneBuilder> {
}
~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<EngineLayer> oldLayer);
void pushOffset(Dart_Handle layer_handle,
double dx,
double dy,
fml::RefPtr<EngineLayer> oldLayer);
void pushClipRect(Dart_Handle layer_handle,
double left,
double right,
double top,
double bottom,
int clipBehavior);
int clipBehavior,
fml::RefPtr<EngineLayer> oldLayer);
void pushClipRRect(Dart_Handle layer_handle,
const RRect& rrect,
int clipBehavior);
int clipBehavior,
fml::RefPtr<EngineLayer> oldLayer);
void pushClipPath(Dart_Handle layer_handle,
const CanvasPath* path,
int clipBehavior);
int clipBehavior,
fml::RefPtr<EngineLayer> oldLayer);
void pushOpacity(Dart_Handle layer_handle,
int alpha,
double dx = 0,
double dy = 0);
double dx,
double dy,
fml::RefPtr<EngineLayer> oldLayer);
void pushColorFilter(Dart_Handle layer_handle,
const ColorFilter* color_filter);
const ColorFilter* color_filter,
fml::RefPtr<EngineLayer> 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<EngineLayer> oldLayer);
void pushBackdropFilter(Dart_Handle layer_handle,
ImageFilter* filter,
fml::RefPtr<EngineLayer> oldLayer);
void pushShaderMask(Dart_Handle layer_handle,
Shader* shader,
double maskRectLeft,
double maskRectRight,
double maskRectTop,
double maskRectBottom,
int blendMode);
int blendMode,
fml::RefPtr<EngineLayer> oldLayer);
void pushPhysicalShape(Dart_Handle layer_handle,
const CanvasPath* path,
double elevation,
int color,
int shadowColor,
int clipBehavior);
int clipBehavior,
fml::RefPtr<EngineLayer> oldLayer);
void addRetained(fml::RefPtr<EngineLayer> retainedLayer);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册