forked from firka/flutter
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:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
304
engine/src/flutter/display_list/display_list_rtree_unittests.cc
Normal file
304
engine/src/flutter/display_list/display_list_rtree_unittests.cc
Normal 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
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user