Display list R-Tree culling (flutter/engine#38429)

* collect DL indices in RTree for clip culling

* fix bounds in unit test and minor opt in Dispatch

* normalize inline matrix objects and minor fixes to unit test

* remove over-eager DCHECK and improve R-Tree comments

* formatting

* include vector for Windows

* method rename and distribute child nodes more evenly

* add R-Tree specific unit tests and debug checks

* add comments about geometry to R-Tree unit tests and adjust spacing

* licenses

* licenses attempt 2

* fix potential overflow with uint32_t

* aggressively const DisplayList fields and methods

* add implementation comments per review feedback
This commit is contained in:
Jim Graham
2022-12-22 02:16:25 -08:00
committed by GitHub
parent cca1486c4d
commit c48bdca6da
15 changed files with 1294 additions and 374 deletions

View File

@@ -39,6 +39,7 @@
../../../flutter/display_list/display_list_matrix_clip_tracker_unittests.cc
../../../flutter/display_list/display_list_paint_unittests.cc
../../../flutter/display_list/display_list_path_effect_unittests.cc
../../../flutter/display_list/display_list_rtree_unittests.cc
../../../flutter/display_list/display_list_unittests.cc
../../../flutter/display_list/display_list_utils_unittests.cc
../../../flutter/display_list/display_list_vertices_unittests.cc

View File

@@ -109,6 +109,7 @@ if (enable_unittests) {
"display_list_matrix_clip_tracker_unittests.cc",
"display_list_paint_unittests.cc",
"display_list_path_effect_unittests.cc",
"display_list_rtree_unittests.cc",
"display_list_unittests.cc",
"display_list_utils_unittests.cc",
"display_list_vertices_unittests.cc",

View File

@@ -38,31 +38,144 @@ DisplayList::DisplayList(DisplayListStorage&& storage,
op_count_(op_count),
nested_byte_count_(nested_byte_count),
nested_op_count_(nested_op_count),
unique_id_(next_unique_id()),
bounds_(bounds),
can_apply_group_opacity_(can_apply_group_opacity),
rtree_(std::move(rtree)) {
static std::atomic<uint32_t> next_id{1};
do {
unique_id_ = next_id.fetch_add(+1, std::memory_order_relaxed);
} while (unique_id_ == 0);
}
rtree_(std::move(rtree)) {}
DisplayList::~DisplayList() {
uint8_t* ptr = storage_.get();
DisposeOps(ptr, ptr + byte_count_);
}
uint32_t DisplayList::next_unique_id() {
static std::atomic<uint32_t> next_id{1};
uint32_t id;
do {
id = next_id.fetch_add(+1, std::memory_order_relaxed);
} while (id == 0);
return id;
}
class Culler {
public:
virtual bool init(DispatchContext& context) = 0;
virtual void update(DispatchContext& context) = 0;
};
class NopCuller : public Culler {
public:
static NopCuller instance;
bool init(DispatchContext& context) override {
// Setting next_render_index to 0 means that
// all rendering ops will be at or after that
// index so they will execute and all restore
// indices will be after it as well so all
// clip and transform operations will execute.
context.next_render_index = 0;
return true;
}
void update(DispatchContext& context) override {}
};
NopCuller NopCuller::instance = NopCuller();
class VectorCuller : public Culler {
public:
VectorCuller(const DlRTree* rtree, const std::vector<int>& rect_indices)
: rtree_(rtree), cur_(rect_indices.begin()), end_(rect_indices.end()) {}
bool init(DispatchContext& context) override {
if (cur_ < end_) {
context.next_render_index = rtree_->id(*cur_++);
return true;
} else {
// Setting next_render_index to MAX_INT means that
// all rendering ops will be "before" that index and
// they will skip themselves and all clip and transform
// ops will see that the next render index is not
// before the next restore index (even if both are MAX_INT)
// and so they will also not execute.
// None of this really matters because returning false
// here should cause the Dispatch operation to abort,
// but this value is conceptually correct if that short
// circuit optimization isn't used.
context.next_render_index = std::numeric_limits<int>::max();
return false;
}
}
void update(DispatchContext& context) override {
if (++context.cur_index > context.next_render_index) {
while (cur_ < end_) {
context.next_render_index = rtree_->id(*cur_++);
if (context.next_render_index >= context.cur_index) {
// It should be rare that we have duplicate indices
// but if we do, then having a while loop is a cheap
// insurance for those cases.
// The main cause of duplicate indices is when a
// DrawDisplayListOp was added to this DisplayList and
// both are computing an R-Tree, in which case the
// builder method will forward all of the child
// DisplayList's rects to this R-Tree with the same
// op_index.
return;
}
}
context.next_render_index = std::numeric_limits<int>::max();
}
}
private:
const DlRTree* rtree_;
std::vector<int>::const_iterator cur_;
std::vector<int>::const_iterator end_;
};
void DisplayList::Dispatch(Dispatcher& ctx) const {
uint8_t* ptr = storage_.get();
Dispatch(ctx, ptr, ptr + byte_count_, NopCuller::instance);
}
void DisplayList::Dispatch(Dispatcher& ctx, const SkRect& cull_rect) const {
if (cull_rect.isEmpty()) {
return;
}
if (cull_rect.contains(bounds())) {
Dispatch(ctx);
return;
}
const DlRTree* rtree = this->rtree().get();
FML_DCHECK(rtree != nullptr);
if (rtree == nullptr) {
FML_LOG(ERROR) << "dispatched with culling rect on DL with no rtree";
Dispatch(ctx);
return;
}
uint8_t* ptr = storage_.get();
std::vector<int> rect_indices;
rtree->search(cull_rect, &rect_indices);
VectorCuller culler(rtree, rect_indices);
Dispatch(ctx, ptr, ptr + byte_count_, culler);
}
void DisplayList::Dispatch(Dispatcher& dispatcher,
uint8_t* ptr,
uint8_t* end) const {
uint8_t* end,
Culler& culler) const {
DispatchContext context = {
.dispatcher = dispatcher,
.cur_index = 0,
// next_render_index will be initialized by culler.init()
.next_restore_index = std::numeric_limits<int>::max(),
};
if (!culler.init(context)) {
return;
}
while (ptr < end) {
auto op = reinterpret_cast<const DLOp*>(ptr);
ptr += op->size;
FML_DCHECK(ptr <= end);
switch (op->type) {
#define DL_OP_DISPATCH(name) \
case DisplayListOpType::k##name: \
static_cast<const name##Op*>(op)->dispatch(dispatcher); \
#define DL_OP_DISPATCH(name) \
case DisplayListOpType::k##name: \
static_cast<const name##Op*>(op)->dispatch(context); \
break;
FOR_EACH_DISPLAY_LIST_OP(DL_OP_DISPATCH)
@@ -73,6 +186,7 @@ void DisplayList::Dispatch(Dispatcher& dispatcher,
FML_DCHECK(false);
return;
}
culler.update(context);
}
}
@@ -172,12 +286,20 @@ void DisplayList::RenderTo(DisplayListBuilder* builder,
if (!builder) {
return;
}
Dispatch(*builder);
if (has_rtree()) {
Dispatch(*builder, builder->getLocalClipBounds());
} else {
Dispatch(*builder);
}
}
void DisplayList::RenderTo(SkCanvas* canvas, SkScalar opacity) const {
DisplayListCanvasDispatcher dispatcher(canvas, opacity);
Dispatch(dispatcher);
if (has_rtree()) {
Dispatch(dispatcher, canvas->getLocalClipBounds());
} else {
Dispatch(dispatcher);
}
}
bool DisplayList::Equals(const DisplayList* other) const {

View File

@@ -235,6 +235,8 @@ class DisplayListStorage {
std::unique_ptr<uint8_t, FreeDeleter> ptr_;
};
class Culler;
// The base class that contains a sequence of rendering operations
// for dispatch to a Dispatcher. These objects must be instantiated
// through an instance of DisplayListBuilder::build().
@@ -244,10 +246,8 @@ class DisplayList : public SkRefCnt {
~DisplayList();
void Dispatch(Dispatcher& ctx) const {
uint8_t* ptr = storage_.get();
Dispatch(ctx, ptr, ptr + byte_count_);
}
void Dispatch(Dispatcher& ctx) const;
void Dispatch(Dispatcher& ctx, const SkRect& cull_rect) const;
void RenderTo(DisplayListBuilder* builder,
SkScalar opacity = SK_Scalar1) const;
@@ -268,9 +268,10 @@ class DisplayList : public SkRefCnt {
uint32_t unique_id() const { return unique_id_; }
const SkRect& bounds() { return bounds_; }
const SkRect& bounds() const { return bounds_; }
sk_sp<const DlRTree> rtree() { return rtree_; }
bool has_rtree() const { return rtree_ != nullptr; }
sk_sp<const DlRTree> rtree() const { return rtree_; }
bool Equals(const DisplayList* other) const;
bool Equals(const DisplayList& other) const { return Equals(&other); }
@@ -278,7 +279,7 @@ class DisplayList : public SkRefCnt {
return Equals(other.get());
}
bool can_apply_group_opacity() { return can_apply_group_opacity_; }
bool can_apply_group_opacity() const { return can_apply_group_opacity_; }
static void DisposeOps(uint8_t* ptr, uint8_t* end);
@@ -292,20 +293,25 @@ class DisplayList : public SkRefCnt {
bool can_apply_group_opacity,
sk_sp<const DlRTree> rtree);
DisplayListStorage storage_;
size_t byte_count_;
unsigned int op_count_;
static uint32_t next_unique_id();
size_t nested_byte_count_;
unsigned int nested_op_count_;
const DisplayListStorage storage_;
const size_t byte_count_;
const unsigned int op_count_;
uint32_t unique_id_;
SkRect bounds_;
const size_t nested_byte_count_;
const unsigned int nested_op_count_;
bool can_apply_group_opacity_;
sk_sp<const DlRTree> rtree_;
const uint32_t unique_id_;
const SkRect bounds_;
void Dispatch(Dispatcher& ctx, uint8_t* ptr, uint8_t* end) const;
const bool can_apply_group_opacity_;
const sk_sp<const DlRTree> rtree_;
void Dispatch(Dispatcher& ctx,
uint8_t* ptr,
uint8_t* end,
Culler& culler) const;
friend class DisplayListBuilder;
};

View File

@@ -27,7 +27,7 @@ static void CopyV(void* dst, const S* src, int n, Rest&&... rest) {
}
template <typename T, typename... Args>
void* DisplayListBuilder::Push(size_t pod, int op_inc, Args&&... args) {
void* DisplayListBuilder::Push(size_t pod, int render_op_inc, Args&&... args) {
size_t size = SkAlignPtr(sizeof(T) + pod);
FML_DCHECK(size < (1 << 24));
if (used_ + size > allocated_) {
@@ -45,7 +45,8 @@ void* DisplayListBuilder::Push(size_t pod, int op_inc, Args&&... args) {
new (op) T{std::forward<Args>(args)...};
op->type = T::kType;
op->size = size;
op_count_ += op_inc;
render_op_count_ += render_op_inc;
op_index_++;
return op + 1;
}
@@ -54,10 +55,10 @@ sk_sp<DisplayList> DisplayListBuilder::Build() {
restore();
}
size_t bytes = used_;
int count = op_count_;
int count = render_op_count_;
size_t nested_bytes = nested_bytes_;
int nested_count = nested_op_count_;
used_ = allocated_ = op_count_ = 0;
used_ = allocated_ = render_op_count_ = op_index_ = 0;
nested_bytes_ = nested_op_count_ = 0;
storage_.realloc(bytes);
bool compatible = layer_stack_.back().is_group_opacity_compatible();
@@ -433,7 +434,9 @@ void DisplayListBuilder::setAttributesFromPaint(
void DisplayListBuilder::checkForDeferredSave() {
if (current_layer_->has_deferred_save_op_) {
size_t save_offset_ = used_;
Push<SaveOp>(0, 1);
current_layer_->save_offset_ = save_offset_;
current_layer_->has_deferred_save_op_ = false;
}
}
@@ -448,7 +451,10 @@ void DisplayListBuilder::save() {
void DisplayListBuilder::restore() {
if (layer_stack_.size() > 1) {
SaveOpBase* op = reinterpret_cast<SaveOpBase*>(
storage_.get() + current_layer_->save_offset());
if (!current_layer_->has_deferred_save_op_) {
op->restore_index = op_index_;
Push<RestoreOp>(0, 1);
}
// Grab the current layer info before we push the restore
@@ -486,6 +492,9 @@ void DisplayListBuilder::restore() {
}
if (layer_info.has_layer()) {
// Layers are never deferred for now, we need to update the
// following code if we ever do saveLayer culling...
FML_DCHECK(!layer_info.has_deferred_save_op_);
if (layer_info.is_group_opacity_compatible()) {
// We are now going to go back and modify the matching saveLayer
// call to add the option indicating it can distribute an opacity
@@ -499,8 +508,6 @@ void DisplayListBuilder::restore() {
// in the DisplayList are only allowed *during* the build phase.
// Once built, the DisplayList records must remain read only to
// ensure consistency of rendering and |Equals()| behavior.
SaveLayerOp* op = reinterpret_cast<SaveLayerOp*>(
storage_.get() + layer_info.save_layer_offset());
op->options = op->options.with_can_distribute_opacity();
}
} else {
@@ -527,11 +534,11 @@ void DisplayListBuilder::saveLayer(const SkRect* bounds,
size_t save_layer_offset = used_;
if (backdrop) {
bounds //
? Push<SaveLayerBackdropBoundsOp>(0, 1, *bounds, options, backdrop)
? Push<SaveLayerBackdropBoundsOp>(0, 1, options, *bounds, backdrop)
: Push<SaveLayerBackdropOp>(0, 1, options, backdrop);
} else {
bounds //
? Push<SaveLayerBoundsOp>(0, 1, *bounds, options)
? Push<SaveLayerBoundsOp>(0, 1, options, *bounds)
: Push<SaveLayerOp>(0, 1, options);
}
CheckLayerOpacityCompatibility(options.renders_with_attributes());
@@ -541,6 +548,10 @@ void DisplayListBuilder::saveLayer(const SkRect* bounds,
// (eventual) corresponding restore is called, but rather than
// remember this information in the LayerInfo until the restore
// method is processed, we just mark the unbounded state up front.
// Another reason to accumulate the clip here rather than in
// restore is so that this savelayer will be tagged in the rtree
// with its full bounds and the right op_index so that it doesn't
// get culled during rendering.
if (!paint_nops_on_transparency()) {
// We will fill the clip of the outer layer when we restore
AccumulateUnbounded();
@@ -1161,6 +1172,11 @@ void DisplayListBuilder::drawAtlas(const sk_sp<DlImage>& atlas,
void DisplayListBuilder::drawPicture(const sk_sp<SkPicture> picture,
const SkMatrix* matrix,
bool render_with_attributes) {
matrix //
? Push<DrawSkPictureMatrixOp>(0, 1, picture, *matrix,
render_with_attributes)
: Push<DrawSkPictureOp>(0, 1, picture, render_with_attributes);
// TODO(flar) cull rect really cannot be trusted in general, but it will
// work for SkPictures generated from our own PictureRecorder or any
// picture captured with an SkRTreeFactory or accurate bounds estimate.
@@ -1172,11 +1188,6 @@ void DisplayListBuilder::drawPicture(const sk_sp<SkPicture> picture,
? kDrawPictureWithPaintFlags
: kDrawPictureFlags;
AccumulateOpBounds(bounds, flags);
matrix //
? Push<DrawSkPictureMatrixOp>(0, 1, picture, *matrix,
render_with_attributes)
: Push<DrawSkPictureOp>(0, 1, picture, render_with_attributes);
// The non-nested op count accumulated in the |Push| method will include
// this call to |drawPicture| for non-nested op count metrics.
// But, for nested op count metrics we want the |drawPicture| call itself
@@ -1189,6 +1200,8 @@ void DisplayListBuilder::drawPicture(const sk_sp<SkPicture> picture,
}
void DisplayListBuilder::drawDisplayList(
const sk_sp<DisplayList> display_list) {
Push<DrawDisplayListOp>(0, 1, display_list);
const SkRect bounds = display_list->bounds();
switch (accumulator()->type()) {
case BoundsAccumulatorType::kRect:
@@ -1197,7 +1210,7 @@ void DisplayListBuilder::drawDisplayList(
case BoundsAccumulatorType::kRTree:
auto rtree = display_list->rtree();
if (rtree) {
std::list<SkRect> rects = rtree->searchNonOverlappingDrawnRects(bounds);
std::list<SkRect> rects = rtree->searchAndConsolidateRects(bounds);
for (const SkRect& rect : rects) {
// TODO (https://github.com/flutter/flutter/issues/114919): Attributes
// are not necessarily `kDrawDisplayListFlags`.
@@ -1208,7 +1221,6 @@ void DisplayListBuilder::drawDisplayList(
}
break;
}
Push<DrawDisplayListOp>(0, 1, display_list);
// The non-nested op count accumulated in the |Push| method will include
// this call to |drawDisplayList| for non-nested op count metrics.
// But, for nested op count metrics we want the |drawDisplayList| call itself
@@ -1222,8 +1234,8 @@ void DisplayListBuilder::drawDisplayList(
void DisplayListBuilder::drawTextBlob(const sk_sp<SkTextBlob> blob,
SkScalar x,
SkScalar y) {
AccumulateOpBounds(blob->bounds().makeOffset(x, y), kDrawTextBlobFlags);
Push<DrawTextBlobOp>(0, 1, blob, x, y);
AccumulateOpBounds(blob->bounds().makeOffset(x, y), kDrawTextBlobFlags);
CheckLayerOpacityCompatibility();
}
void DisplayListBuilder::drawTextBlob(const sk_sp<SkTextBlob>& blob,
@@ -1238,13 +1250,13 @@ void DisplayListBuilder::drawShadow(const SkPath& path,
const SkScalar elevation,
bool transparent_occluder,
SkScalar dpr) {
SkRect shadow_bounds = DisplayListCanvasDispatcher::ComputeShadowBounds(
path, elevation, dpr, getTransform());
AccumulateOpBounds(shadow_bounds, kDrawShadowFlags);
transparent_occluder //
? Push<DrawShadowTransparentOccluderOp>(0, 1, path, color, elevation, dpr)
: Push<DrawShadowOp>(0, 1, path, color, elevation, dpr);
SkRect shadow_bounds = DisplayListCanvasDispatcher::ComputeShadowBounds(
path, elevation, dpr, getTransform());
AccumulateOpBounds(shadow_bounds, kDrawShadowFlags);
UpdateLayerOpacityCompatibility(false);
}
@@ -1318,7 +1330,7 @@ bool DisplayListBuilder::AdjustBoundsForPaint(SkRect& bounds,
}
void DisplayListBuilder::AccumulateUnbounded() {
accumulator()->accumulate(tracker_.device_cull_rect());
accumulator()->accumulate(tracker_.device_cull_rect(), op_index_ - 1);
}
void DisplayListBuilder::AccumulateOpBounds(SkRect& bounds,
@@ -1332,7 +1344,7 @@ void DisplayListBuilder::AccumulateOpBounds(SkRect& bounds,
void DisplayListBuilder::AccumulateBounds(SkRect& bounds) {
tracker_.mapRect(&bounds);
if (bounds.intersect(tracker_.device_cull_rect())) {
accumulator()->accumulate(bounds);
accumulator()->accumulate(bounds, op_index_ - 1);
}
}

View File

@@ -366,7 +366,8 @@ class DisplayListBuilder final : public virtual Dispatcher,
DisplayListStorage storage_;
size_t used_ = 0;
size_t allocated_ = 0;
int op_count_ = 0;
int render_op_count_ = 0;
int op_index_ = 0;
// bytes and ops from |drawPicture| and |drawDisplayList|
size_t nested_bytes_ = 0;
@@ -387,10 +388,10 @@ class DisplayListBuilder final : public virtual Dispatcher,
class LayerInfo {
public:
explicit LayerInfo(size_t save_layer_offset = 0,
explicit LayerInfo(size_t save_offset = 0,
bool has_layer = false,
std::shared_ptr<const DlImageFilter> filter = nullptr)
: save_layer_offset_(save_layer_offset),
: save_offset_(save_offset),
has_layer_(has_layer),
cannot_inherit_opacity_(false),
has_compatible_op_(false),
@@ -403,7 +404,7 @@ class DisplayListBuilder final : public virtual Dispatcher,
// the records inside the saveLayer that may impact how the saveLayer
// is handled (e.g., |cannot_inherit_opacity| == false).
// This offset is only valid if |has_layer| is true.
size_t save_layer_offset() const { return save_layer_offset_; }
size_t save_offset() const { return save_offset_; }
bool has_layer() const { return has_layer_; }
bool cannot_inherit_opacity() const { return cannot_inherit_opacity_; }
@@ -461,7 +462,7 @@ class DisplayListBuilder final : public virtual Dispatcher,
bool is_unbounded() const { return is_unbounded_; }
private:
size_t save_layer_offset_;
size_t save_offset_;
bool has_layer_;
bool cannot_inherit_opacity_;
bool has_compatible_op_;

File diff suppressed because it is too large Load Diff

View File

@@ -8,30 +8,158 @@
namespace flutter {
DlRTree::DlRTree() : bbh_{SkRTreeFactory{}()}, all_ops_count_(0) {}
DlRTree::DlRTree(const SkRect rects[],
int N,
const int ids[],
bool p(int),
int invalid_id)
: leaf_count_(0), invalid_id_(invalid_id) {
if (N <= 0) {
FML_DCHECK(N >= 0);
return;
}
FML_DCHECK(rects != nullptr);
void DlRTree::insert(const SkRect boundsArray[],
const SkBBoxHierarchy::Metadata metadata[],
int N) {
FML_DCHECK(0 == all_ops_count_);
bbh_->insert(boundsArray, metadata, N);
// Count the number of rectangles we actually want to track,
// which includes only non-empty rectangles whose optional
// ID is not filtered by the predicate.
int leaf_count = 0;
for (int i = 0; i < N; i++) {
if (metadata == nullptr || metadata[i].isDraw) {
draw_op_[i] = boundsArray[i];
if (!rects[i].isEmpty()) {
if (ids == nullptr || p(ids[i])) {
leaf_count++;
}
}
}
all_ops_count_ = N;
}
leaf_count_ = leaf_count;
void DlRTree::insert(const SkRect boundsArray[], int N) {
insert(boundsArray, nullptr, N);
// Count the total number of nodes (leaf and internal) up front
// so we can resize the vector just once.
uint32_t total_node_count = leaf_count;
uint32_t gen_count = leaf_count;
while (gen_count > 1) {
uint32_t family_count = (gen_count + kMaxChildren - 1u) / kMaxChildren;
total_node_count += family_count;
gen_count = family_count;
}
nodes_.resize(total_node_count);
// Now place only the tracked rectangles into the nodes array
// in the first leaf_count_ entries.
int leaf_index = 0;
int id = invalid_id;
for (int i = 0; i < N; i++) {
if (!rects[i].isEmpty()) {
if (ids == nullptr || p(id = ids[i])) {
Node& node = nodes_[leaf_index++];
node.bounds = rects[i];
node.id = id;
}
}
}
FML_DCHECK(leaf_index == leaf_count);
// --- Implementation note ---
// Many R-Tree algorithms attempt to consolidate nearby rectangles
// into branches of the tree in order to maximize the benefit of
// bounds testing against whole sub-trees. The Skia code from which
// this was based, though, indicated that empirical tests against a
// browser client showed little gains in rendering performance while
// costing 17% performance in bulk loading the rects into the R-Tree:
// https://github.com/google/skia/blob/12b6bd042f7cdffb9012c90c3b4885601fc7be95/src/core/SkRTree.cpp#L96
//
// Given that this class will most often be used to track rendering
// operations that were drawn in an app that performs a type of
// "page layout" with rendering proceeding in a linear fashion from
// top to bottom (and left to right or right to left), the rectangles
// are likely nearly sorted when they are delivered to this constructor
// so leaving them in their original order should show similar results
// to what Skia found in their empirical browser tests.
// ---
// Continually process the previous level (generation) of nodes,
// combining them into a new generation of parent groups each grouping
// at most |kMaxChildren| children and joining their bounds into its
// parent bounds.
// Each generation will end up reduced by a factor of up to kMaxChildren
// until there is just one node left, which is the root node of
// the R-Tree.
uint32_t gen_start = 0;
gen_count = leaf_count;
while (gen_count > 1) {
uint32_t gen_end = gen_start + gen_count;
uint32_t family_count = (gen_count + kMaxChildren - 1u) / kMaxChildren;
FML_DCHECK(gen_end + family_count <= total_node_count);
// D here is similar to the variable in a Bresenham line algorithm where
// we want to slowly move |family_count| steps along the minor axis as
// we move |gen_count| steps along the major axis.
//
// Each inner loop increments D by family_count.
// The inner loop executes a total of gen_count times.
// Every time D exceeds 0 we subtract gen_count and move to a new parent.
// All told we will increment D by family_count a total of gen_count times.
// All told we will decrement D by gen_count a total of family_count times.
// This leaves D back at its starting value.
//
// We could bias/balance where the extra children are placed by varying
// the initial count of D from 0 to (1 - family_count), but we aren't
// looking at this process aesthetically so we just use 0 as an initial
// value. Using 0 provides a "greedy" allocation of the extra children.
// Bresenham also uses double the size of the steps we use here also to
// have better rounding of when the minor axis steps occur, but again we
// don't care about the distribution of the extra children.
int D = 0;
uint32_t sibling_index = gen_start;
uint32_t parent_index = gen_end;
Node* parent = nullptr;
while (sibling_index < gen_end) {
if ((D += family_count) > 0) {
D -= gen_count;
FML_DCHECK(parent_index < gen_end + family_count);
parent = &nodes_[parent_index++];
parent->bounds.setEmpty();
parent->child.index = sibling_index;
parent->child.count = 0;
}
FML_DCHECK(parent != nullptr);
parent->bounds.join(nodes_[sibling_index++].bounds);
parent->child.count++;
}
FML_DCHECK(D == 0);
FML_DCHECK(sibling_index == gen_end);
FML_DCHECK(parent_index == gen_end + family_count);
gen_start = gen_end;
gen_count = family_count;
}
FML_DCHECK(gen_start + gen_count == total_node_count);
}
void DlRTree::search(const SkRect& query, std::vector<int>* results) const {
bbh_->search(query, results);
FML_DCHECK(results != nullptr);
if (query.isEmpty()) {
return;
}
if (nodes_.size() <= 0) {
FML_DCHECK(leaf_count_ == 0);
return;
}
const Node& root = nodes_.back();
if (root.bounds.intersects(query)) {
if (nodes_.size() == 1) {
FML_DCHECK(leaf_count_ == 1);
// The root node is the only node and it is a leaf node
results->push_back(0);
} else {
search(root, query, results);
}
}
}
std::list<SkRect> DlRTree::searchNonOverlappingDrawnRects(
std::list<SkRect> DlRTree::searchAndConsolidateRects(
const SkRect& query) const {
// Get the indexes for the operations that intersect with the query rect.
std::vector<int> intermediary_results;
@@ -39,12 +167,7 @@ std::list<SkRect> DlRTree::searchNonOverlappingDrawnRects(
std::list<SkRect> final_results;
for (int index : intermediary_results) {
auto draw_op = draw_op_.find(index);
// Ignore records that don't draw anything.
if (draw_op == draw_op_.end()) {
continue;
}
auto current_record_rect = draw_op->second;
auto current_record_rect = bounds(index);
auto replaced_existing_rect = false;
// // If the current record rect intersects with any of the rects in the
// // result list, then join them, and update the rect in final_results.
@@ -78,20 +201,22 @@ std::list<SkRect> DlRTree::searchNonOverlappingDrawnRects(
return final_results;
}
size_t DlRTree::bytesUsed() const {
return bbh_->bytesUsed();
}
DlRTreeFactory::DlRTreeFactory() {
r_tree_ = sk_make_sp<DlRTree>();
}
sk_sp<DlRTree> DlRTreeFactory::getInstance() {
return r_tree_;
}
sk_sp<SkBBoxHierarchy> DlRTreeFactory::operator()() const {
return r_tree_;
void DlRTree::search(const Node& parent,
const SkRect& query,
std::vector<int>* results) const {
// Caller protects against empty query
int start = parent.child.index;
int end = start + parent.child.count;
for (int i = start; i < end; i++) {
const Node& node = nodes_[i];
if (node.bounds.intersects(query)) {
if (i < leaf_count_) {
results->push_back(i);
} else {
search(node, query, results);
}
}
}
}
} // namespace flutter

View File

@@ -6,61 +6,125 @@
#define FLUTTER_DISPLAY_LIST_RTREE_H_
#include <list>
#include <map>
#include <vector>
#include "third_party/skia/include/core/SkBBHFactory.h"
#include "flutter/fml/logging.h"
#include "third_party/skia/include/core/SkRect.h"
#include "third_party/skia/include/core/SkRefCnt.h"
namespace flutter {
/**
* An R-Tree implementation that forwards calls to an SkRTree. This is just
* a copy of flow/rtree.h/cc until we can figure out where these utilities
* can live with appropriate linking visibility.
*
* This implementation provides a searchNonOverlappingDrawnRects method,
* which can be used to query the rects for the operations recorded in the tree.
*/
class DlRTree : public SkBBoxHierarchy {
/// An R-Tree that stores a list of bounding rectangles with optional
/// associated IDs.
///
/// The R-Tree can be searched in one of two ways:
/// - Query for a list of hits among the original rectangles
/// @see |search|
/// - Query for a set of non-overlapping rectangles that are joined
/// from the original rectangles that intersect a query rect
/// @see |searchAndConsolidateRects|
class DlRTree : public SkRefCnt {
private:
static constexpr int kMaxChildren = 11;
// Leaf nodes at start of vector have an ID,
// Internal nodes after that have child index and count.
struct Node {
SkRect bounds;
union {
struct {
uint32_t index;
uint32_t count;
} child;
int id;
};
};
public:
DlRTree();
/// Construct an R-Tree from the list of rectangles respecting the
/// order in which they appear in the list. An optional array of
/// IDs can be provided to tag each rectangle with information needed
/// by the caller as well as an optional predicate that can filter
/// out rectangles with IDs that should not be stored in the R-Tree.
///
/// If an array of IDs is not specified then all leaf nodes will be
/// represented by the |invalid_id| (which defaults to -1).
///
/// Invallid rects that are empty or contain a NaN value will not be
/// stored in the R-Tree. And, if a |predicate| function is provided,
/// that function will be called to query if the rectangle associated
/// with the ID should be included.
///
/// Duplicate rectangles and IDs are allowed and not processed in any
/// way except to eliminate invalid rectangles and IDs that are rejected
/// by the optional predicate function.
DlRTree(
const SkRect rects[],
int N,
const int ids[] = nullptr,
bool predicate(int id) = [](int) { return true; },
int invalid_id = -1);
void insert(const SkRect[],
const SkBBoxHierarchy::Metadata[],
int N) override;
void insert(const SkRect[], int N) override;
void search(const SkRect& query, std::vector<int>* results) const override;
size_t bytesUsed() const override;
/// Search the rectangles and return a vector of leaf node indices for
/// rectangles that intersect the query.
///
/// Note that the indices are internal indices of the stored data
/// and not the index of the rectangles or ids in the constructor.
/// The returned indices may not themselves be in numerical order,
/// but they will represent the rectangles and IDs in the order in
/// which they were passed into the constructor. The actual rectangle
/// and ID associated with each index can be retreived using the
/// |DlRTree::id| and |DlRTree::bouds| methods.
void search(const SkRect& query, std::vector<int>* results) const;
// Finds the rects in the tree that represent drawing operations and intersect
// with the query rect.
//
// When two rects intersect with each other, they are joined into a single
// rect which also intersects with the query rect. In other words, the bounds
// of each rect in the result list are mutually exclusive.
std::list<SkRect> searchNonOverlappingDrawnRects(const SkRect& query) const;
/// Return the ID for the indicated result of a query or
/// invalid_id if the index is not a valid leaf node index.
int id(int result_index) const {
return (result_index >= 0 && result_index < leaf_count_)
? nodes_[result_index].id
: invalid_id_;
}
// Insertion count (not overall node count, which may be greater).
int getCount() const { return all_ops_count_; }
/// Return the rectangle bounds for the indicated result of a query
/// or an empty rect if the index is not a valid leaf node index.
const SkRect& bounds(int result_index) const {
return (result_index >= 0 && result_index < leaf_count_)
? nodes_[result_index].bounds
: empty_;
}
/// Returns the bytes used by the object and all of its node data.
size_t bytes_used() const {
return sizeof(DlRTree) + sizeof(Node) * nodes_.size();
}
/// Returns the number of leaf nodes corresponding to non-empty
/// rectangles that were passed in the constructor and not filtered
/// out by the predicate.
int leaf_count() const { return leaf_count_; }
/// Return the total number of nodes used in the R-Tree, both leaf
/// and internal consolidation nodes.
int node_count() const { return nodes_.size(); }
/// Finds the rects in the tree that intersect with the query rect.
///
/// When two matching query results intersect with each other, they are
/// joined into a single rect which also intersects with the query rect.
/// In other words, the bounds of each rect in the result list are mutually
/// exclusive.
std::list<SkRect> searchAndConsolidateRects(const SkRect& query) const;
private:
// A map containing the draw operation rects keyed off the operation index
// in the insert call.
std::map<int, SkRect> draw_op_;
sk_sp<SkBBoxHierarchy> bbh_;
int all_ops_count_;
};
static constexpr SkRect empty_ = SkRect::MakeEmpty();
class DlRTreeFactory : public SkBBHFactory {
public:
DlRTreeFactory();
void search(const Node& parent,
const SkRect& query,
std::vector<int>* results) const;
// Gets the instance to the R-tree.
sk_sp<DlRTree> getInstance();
sk_sp<SkBBoxHierarchy> operator()() const override;
private:
sk_sp<DlRTree> r_tree_;
std::vector<Node> nodes_;
int leaf_count_;
int invalid_id_;
};
} // namespace flutter

View File

@@ -0,0 +1,304 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/display_list/display_list_rtree.h"
#include "gtest/gtest.h"
#include "third_party/skia/include/core/SkRect.h"
namespace flutter {
namespace testing {
#ifndef NDEBUG
TEST(DisplayListRTree, NullRectListNonZeroCount) {
EXPECT_DEATH_IF_SUPPORTED(new DlRTree(nullptr, 1), "rects != nullptr");
}
TEST(DisplayListRTree, NegativeCount) {
EXPECT_DEATH_IF_SUPPORTED(new DlRTree(nullptr, -1), "N >= 0");
}
TEST(DisplayListRTree, NullSearchResultVector) {
DlRTree tree(nullptr, 0);
EXPECT_DEATH_IF_SUPPORTED(tree.search(SkRect::MakeLTRB(0, 0, 1, 1), nullptr),
"results != nullptr");
}
#endif
TEST(DisplayListRTree, NullRectListZeroCount) {
DlRTree tree(nullptr, 0);
EXPECT_EQ(tree.leaf_count(), 0);
EXPECT_EQ(tree.node_count(), 0);
std::vector<int> results;
auto huge = SkRect::MakeLTRB(-1e6, -1e6, 1e6, 1e6);
tree.search(huge, &results);
EXPECT_EQ(results.size(), 0u);
auto list = tree.searchAndConsolidateRects(huge);
EXPECT_EQ(list.size(), 0u);
}
TEST(DisplayListRTree, ManySizes) {
// A diagonal of non-overlapping 10x10 rectangles spaced 20
// pixels apart.
// Rect 1 goes from 0 to 10
// Rect 2 goes from 20 to 30
// etc. in both dimensions
const int kMaxN = 250;
SkRect rects[kMaxN + 1];
int ids[kMaxN + 1];
for (int i = 0; i <= kMaxN; i++) {
rects[i].setXYWH(i * 20, i * 20, 10, 10);
ids[i] = i + 42;
}
std::vector<int> results;
for (int N = 0; N <= kMaxN; N++) {
DlRTree tree(rects, N, ids);
auto desc = "node count = " + std::to_string(N);
EXPECT_EQ(tree.leaf_count(), N) << desc;
EXPECT_GE(tree.node_count(), N) << desc;
EXPECT_EQ(tree.id(-1), -1) << desc;
EXPECT_EQ(tree.bounds(-1), SkRect::MakeEmpty()) << desc;
EXPECT_EQ(tree.id(N), -1) << desc;
EXPECT_EQ(tree.bounds(N), SkRect::MakeEmpty()) << desc;
results.clear();
tree.search(SkRect::MakeEmpty(), &results);
EXPECT_EQ(results.size(), 0u) << desc;
results.clear();
tree.search(SkRect::MakeLTRB(2, 2, 8, 8), &results);
if (N == 0) {
EXPECT_EQ(results.size(), 0u) << desc;
} else {
EXPECT_EQ(results.size(), 1u) << desc;
EXPECT_EQ(results[0], 0) << desc;
EXPECT_EQ(tree.id(results[0]), ids[0]) << desc;
EXPECT_EQ(tree.bounds(results[0]), rects[0]) << desc;
for (int i = 1; i < N; i++) {
results.clear();
auto query = SkRect::MakeXYWH(i * 20 + 2, i * 20 + 2, 6, 6);
tree.search(query, &results);
EXPECT_EQ(results.size(), 1u) << desc;
EXPECT_EQ(results[0], i) << desc;
EXPECT_EQ(tree.id(results[0]), ids[i]) << desc;
EXPECT_EQ(tree.bounds(results[0]), rects[i]) << desc;
auto list = tree.searchAndConsolidateRects(query);
EXPECT_EQ(list.size(), 1u);
EXPECT_EQ(list.front(), rects[i]);
}
}
}
}
TEST(DisplayListRTree, HugeSize) {
// A diagonal of non-overlapping 10x10 rectangles spaced 20
// pixels apart.
// Rect 1 goes from 0 to 10
// Rect 2 goes from 20 to 30
// etc. in both dimensions
const int N = 10000;
SkRect rects[N];
int ids[N];
for (int i = 0; i < N; i++) {
rects[i].setXYWH(i * 20, i * 20, 10, 10);
ids[i] = i + 42;
}
DlRTree tree(rects, N, ids);
EXPECT_EQ(tree.leaf_count(), N);
EXPECT_GE(tree.node_count(), N);
EXPECT_EQ(tree.id(-1), -1);
EXPECT_EQ(tree.bounds(-1), SkRect::MakeEmpty());
EXPECT_EQ(tree.id(N), -1);
EXPECT_EQ(tree.bounds(N), SkRect::MakeEmpty());
std::vector<int> results;
tree.search(SkRect::MakeEmpty(), &results);
EXPECT_EQ(results.size(), 0u);
for (int i = 0; i < N; i++) {
results.clear();
tree.search(SkRect::MakeXYWH(i * 20 + 2, i * 20 + 2, 6, 6), &results);
EXPECT_EQ(results.size(), 1u);
EXPECT_EQ(results[0], i);
EXPECT_EQ(tree.id(results[0]), ids[i]);
EXPECT_EQ(tree.bounds(results[0]), rects[i]);
}
}
TEST(DisplayListRTree, Grid) {
// Non-overlapping 10 x 10 rectangles starting at 5, 5 with
// 10 pixels between them.
// Rect 1 goes from 5 to 15
// Rect 2 goes from 25 to 35
// etc. in both dimensions
const int ROWS = 10;
const int COLS = 10;
const int N = ROWS * COLS;
SkRect rects[N];
int ids[N];
for (int r = 0; r < ROWS; r++) {
int y = r * 20 + 5;
for (int c = 0; c < COLS; c++) {
int x = c * 20 + 5;
int i = r * COLS + c;
rects[i] = SkRect::MakeXYWH(x, y, 10, 10);
ids[i] = i + 42;
}
}
DlRTree tree(rects, N, ids);
EXPECT_EQ(tree.leaf_count(), N);
EXPECT_GE(tree.node_count(), N);
EXPECT_EQ(tree.id(-1), -1);
EXPECT_EQ(tree.bounds(-1), SkRect::MakeEmpty());
EXPECT_EQ(tree.id(N), -1);
EXPECT_EQ(tree.bounds(N), SkRect::MakeEmpty());
std::vector<int> results;
tree.search(SkRect::MakeEmpty(), &results);
EXPECT_EQ(results.size(), 0u);
// Testing eqch rect for a single hit
for (int r = 0; r < ROWS; r++) {
int y = r * 20 + 5;
for (int c = 0; c < COLS; c++) {
int x = c * 20 + 5;
int i = r * COLS + c;
auto desc =
"row " + std::to_string(r + 1) + ", col " + std::to_string(c + 1);
results.clear();
auto query = SkRect::MakeXYWH(x + 2, y + 2, 6, 6);
tree.search(query, &results);
EXPECT_EQ(results.size(), 1u) << desc;
EXPECT_EQ(results[0], i) << desc;
EXPECT_EQ(tree.id(results[0]), ids[i]) << desc;
EXPECT_EQ(tree.bounds(results[0]), rects[i]) << desc;
auto list = tree.searchAndConsolidateRects(query);
EXPECT_EQ(list.size(), 1u);
EXPECT_EQ(list.front(), rects[i]);
}
}
// Testing inside each gap for no hits
for (int r = 1; r < ROWS; r++) {
int y = r * 20 + 5;
for (int c = 1; c < COLS; c++) {
int x = c * 20 + 5;
auto desc =
"row " + std::to_string(r + 1) + ", col " + std::to_string(c + 1);
results.clear();
auto query = SkRect::MakeXYWH(x - 8, y - 8, 6, 6);
tree.search(query, &results);
EXPECT_EQ(results.size(), 0u) << desc;
auto list = tree.searchAndConsolidateRects(query);
EXPECT_EQ(list.size(), 0u) << desc;
}
}
// Spanning each gap for a quad of hits
for (int r = 1; r < ROWS; r++) {
int y = r * 20 + 5;
for (int c = 1; c < COLS; c++) {
int x = c * 20 + 5;
// We will hit this rect and the ones above/left of us
int i = r * COLS + c;
auto desc =
"row " + std::to_string(r + 1) + ", col " + std::to_string(c + 1);
results.clear();
auto query = SkRect::MakeXYWH(x - 11, y - 11, 12, 12);
tree.search(query, &results);
EXPECT_EQ(results.size(), 4u) << desc;
// First rect is above and to the left
EXPECT_EQ(results[0], i - COLS - 1) << desc;
EXPECT_EQ(tree.id(results[0]), ids[i - COLS - 1]) << desc;
EXPECT_EQ(tree.bounds(results[0]), rects[i - COLS - 1]) << desc;
// Second rect is above
EXPECT_EQ(results[1], i - COLS) << desc;
EXPECT_EQ(tree.id(results[1]), ids[i - COLS]) << desc;
EXPECT_EQ(tree.bounds(results[1]), rects[i - COLS]) << desc;
// Third rect is left
EXPECT_EQ(results[2], i - 1) << desc;
EXPECT_EQ(tree.id(results[2]), ids[i - 1]) << desc;
EXPECT_EQ(tree.bounds(results[2]), rects[i - 1]) << desc;
// Fourth rect is us
EXPECT_EQ(results[3], i) << desc;
EXPECT_EQ(tree.id(results[3]), ids[i]) << desc;
EXPECT_EQ(tree.bounds(results[3]), rects[i]) << desc;
auto list = tree.searchAndConsolidateRects(query);
EXPECT_EQ(list.size(), 4u);
list.remove(rects[i - COLS - 1]);
list.remove(rects[i - COLS]);
list.remove(rects[i - 1]);
list.remove(rects[i]);
EXPECT_EQ(list.size(), 0u);
}
}
}
TEST(DisplayListRTree, OverlappingRects) {
// Rectangles are centered at coordinates 15, 35, and 55 and are 15 wide
// This gives them 10 pixels of overlap with the rectangles on either
// side of them and the 10 pixels around their center coordinate are
// exclusive to themselves.
// So, horizontally and vertically, they cover the following ranges:
// First row/col: 0 to 30
// Second row/col: 20 to 50
// Third row/col: 40 to 70
// Coords 0 to 20 are only the first row/col
// Coords 20 to 30 are both first and second row/col
// Coords 30 to 40 are only the second row/col
// Coords 40 to 50 are both second and third row/col
// Coords 50 to 70 are only the third row/col
//
// In either dimension:
// 0------------------20--------30--------40--------50------------------70
// | rect1 |
// | 1 & 2 |
// | rect2 |
// | 2 & 3 |
// | rect3 |
SkRect rects[9];
for (int r = 0; r < 3; r++) {
int y = 15 + 20 * r;
for (int c = 0; c < 3; c++) {
int x = 15 + 20 * c;
rects[r * 3 + c].setLTRB(x - 15, y - 15, x + 15, y + 15);
}
}
DlRTree tree(rects, 9);
// Tiny rects only intersecting a single source rect
for (int r = 0; r < 3; r++) {
int y = 15 + 20 * r;
for (int c = 0; c < 3; c++) {
int x = 15 + 20 * c;
auto query = SkRect::MakeLTRB(x - 1, y - 1, x + 1, y + 1);
auto list = tree.searchAndConsolidateRects(query);
EXPECT_EQ(list.size(), 1u);
EXPECT_EQ(list.front(), rects[r * 3 + c]);
}
}
// Wide rects intersecting 3 source rects horizontally
for (int r = 0; r < 3; r++) {
int c = 1;
int y = 15 + 20 * r;
int x = 15 + 20 * c;
auto query = SkRect::MakeLTRB(x - 6, y - 1, x + 6, y + 1);
auto list = tree.searchAndConsolidateRects(query);
EXPECT_EQ(list.size(), 1u);
EXPECT_EQ(list.front(), SkRect::MakeLTRB(x - 35, y - 15, x + 35, y + 15));
}
// Tall rects intersecting 3 source rects vertically
for (int c = 0; c < 3; c++) {
int r = 1;
int x = 15 + 20 * c;
int y = 15 + 20 * r;
auto query = SkRect::MakeLTRB(x - 1, y - 6, x + 1, y + 6);
auto list = tree.searchAndConsolidateRects(query);
EXPECT_EQ(list.size(), 1u);
EXPECT_EQ(list.front(), SkRect::MakeLTRB(x - 15, y - 35, x + 15, y + 35));
}
// Finally intersecting all 9 rects
auto query = SkRect::MakeLTRB(35 - 6, 35 - 6, 35 + 6, 35 + 6);
auto list = tree.searchAndConsolidateRects(query);
EXPECT_EQ(list.size(), 1u);
EXPECT_EQ(list.front(), SkRect::MakeLTRB(0, 0, 70, 70));
}
} // namespace testing
} // namespace flutter

View File

@@ -304,7 +304,7 @@ std::vector<DisplayListInvocationGroup> CreateAllSaveRestoreOps() {
return {
{"Save(Layer)+Restore",
{
{5, 104, 5, 104,
{5, 112, 5, 112,
[](DisplayListBuilder& b) {
b.saveLayer(nullptr, SaveLayerOptions::kNoAttributes,
&kTestCFImageFilter1);
@@ -320,7 +320,7 @@ std::vector<DisplayListInvocationGroup> CreateAllSaveRestoreOps() {
// attributes to be distributed to the children. To prevent those
// cases we include at least one clip operation and 2 overlapping
// rendering primitives between each save/restore pair.
{5, 88, 5, 88,
{5, 96, 5, 96,
[](DisplayListBuilder& b) {
b.save();
b.clipRect({0, 0, 25, 25}, SkClipOp::kIntersect, true);
@@ -328,7 +328,7 @@ std::vector<DisplayListInvocationGroup> CreateAllSaveRestoreOps() {
b.drawRect({10, 10, 20, 20});
b.restore();
}},
{5, 88, 5, 88,
{5, 96, 5, 96,
[](DisplayListBuilder& b) {
b.saveLayer(nullptr, false);
b.clipRect({0, 0, 25, 25}, SkClipOp::kIntersect, true);
@@ -336,7 +336,7 @@ std::vector<DisplayListInvocationGroup> CreateAllSaveRestoreOps() {
b.drawRect({10, 10, 20, 20});
b.restore();
}},
{5, 88, 5, 88,
{5, 96, 5, 96,
[](DisplayListBuilder& b) {
b.saveLayer(nullptr, true);
b.clipRect({0, 0, 25, 25}, SkClipOp::kIntersect, true);
@@ -344,7 +344,7 @@ std::vector<DisplayListInvocationGroup> CreateAllSaveRestoreOps() {
b.drawRect({10, 10, 20, 20});
b.restore();
}},
{5, 104, 5, 104,
{5, 112, 5, 112,
[](DisplayListBuilder& b) {
b.saveLayer(&kTestBounds, false);
b.clipRect({0, 0, 25, 25}, SkClipOp::kIntersect, true);
@@ -352,7 +352,7 @@ std::vector<DisplayListInvocationGroup> CreateAllSaveRestoreOps() {
b.drawRect({10, 10, 20, 20});
b.restore();
}},
{5, 104, 5, 104,
{5, 112, 5, 112,
[](DisplayListBuilder& b) {
b.saveLayer(&kTestBounds, true);
b.clipRect({0, 0, 25, 25}, SkClipOp::kIntersect, true);
@@ -369,7 +369,7 @@ std::vector<DisplayListInvocationGroup> CreateAllSaveRestoreOps() {
// b.drawRect({10, 10, 20, 20});
// b.restore();
// }},
{5, 104, 5, 104,
{5, 112, 5, 112,
[](DisplayListBuilder& b) {
b.saveLayer(nullptr, SaveLayerOptions::kWithAttributes,
&kTestCFImageFilter1);
@@ -378,7 +378,7 @@ std::vector<DisplayListInvocationGroup> CreateAllSaveRestoreOps() {
b.drawRect({10, 10, 20, 20});
b.restore();
}},
{5, 120, 5, 120,
{5, 128, 5, 128,
[](DisplayListBuilder& b) {
b.saveLayer(&kTestBounds, SaveLayerOptions::kNoAttributes,
&kTestCFImageFilter1);
@@ -387,7 +387,7 @@ std::vector<DisplayListInvocationGroup> CreateAllSaveRestoreOps() {
b.drawRect({10, 10, 20, 20});
b.restore();
}},
{5, 120, 5, 120,
{5, 128, 5, 128,
[](DisplayListBuilder& b) {
b.saveLayer(&kTestBounds, SaveLayerOptions::kWithAttributes,
&kTestCFImageFilter1);

View File

@@ -273,12 +273,12 @@ TEST(DisplayList, SingleOpDisplayListsCompareToEachOther) {
TEST(DisplayList, SingleOpDisplayListsAreEqualWhetherOrNotToPrepareRtree) {
for (auto& group : allGroups) {
for (size_t i = 0; i < group.variants.size(); i++) {
DisplayListBuilder buider1(/*prepare_rtree=*/false);
DisplayListBuilder buider2(/*prepare_rtree=*/true);
group.variants[i].invoker(buider1);
group.variants[i].invoker(buider2);
sk_sp<DisplayList> dl1 = buider1.Build();
sk_sp<DisplayList> dl2 = buider2.Build();
DisplayListBuilder builder1(/*prepare_rtree=*/false);
DisplayListBuilder builder2(/*prepare_rtree=*/true);
group.variants[i].invoker(builder1);
group.variants[i].invoker(builder2);
sk_sp<DisplayList> dl1 = builder1.Build();
sk_sp<DisplayList> dl2 = builder2.Build();
auto desc = group.op_name + "(variant " + std::to_string(i + 1) + " )";
ASSERT_EQ(dl1->op_count(false), dl2->op_count(false)) << desc;
@@ -286,8 +286,8 @@ TEST(DisplayList, SingleOpDisplayListsAreEqualWhetherOrNotToPrepareRtree) {
ASSERT_EQ(dl1->op_count(true), dl2->op_count(true)) << desc;
ASSERT_EQ(dl1->bytes(true), dl2->bytes(true)) << desc;
ASSERT_EQ(dl1->bounds(), dl2->bounds()) << desc;
ASSERT_TRUE(dl1->Equals(*dl2)) << desc;
ASSERT_TRUE(dl2->Equals(*dl1)) << desc;
ASSERT_TRUE(DisplayListsEQ_Verbose(dl1, dl2)) << desc;
ASSERT_TRUE(DisplayListsEQ_Verbose(dl2, dl2)) << desc;
ASSERT_EQ(dl1->rtree().get(), nullptr) << desc;
ASSERT_NE(dl2->rtree().get(), nullptr) << desc;
}
@@ -1240,7 +1240,7 @@ TEST(DisplayList, FlutterSvgIssue661BoundsWereEmpty) {
// This is the more practical result. The bounds are "almost" 0,0,100x100
EXPECT_EQ(display_list->bounds().roundOut(), SkIRect::MakeWH(100, 100));
EXPECT_EQ(display_list->op_count(), 19u);
EXPECT_EQ(display_list->bytes(), sizeof(DisplayList) + 304u);
EXPECT_EQ(display_list->bytes(), sizeof(DisplayList) + 352u);
}
TEST(DisplayList, TranslateAffectsCurrentTransform) {
@@ -1795,7 +1795,7 @@ static void test_rtree(const sk_sp<const DlRTree>& rtree,
rtree->search(query, &indices);
EXPECT_EQ(indices, expected_indices);
EXPECT_EQ(indices.size(), expected_indices.size());
std::list<SkRect> rects = rtree->searchNonOverlappingDrawnRects(query);
std::list<SkRect> rects = rtree->searchAndConsolidateRects(query);
// ASSERT_EQ(rects.size(), expected_indices.size());
auto iterator = rects.cbegin();
for (int i : expected_indices) {
@@ -2496,5 +2496,117 @@ TEST(DisplayList, RTreeOfClippedSaveLayerFilterScene) {
test_rtree(rtree, {19, 19, 51, 51}, rects, {0, 1});
}
TEST(DisplayList, RTreeRenderCulling) {
DisplayListBuilder main_builder(true);
main_builder.drawRect({0, 0, 10, 10});
main_builder.drawRect({20, 0, 30, 10});
main_builder.drawRect({0, 20, 10, 30});
main_builder.drawRect({20, 20, 30, 30});
auto main = main_builder.Build();
{ // No rects
SkRect cull_rect = {11, 11, 19, 19};
DisplayListBuilder expected_builder;
auto expected = expected_builder.Build();
DisplayListBuilder culling_builder(cull_rect);
main->RenderTo(&culling_builder);
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected));
DisplayListCanvasRecorder culling_recorder(cull_rect);
main->RenderTo(&culling_recorder);
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_recorder.Build(), expected));
}
{ // Rect 1
SkRect cull_rect = {9, 9, 19, 19};
DisplayListBuilder expected_builder;
expected_builder.drawRect({0, 0, 10, 10});
auto expected = expected_builder.Build();
DisplayListBuilder culling_builder(cull_rect);
main->RenderTo(&culling_builder);
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected));
DisplayListCanvasRecorder culling_recorder(cull_rect);
main->RenderTo(&culling_recorder);
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_recorder.Build(), expected));
}
{ // Rect 2
SkRect cull_rect = {11, 9, 21, 19};
DisplayListBuilder expected_builder;
expected_builder.drawRect({20, 0, 30, 10});
auto expected = expected_builder.Build();
DisplayListBuilder culling_builder(cull_rect);
main->RenderTo(&culling_builder);
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected));
DisplayListCanvasRecorder culling_recorder(cull_rect);
main->RenderTo(&culling_recorder);
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_recorder.Build(), expected));
}
{ // Rect 3
SkRect cull_rect = {9, 11, 19, 21};
DisplayListBuilder expected_builder;
expected_builder.drawRect({0, 20, 10, 30});
auto expected = expected_builder.Build();
DisplayListBuilder culling_builder(cull_rect);
main->RenderTo(&culling_builder);
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected));
DisplayListCanvasRecorder culling_recorder(cull_rect);
main->RenderTo(&culling_recorder);
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_recorder.Build(), expected));
}
{ // Rect 4
SkRect cull_rect = {11, 11, 21, 21};
DisplayListBuilder expected_builder;
expected_builder.drawRect({20, 20, 30, 30});
auto expected = expected_builder.Build();
DisplayListBuilder culling_builder(cull_rect);
main->RenderTo(&culling_builder);
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected));
DisplayListCanvasRecorder culling_recorder(cull_rect);
main->RenderTo(&culling_recorder);
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_recorder.Build(), expected));
}
{ // All 4 rects
SkRect cull_rect = {9, 9, 21, 21};
DisplayListBuilder culling_builder(cull_rect);
main->RenderTo(&culling_builder);
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), main));
DisplayListCanvasRecorder culling_recorder(cull_rect);
main->RenderTo(&culling_recorder);
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_recorder.Build(), main));
}
}
} // namespace testing
} // namespace flutter

View File

@@ -107,7 +107,7 @@ sk_sp<SkColorFilter> SkPaintDispatchHelper::makeColorFilter() const {
return invert_filter;
}
void RectBoundsAccumulator::accumulate(const SkRect& r) {
void RectBoundsAccumulator::accumulate(const SkRect& r, int index) {
if (r.fLeft < r.fRight && r.fTop < r.fBottom) {
rect_.accumulate(r.fLeft, r.fTop);
rect_.accumulate(r.fRight, r.fBottom);
@@ -143,7 +143,7 @@ void RectBoundsAccumulator::pop_and_accumulate(SkRect& layer_bounds,
saved_rects_.pop_back();
if (clip == nullptr || layer_bounds.intersect(*clip)) {
accumulate(layer_bounds);
accumulate(layer_bounds, -1);
}
}
@@ -174,9 +174,10 @@ SkRect RectBoundsAccumulator::AccumulationRect::bounds() const {
: SkRect::MakeEmpty();
}
void RTreeBoundsAccumulator::accumulate(const SkRect& r) {
void RTreeBoundsAccumulator::accumulate(const SkRect& r, int index) {
if (r.fLeft < r.fRight && r.fTop < r.fBottom) {
rects_.push_back(r);
rect_indices_.push_back(index);
}
}
void RTreeBoundsAccumulator::save() {
@@ -206,10 +207,13 @@ bool RTreeBoundsAccumulator::restore(
success = false;
}
if (clip == nullptr || original.intersect(*clip)) {
rects_[previous_size++] = original;
rect_indices_[previous_size] = rect_indices_[i];
rects_[previous_size] = original;
previous_size++;
}
}
rects_.resize(previous_size);
rect_indices_.resize(previous_size);
return success;
}
@@ -217,17 +221,15 @@ SkRect RTreeBoundsAccumulator::bounds() const {
FML_DCHECK(saved_offsets_.empty());
RectBoundsAccumulator accumulator;
for (auto& rect : rects_) {
accumulator.accumulate(rect);
accumulator.accumulate(rect, 0);
}
return accumulator.bounds();
}
sk_sp<DlRTree> RTreeBoundsAccumulator::rtree() const {
FML_DCHECK(saved_offsets_.empty());
DlRTreeFactory factory;
sk_sp<DlRTree> rtree = factory.getInstance();
rtree->insert(rects_.data(), rects_.size());
return rtree;
return sk_make_sp<DlRTree>(rects_.data(), rects_.size(), rect_indices_.data(),
[](int id) { return id >= 0; });
}
} // namespace flutter

View File

@@ -251,7 +251,7 @@ class BoundsAccumulator {
virtual ~BoundsAccumulator() = default;
virtual void accumulate(const SkRect& r) = 0;
virtual void accumulate(const SkRect& r, int index = 0) = 0;
/// Save aside the rects/bounds currently being accumulated and start
/// accumulating a new set of rects/bounds. When restore is called,
@@ -296,7 +296,7 @@ class RectBoundsAccumulator final : public virtual BoundsAccumulator {
public:
void accumulate(SkScalar x, SkScalar y) { rect_.accumulate(x, y); }
void accumulate(const SkPoint& p) { rect_.accumulate(p.fX, p.fY); }
void accumulate(const SkRect& r) override;
void accumulate(const SkRect& r, int index) override;
bool is_empty() const { return rect_.is_empty(); }
bool is_not_empty() const { return rect_.is_not_empty(); }
@@ -344,7 +344,7 @@ class RectBoundsAccumulator final : public virtual BoundsAccumulator {
class RTreeBoundsAccumulator final : public virtual BoundsAccumulator {
public:
void accumulate(const SkRect& r) override;
void accumulate(const SkRect& r, int index) override;
void save() override;
void restore() override;
@@ -362,6 +362,7 @@ class RTreeBoundsAccumulator final : public virtual BoundsAccumulator {
private:
std::vector<SkRect> rects_;
std::vector<int> rect_indices_;
std::vector<size_t> saved_offsets_;
};

View File

@@ -60,7 +60,7 @@ void DisplayListEmbedderViewSlice::end_recording() {
std::list<SkRect> DisplayListEmbedderViewSlice::searchNonOverlappingDrawnRects(
const SkRect& query) const {
return display_list_->rtree()->searchNonOverlappingDrawnRects(query);
return display_list_->rtree()->searchAndConsolidateRects(query);
}
void DisplayListEmbedderViewSlice::render_into(SkCanvas* canvas) {