diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index ec6f5f08de71d..7147bcc6eb607 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -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 diff --git a/display_list/BUILD.gn b/display_list/BUILD.gn index 5a49ecf8a8b97..2c9863a6cf907 100644 --- a/display_list/BUILD.gn +++ b/display_list/BUILD.gn @@ -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", diff --git a/display_list/display_list.cc b/display_list/display_list.cc index 9268ed3fdbcd7..27511e0a5d619 100644 --- a/display_list/display_list.cc +++ b/display_list/display_list.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)) { + 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 next_id{1}; + uint32_t id; do { - unique_id_ = next_id.fetch_add(+1, std::memory_order_relaxed); - } while (unique_id_ == 0); + id = next_id.fetch_add(+1, std::memory_order_relaxed); + } while (id == 0); + return id; } -DisplayList::~DisplayList() { +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& 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::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::max(); + } + } + + private: + const DlRTree* rtree_; + std::vector::const_iterator cur_; + std::vector::const_iterator end_; +}; + +void DisplayList::Dispatch(Dispatcher& ctx) const { uint8_t* ptr = storage_.get(); - DisposeOps(ptr, ptr + byte_count_); + 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 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::max(), + }; + if (!culler.init(context)) { + return; + } while (ptr < end) { auto op = reinterpret_cast(ptr); ptr += op->size; FML_DCHECK(ptr <= end); switch (op->type) { -#define DL_OP_DISPATCH(name) \ - case DisplayListOpType::k##name: \ - static_cast(op)->dispatch(dispatcher); \ +#define DL_OP_DISPATCH(name) \ + case DisplayListOpType::k##name: \ + static_cast(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 { diff --git a/display_list/display_list.h b/display_list/display_list.h index a20f93f56a025..0fa5636f372cd 100644 --- a/display_list/display_list.h +++ b/display_list/display_list.h @@ -235,6 +235,8 @@ class DisplayListStorage { std::unique_ptr 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 rtree() { return rtree_; } + bool has_rtree() const { return rtree_ != nullptr; } + sk_sp 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 rtree); - DisplayListStorage storage_; - size_t byte_count_; - unsigned int op_count_; + static uint32_t next_unique_id(); + + const DisplayListStorage storage_; + const size_t byte_count_; + const unsigned int op_count_; - size_t nested_byte_count_; - unsigned int nested_op_count_; + const size_t nested_byte_count_; + const unsigned int nested_op_count_; - uint32_t unique_id_; - SkRect bounds_; + const uint32_t unique_id_; + const SkRect bounds_; - bool can_apply_group_opacity_; - sk_sp rtree_; + const bool can_apply_group_opacity_; + const sk_sp rtree_; - void Dispatch(Dispatcher& ctx, uint8_t* ptr, uint8_t* end) const; + void Dispatch(Dispatcher& ctx, + uint8_t* ptr, + uint8_t* end, + Culler& culler) const; friend class DisplayListBuilder; }; diff --git a/display_list/display_list_builder.cc b/display_list/display_list_builder.cc index 6cb5d6c52fcf2..b5b1c0e2b9e0e 100644 --- a/display_list/display_list_builder.cc +++ b/display_list/display_list_builder.cc @@ -27,7 +27,7 @@ static void CopyV(void* dst, const S* src, int n, Rest&&... rest) { } template -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)...}; 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 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(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( + storage_.get() + current_layer_->save_offset()); if (!current_layer_->has_deferred_save_op_) { + op->restore_index = op_index_; Push(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( - 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(0, 1, *bounds, options, backdrop) + ? Push(0, 1, options, *bounds, backdrop) : Push(0, 1, options, backdrop); } else { bounds // - ? Push(0, 1, *bounds, options) + ? Push(0, 1, options, *bounds) : Push(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& atlas, void DisplayListBuilder::drawPicture(const sk_sp picture, const SkMatrix* matrix, bool render_with_attributes) { + matrix // + ? Push(0, 1, picture, *matrix, + render_with_attributes) + : Push(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 picture, ? kDrawPictureWithPaintFlags : kDrawPictureFlags; AccumulateOpBounds(bounds, flags); - - matrix // - ? Push(0, 1, picture, *matrix, - render_with_attributes) - : Push(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 picture, } void DisplayListBuilder::drawDisplayList( const sk_sp display_list) { + Push(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 rects = rtree->searchNonOverlappingDrawnRects(bounds); + std::list 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(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 blob, SkScalar x, SkScalar y) { - AccumulateOpBounds(blob->bounds().makeOffset(x, y), kDrawTextBlobFlags); Push(0, 1, blob, x, y); + AccumulateOpBounds(blob->bounds().makeOffset(x, y), kDrawTextBlobFlags); CheckLayerOpacityCompatibility(); } void DisplayListBuilder::drawTextBlob(const sk_sp& 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(0, 1, path, color, elevation, dpr) : Push(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); } } diff --git a/display_list/display_list_builder.h b/display_list/display_list_builder.h index b1caf3714cc9c..cc5888fb45951 100644 --- a/display_list/display_list_builder.h +++ b/display_list/display_list_builder.h @@ -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 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_; diff --git a/display_list/display_list_ops.h b/display_list/display_list_ops.h index 061f29365df45..40775cdae7066 100644 --- a/display_list/display_list_ops.h +++ b/display_list/display_list_ops.h @@ -15,6 +15,47 @@ namespace flutter { +// Structure holding the information necessary to dispatch and +// potentially cull the DLOps during playback. +// +// Generally drawing ops will execute as long as |cur_index| +// is at or after |next_render_index|, so setting the latter +// to 0 will render all primitives and setting it to MAX_INT +// will skip all remaining rendering primitives. +// +// Save and saveLayer ops will execute as long as the next +// rendering index is before their closing restore index. +// They will also store their own restore index into the +// |next_restore_index| field for use by clip and transform ops. +// +// Clip and transform ops will only execute if the next +// render index is before the next restore index. Otherwise +// their modified state will not be used before it gets +// restored. +// +// Attribute ops always execute as they are too numerous and +// cheap to deal with a complicated "lifetime" tracking to +// determine if they will be used. +struct DispatchContext { + Dispatcher& dispatcher; + + int cur_index; + int next_render_index; + + int next_restore_index; + + struct SaveInfo { + SaveInfo(int previous_restore_index, bool save_was_needed) + : previous_restore_index(previous_restore_index), + save_was_needed(save_was_needed) {} + + int previous_restore_index; + bool save_was_needed; + }; + + std::vector save_infos; +}; + // Most Ops can be bulk compared using memcmp because they contain // only numeric values or constructs that are constructed from numeric // values. @@ -69,8 +110,8 @@ struct DLOp { \ const bool value; \ \ - void dispatch(Dispatcher& dispatcher) const { \ - dispatcher.set##name(value); \ + void dispatch(DispatchContext& ctx) const { \ + ctx.dispatcher.set##name(value); \ } \ }; DEFINE_SET_BOOL_OP(AntiAlias) @@ -87,8 +128,8 @@ DEFINE_SET_BOOL_OP(InvertColors) \ const DlStroke##name value; \ \ - void dispatch(Dispatcher& dispatcher) const { \ - dispatcher.setStroke##name(value); \ + void dispatch(DispatchContext& ctx) const { \ + ctx.dispatcher.setStroke##name(value); \ } \ }; DEFINE_SET_ENUM_OP(Cap) @@ -103,7 +144,7 @@ struct SetStyleOp final : DLOp { const DlDrawStyle style; - void dispatch(Dispatcher& dispatcher) const { dispatcher.setStyle(style); } + void dispatch(DispatchContext& ctx) const { ctx.dispatcher.setStyle(style); } }; // 4 byte header + 4 byte payload packs into minimum 8 bytes struct SetStrokeWidthOp final : DLOp { @@ -113,8 +154,8 @@ struct SetStrokeWidthOp final : DLOp { const float width; - void dispatch(Dispatcher& dispatcher) const { - dispatcher.setStrokeWidth(width); + void dispatch(DispatchContext& ctx) const { + ctx.dispatcher.setStrokeWidth(width); } }; // 4 byte header + 4 byte payload packs into minimum 8 bytes @@ -125,8 +166,8 @@ struct SetStrokeMiterOp final : DLOp { const float limit; - void dispatch(Dispatcher& dispatcher) const { - dispatcher.setStrokeMiter(limit); + void dispatch(DispatchContext& ctx) const { + ctx.dispatcher.setStrokeMiter(limit); } }; @@ -138,7 +179,7 @@ struct SetColorOp final : DLOp { const DlColor color; - void dispatch(Dispatcher& dispatcher) const { dispatcher.setColor(color); } + void dispatch(DispatchContext& ctx) const { ctx.dispatcher.setColor(color); } }; // 4 byte header + 4 byte payload packs into minimum 8 bytes struct SetBlendModeOp final : DLOp { @@ -148,7 +189,9 @@ struct SetBlendModeOp final : DLOp { const DlBlendMode mode; - void dispatch(Dispatcher& dispatcher) const { dispatcher.setBlendMode(mode); } + void dispatch(DispatchContext& ctx) const { + ctx.dispatcher.setBlendMode(mode); + } }; // Clear: 4 byte header + unused 4 byte payload uses 8 bytes @@ -162,8 +205,8 @@ struct SetBlendModeOp final : DLOp { \ Clear##name##Op() {} \ \ - void dispatch(Dispatcher& dispatcher) const { \ - dispatcher.set##name(nullptr); \ + void dispatch(DispatchContext& ctx) const { \ + ctx.dispatcher.set##name(nullptr); \ } \ }; \ struct Set##name##Op final : DLOp { \ @@ -173,8 +216,8 @@ struct SetBlendModeOp final : DLOp { \ sk_sp field; \ \ - void dispatch(Dispatcher& dispatcher) const { \ - dispatcher.set##name(field); \ + void dispatch(DispatchContext& ctx) const { \ + ctx.dispatcher.set##name(field); \ } \ }; DEFINE_SET_CLEAR_SKREF_OP(Blender, blender) @@ -195,8 +238,8 @@ DEFINE_SET_CLEAR_SKREF_OP(Blender, blender) \ Clear##name##Op() {} \ \ - void dispatch(Dispatcher& dispatcher) const { \ - dispatcher.set##name(nullptr); \ + void dispatch(DispatchContext& ctx) const { \ + ctx.dispatcher.set##name(nullptr); \ } \ }; \ struct SetPod##name##Op final : DLOp { \ @@ -204,9 +247,9 @@ DEFINE_SET_CLEAR_SKREF_OP(Blender, blender) \ SetPod##name##Op() {} \ \ - void dispatch(Dispatcher& dispatcher) const { \ + void dispatch(DispatchContext& ctx) const { \ const Dl##name* filter = reinterpret_cast(this + 1); \ - dispatcher.set##name(filter); \ + ctx.dispatcher.set##name(filter); \ } \ }; \ struct SetSk##name##Op final : DLOp { \ @@ -216,9 +259,9 @@ DEFINE_SET_CLEAR_SKREF_OP(Blender, blender) \ sk_sp field; \ \ - void dispatch(Dispatcher& dispatcher) const { \ + void dispatch(DispatchContext& ctx) const { \ DlUnknown##name dl_filter(field); \ - dispatcher.set##name(&dl_filter); \ + ctx.dispatcher.set##name(&dl_filter); \ } \ }; DEFINE_SET_CLEAR_DLATTR_OP(ColorFilter, ColorFilter, filter) @@ -242,8 +285,8 @@ struct SetImageColorSourceOp : DLOp { const DlImageColorSource source; - void dispatch(Dispatcher& dispatcher) const { - dispatcher.setColorSource(&source); + void dispatch(DispatchContext& ctx) const { + ctx.dispatcher.setColorSource(&source); } }; @@ -259,8 +302,8 @@ struct SetRuntimeEffectColorSourceOp : DLOp { const DlRuntimeEffectColorSource source; - void dispatch(Dispatcher& dispatcher) const { - dispatcher.setColorSource(&source); + void dispatch(DispatchContext& ctx) const { + ctx.dispatcher.setColorSource(&source); } DisplayListCompare equals(const SetRuntimeEffectColorSourceOp* other) const { @@ -278,8 +321,8 @@ struct SetSharedImageFilterOp : DLOp { const std::shared_ptr filter; - void dispatch(Dispatcher& dispatcher) const { - dispatcher.setImageFilter(filter.get()); + void dispatch(DispatchContext& ctx) const { + ctx.dispatcher.setImageFilter(filter.get()); } DisplayListCompare equals(const SetSharedImageFilterOp* other) const { @@ -288,53 +331,80 @@ struct SetSharedImageFilterOp : DLOp { } }; -// 4 byte header + no payload uses minimum 8 bytes (4 bytes unused) -struct SaveOp final : DLOp { +// The base object for all save() and saveLayer() ops +// 4 byte header + 8 byte payload packs neatly into 16 bytes (4 bytes unused) +struct SaveOpBase : DLOp { + SaveOpBase() : options(), restore_index(0) {} + + SaveOpBase(const SaveLayerOptions options) + : options(options), restore_index(0) {} + + // options parameter is only used by saveLayer operations, but since + // it packs neatly into the empty space created by laying out the 64-bit + // offsets, it can be stored for free and defaulted to 0 for save operations. + SaveLayerOptions options; + int restore_index; + + inline bool save_needed(DispatchContext& ctx) const { + bool needed = ctx.next_render_index <= restore_index; + ctx.save_infos.emplace_back(ctx.next_restore_index, needed); + ctx.next_restore_index = restore_index; + return needed; + } +}; +// 24 byte SaveOpBase with no additional data (options is unsed here) +struct SaveOp final : SaveOpBase { static const auto kType = DisplayListOpType::kSave; - SaveOp() {} + SaveOp() : SaveOpBase() {} - void dispatch(Dispatcher& dispatcher) const { dispatcher.save(); } + void dispatch(DispatchContext& ctx) const { + if (save_needed(ctx)) { + ctx.dispatcher.save(); + } + } }; -// 4 byte header + 4 byte payload packs into minimum 8 bytes -struct SaveLayerOp final : DLOp { +// 16 byte SaveOpBase with no additional data +struct SaveLayerOp final : SaveOpBase { static const auto kType = DisplayListOpType::kSaveLayer; - explicit SaveLayerOp(const SaveLayerOptions options) : options(options) {} - - SaveLayerOptions options; + explicit SaveLayerOp(const SaveLayerOptions options) : SaveOpBase(options) {} - void dispatch(Dispatcher& dispatcher) const { - dispatcher.saveLayer(nullptr, options); + void dispatch(DispatchContext& ctx) const { + if (save_needed(ctx)) { + ctx.dispatcher.saveLayer(nullptr, options); + } } }; -// 4 byte header + 20 byte payload packs evenly into 24 bytes -struct SaveLayerBoundsOp final : DLOp { +// 24 byte SaveOpBase + 16 byte payload packs evenly into 40 bytes +struct SaveLayerBoundsOp final : SaveOpBase { static const auto kType = DisplayListOpType::kSaveLayerBounds; - SaveLayerBoundsOp(SkRect rect, const SaveLayerOptions options) - : options(options), rect(rect) {} + SaveLayerBoundsOp(const SaveLayerOptions options, const SkRect& rect) + : SaveOpBase(options), rect(rect) {} - SaveLayerOptions options; const SkRect rect; - void dispatch(Dispatcher& dispatcher) const { - dispatcher.saveLayer(&rect, options); + void dispatch(DispatchContext& ctx) const { + if (save_needed(ctx)) { + ctx.dispatcher.saveLayer(&rect, options); + } } }; -// 4 byte header + 20 byte payload packs into minimum 24 bytes -struct SaveLayerBackdropOp final : DLOp { +// 24 byte SaveOpBase + 16 byte payload packs into minimum 40 bytes +struct SaveLayerBackdropOp final : SaveOpBase { static const auto kType = DisplayListOpType::kSaveLayerBackdrop; explicit SaveLayerBackdropOp(const SaveLayerOptions options, const DlImageFilter* backdrop) - : options(options), backdrop(backdrop->shared()) {} + : SaveOpBase(options), backdrop(backdrop->shared()) {} - SaveLayerOptions options; const std::shared_ptr backdrop; - void dispatch(Dispatcher& dispatcher) const { - dispatcher.saveLayer(nullptr, options, backdrop.get()); + void dispatch(DispatchContext& ctx) const { + if (save_needed(ctx)) { + ctx.dispatcher.saveLayer(nullptr, options, backdrop.get()); + } } DisplayListCompare equals(const SaveLayerBackdropOp* other) const { @@ -343,21 +413,22 @@ struct SaveLayerBackdropOp final : DLOp { : DisplayListCompare::kNotEqual; } }; -// 4 byte header + 36 byte payload packs evenly into 36 bytes -struct SaveLayerBackdropBoundsOp final : DLOp { +// 24 byte SaveOpBase + 32 byte payload packs into minimum 56 bytes +struct SaveLayerBackdropBoundsOp final : SaveOpBase { static const auto kType = DisplayListOpType::kSaveLayerBackdropBounds; - SaveLayerBackdropBoundsOp(SkRect rect, - const SaveLayerOptions options, + SaveLayerBackdropBoundsOp(const SaveLayerOptions options, + const SkRect& rect, const DlImageFilter* backdrop) - : options(options), rect(rect), backdrop(backdrop->shared()) {} + : SaveOpBase(options), rect(rect), backdrop(backdrop->shared()) {} - SaveLayerOptions options; const SkRect rect; const std::shared_ptr backdrop; - void dispatch(Dispatcher& dispatcher) const { - dispatcher.saveLayer(&rect, options, backdrop.get()); + void dispatch(DispatchContext& ctx) const { + if (save_needed(ctx)) { + ctx.dispatcher.saveLayer(&rect, options, backdrop.get()); + } } DisplayListCompare equals(const SaveLayerBackdropBoundsOp* other) const { @@ -373,12 +444,24 @@ struct RestoreOp final : DLOp { RestoreOp() {} - void dispatch(Dispatcher& dispatcher) const { dispatcher.restore(); } + void dispatch(DispatchContext& ctx) const { + DispatchContext::SaveInfo& info = ctx.save_infos.back(); + if (info.save_was_needed) { + ctx.dispatcher.restore(); + } + ctx.next_restore_index = info.previous_restore_index; + ctx.save_infos.pop_back(); + } }; +struct TransformClipOpBase : DLOp { + inline bool op_needed(const DispatchContext& context) const { + return context.next_render_index <= context.next_restore_index; + } +}; // 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes // (4 bytes unused) -struct TranslateOp final : DLOp { +struct TranslateOp final : TransformClipOpBase { static const auto kType = DisplayListOpType::kTranslate; TranslateOp(SkScalar tx, SkScalar ty) : tx(tx), ty(ty) {} @@ -386,11 +469,15 @@ struct TranslateOp final : DLOp { const SkScalar tx; const SkScalar ty; - void dispatch(Dispatcher& dispatcher) const { dispatcher.translate(tx, ty); } + void dispatch(DispatchContext& ctx) const { + if (op_needed(ctx)) { + ctx.dispatcher.translate(tx, ty); + } + } }; // 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes // (4 bytes unused) -struct ScaleOp final : DLOp { +struct ScaleOp final : TransformClipOpBase { static const auto kType = DisplayListOpType::kScale; ScaleOp(SkScalar sx, SkScalar sy) : sx(sx), sy(sy) {} @@ -398,21 +485,29 @@ struct ScaleOp final : DLOp { const SkScalar sx; const SkScalar sy; - void dispatch(Dispatcher& dispatcher) const { dispatcher.scale(sx, sy); } + void dispatch(DispatchContext& ctx) const { + if (op_needed(ctx)) { + ctx.dispatcher.scale(sx, sy); + } + } }; // 4 byte header + 4 byte payload packs into minimum 8 bytes -struct RotateOp final : DLOp { +struct RotateOp final : TransformClipOpBase { static const auto kType = DisplayListOpType::kRotate; explicit RotateOp(SkScalar degrees) : degrees(degrees) {} const SkScalar degrees; - void dispatch(Dispatcher& dispatcher) const { dispatcher.rotate(degrees); } + void dispatch(DispatchContext& ctx) const { + if (op_needed(ctx)) { + ctx.dispatcher.rotate(degrees); + } + } }; // 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes // (4 bytes unused) -struct SkewOp final : DLOp { +struct SkewOp final : TransformClipOpBase { static const auto kType = DisplayListOpType::kSkew; SkewOp(SkScalar sx, SkScalar sy) : sx(sx), sy(sy) {} @@ -420,11 +515,15 @@ struct SkewOp final : DLOp { const SkScalar sx; const SkScalar sy; - void dispatch(Dispatcher& dispatcher) const { dispatcher.skew(sx, sy); } + void dispatch(DispatchContext& ctx) const { + if (op_needed(ctx)) { + ctx.dispatcher.skew(sx, sy); + } + } }; // 4 byte header + 24 byte payload uses 28 bytes but is rounded up to 32 bytes // (4 bytes unused) -struct Transform2DAffineOp final : DLOp { +struct Transform2DAffineOp final : TransformClipOpBase { static const auto kType = DisplayListOpType::kTransform2DAffine; // clang-format off @@ -436,14 +535,16 @@ struct Transform2DAffineOp final : DLOp { const SkScalar mxx, mxy, mxt; const SkScalar myx, myy, myt; - void dispatch(Dispatcher& dispatcher) const { - dispatcher.transform2DAffine(mxx, mxy, mxt, // - myx, myy, myt); + void dispatch(DispatchContext& ctx) const { + if (op_needed(ctx)) { + ctx.dispatcher.transform2DAffine(mxx, mxy, mxt, // + myx, myy, myt); + } } }; // 4 byte header + 64 byte payload uses 68 bytes which is rounded up to 72 bytes // (4 bytes unused) -struct TransformFullPerspectiveOp final : DLOp { +struct TransformFullPerspectiveOp final : TransformClipOpBase { static const auto kType = DisplayListOpType::kTransformFullPerspective; // clang-format off @@ -463,21 +564,27 @@ struct TransformFullPerspectiveOp final : DLOp { const SkScalar mzx, mzy, mzz, mzt; const SkScalar mwx, mwy, mwz, mwt; - void dispatch(Dispatcher& dispatcher) const { - dispatcher.transformFullPerspective(mxx, mxy, mxz, mxt, // - myx, myy, myz, myt, // - mzx, mzy, mzz, mzt, // - mwx, mwy, mwz, mwt); + void dispatch(DispatchContext& ctx) const { + if (op_needed(ctx)) { + ctx.dispatcher.transformFullPerspective(mxx, mxy, mxz, mxt, // + myx, myy, myz, myt, // + mzx, mzy, mzz, mzt, // + mwx, mwy, mwz, mwt); + } } }; // 4 byte header with no payload. -struct TransformResetOp final : DLOp { +struct TransformResetOp final : TransformClipOpBase { static const auto kType = DisplayListOpType::kTransformReset; TransformResetOp() = default; - void dispatch(Dispatcher& dispatcher) const { dispatcher.transformReset(); } + void dispatch(DispatchContext& ctx) const { + if (op_needed(ctx)) { + ctx.dispatcher.transformReset(); + } + } }; // 4 byte header + 4 byte common payload packs into minimum 8 bytes @@ -491,7 +598,7 @@ struct TransformResetOp final : DLOp { // packing into more bytes than needed (even when they are declared as // packed bit fields!) #define DEFINE_CLIP_SHAPE_OP(shapetype, clipop) \ - struct Clip##clipop##shapetype##Op final : DLOp { \ + struct Clip##clipop##shapetype##Op final : TransformClipOpBase { \ static const auto kType = DisplayListOpType::kClip##clipop##shapetype; \ \ Clip##clipop##shapetype##Op(Sk##shapetype shape, bool is_aa) \ @@ -500,8 +607,10 @@ struct TransformResetOp final : DLOp { const bool is_aa; \ const Sk##shapetype shape; \ \ - void dispatch(Dispatcher& dispatcher) const { \ - dispatcher.clip##shapetype(shape, SkClipOp::k##clipop, is_aa); \ + void dispatch(DispatchContext& ctx) const { \ + if (op_needed(ctx)) { \ + ctx.dispatcher.clip##shapetype(shape, SkClipOp::k##clipop, is_aa); \ + } \ } \ }; DEFINE_CLIP_SHAPE_OP(Rect, Intersect) @@ -511,7 +620,7 @@ DEFINE_CLIP_SHAPE_OP(RRect, Difference) #undef DEFINE_CLIP_SHAPE_OP #define DEFINE_CLIP_PATH_OP(clipop) \ - struct Clip##clipop##PathOp final : DLOp { \ + struct Clip##clipop##PathOp final : TransformClipOpBase { \ static const auto kType = DisplayListOpType::kClip##clipop##Path; \ \ Clip##clipop##PathOp(SkPath path, bool is_aa) \ @@ -520,8 +629,10 @@ DEFINE_CLIP_SHAPE_OP(RRect, Difference) const bool is_aa; \ const SkPath path; \ \ - void dispatch(Dispatcher& dispatcher) const { \ - dispatcher.clipPath(path, SkClipOp::k##clipop, is_aa); \ + void dispatch(DispatchContext& ctx) const { \ + if (op_needed(ctx)) { \ + ctx.dispatcher.clipPath(path, SkClipOp::k##clipop, is_aa); \ + } \ } \ \ DisplayListCompare equals(const Clip##clipop##PathOp* other) const { \ @@ -534,17 +645,27 @@ DEFINE_CLIP_PATH_OP(Intersect) DEFINE_CLIP_PATH_OP(Difference) #undef DEFINE_CLIP_PATH_OP +struct DrawOpBase : DLOp { + inline bool op_needed(const DispatchContext& ctx) const { + return ctx.cur_index >= ctx.next_render_index; + } +}; + // 4 byte header + no payload uses minimum 8 bytes (4 bytes unused) -struct DrawPaintOp final : DLOp { +struct DrawPaintOp final : DrawOpBase { static const auto kType = DisplayListOpType::kDrawPaint; DrawPaintOp() {} - void dispatch(Dispatcher& dispatcher) const { dispatcher.drawPaint(); } + void dispatch(DispatchContext& ctx) const { + if (op_needed(ctx)) { + ctx.dispatcher.drawPaint(); + } + } }; // 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes // (4 bytes unused) -struct DrawColorOp final : DLOp { +struct DrawColorOp final : DrawOpBase { static const auto kType = DisplayListOpType::kDrawColor; DrawColorOp(DlColor color, DlBlendMode mode) : color(color), mode(mode) {} @@ -552,8 +673,10 @@ struct DrawColorOp final : DLOp { const DlColor color; const DlBlendMode mode; - void dispatch(Dispatcher& dispatcher) const { - dispatcher.drawColor(color, mode); + void dispatch(DispatchContext& ctx) const { + if (op_needed(ctx)) { + ctx.dispatcher.drawColor(color, mode); + } } }; @@ -563,15 +686,17 @@ struct DrawColorOp final : DLOp { // SkOval is same as SkRect // SkRRect is 52 more bytes, which packs efficiently into 56 bytes total #define DEFINE_DRAW_1ARG_OP(op_name, arg_type, arg_name) \ - struct Draw##op_name##Op final : DLOp { \ + struct Draw##op_name##Op final : DrawOpBase { \ static const auto kType = DisplayListOpType::kDraw##op_name; \ \ explicit Draw##op_name##Op(arg_type arg_name) : arg_name(arg_name) {} \ \ const arg_type arg_name; \ \ - void dispatch(Dispatcher& dispatcher) const { \ - dispatcher.draw##op_name(arg_name); \ + void dispatch(DispatchContext& ctx) const { \ + if (op_needed(ctx)) { \ + ctx.dispatcher.draw##op_name(arg_name); \ + } \ } \ }; DEFINE_DRAW_1ARG_OP(Rect, SkRect, rect) @@ -581,14 +706,18 @@ DEFINE_DRAW_1ARG_OP(RRect, SkRRect, rrect) // 4 byte header + 16 byte payload uses 20 bytes but is rounded up to 24 bytes // (4 bytes unused) -struct DrawPathOp final : DLOp { +struct DrawPathOp final : DrawOpBase { static const auto kType = DisplayListOpType::kDrawPath; explicit DrawPathOp(SkPath path) : path(path) {} const SkPath path; - void dispatch(Dispatcher& dispatcher) const { dispatcher.drawPath(path); } + void dispatch(DispatchContext& ctx) const { + if (op_needed(ctx)) { + ctx.dispatcher.drawPath(path); + } + } DisplayListCompare equals(const DrawPathOp* other) const { return path == other->path ? DisplayListCompare::kEqual @@ -603,7 +732,7 @@ struct DrawPathOp final : DLOp { // 2 x SkRRect is 104 more bytes, using 108 and rounding up to 112 bytes total // (4 bytes unused) #define DEFINE_DRAW_2ARG_OP(op_name, type1, name1, type2, name2) \ - struct Draw##op_name##Op final : DLOp { \ + struct Draw##op_name##Op final : DrawOpBase { \ static const auto kType = DisplayListOpType::kDraw##op_name; \ \ Draw##op_name##Op(type1 name1, type2 name2) \ @@ -612,8 +741,10 @@ struct DrawPathOp final : DLOp { const type1 name1; \ const type2 name2; \ \ - void dispatch(Dispatcher& dispatcher) const { \ - dispatcher.draw##op_name(name1, name2); \ + void dispatch(DispatchContext& ctx) const { \ + if (op_needed(ctx)) { \ + ctx.dispatcher.draw##op_name(name1, name2); \ + } \ } \ }; DEFINE_DRAW_2ARG_OP(Line, SkPoint, p0, SkPoint, p1) @@ -622,7 +753,7 @@ DEFINE_DRAW_2ARG_OP(DRRect, SkRRect, outer, SkRRect, inner) #undef DEFINE_DRAW_2ARG_OP // 4 byte header + 28 byte payload packs efficiently into 32 bytes -struct DrawArcOp final : DLOp { +struct DrawArcOp final : DrawOpBase { static const auto kType = DisplayListOpType::kDrawArc; DrawArcOp(SkRect bounds, SkScalar start, SkScalar sweep, bool center) @@ -633,8 +764,10 @@ struct DrawArcOp final : DLOp { const SkScalar sweep; const bool center; - void dispatch(Dispatcher& dispatcher) const { - dispatcher.drawArc(bounds, start, sweep, center); + void dispatch(DispatchContext& ctx) const { + if (op_needed(ctx)) { + ctx.dispatcher.drawArc(bounds, start, sweep, center); + } } }; @@ -644,18 +777,20 @@ struct DrawArcOp final : DLOp { // so this op will always pack efficiently // The point type is packed into 3 different OpTypes to avoid expanding // the fixed payload beyond the 8 bytes -#define DEFINE_DRAW_POINTS_OP(name, mode) \ - struct Draw##name##Op final : DLOp { \ - static const auto kType = DisplayListOpType::kDraw##name; \ - \ - explicit Draw##name##Op(uint32_t count) : count(count) {} \ - \ - const uint32_t count; \ - \ - void dispatch(Dispatcher& dispatcher) const { \ - const SkPoint* pts = reinterpret_cast(this + 1); \ - dispatcher.drawPoints(SkCanvas::PointMode::mode, count, pts); \ - } \ +#define DEFINE_DRAW_POINTS_OP(name, mode) \ + struct Draw##name##Op final : DrawOpBase { \ + static const auto kType = DisplayListOpType::kDraw##name; \ + \ + explicit Draw##name##Op(uint32_t count) : count(count) {} \ + \ + const uint32_t count; \ + \ + void dispatch(DispatchContext& ctx) const { \ + if (op_needed(ctx)) { \ + const SkPoint* pts = reinterpret_cast(this + 1); \ + ctx.dispatcher.drawPoints(SkCanvas::PointMode::mode, count, pts); \ + } \ + } \ }; DEFINE_DRAW_POINTS_OP(Points, kPoints_PointMode); DEFINE_DRAW_POINTS_OP(Lines, kLines_PointMode); @@ -669,21 +804,24 @@ DEFINE_DRAW_POINTS_OP(Polygon, kPolygon_PointMode); // Note that the DlVertices object ends with an array of 16-bit // indices so the alignment can be up to 6 bytes off leading to // up to 6 bytes of overhead -struct DrawVerticesOp final : DLOp { +struct DrawVerticesOp final : DrawOpBase { static const auto kType = DisplayListOpType::kDrawVertices; DrawVerticesOp(DlBlendMode mode) : mode(mode) {} const DlBlendMode mode; - void dispatch(Dispatcher& dispatcher) const { - const DlVertices* vertices = reinterpret_cast(this + 1); - dispatcher.drawVertices(vertices, mode); + void dispatch(DispatchContext& ctx) const { + if (op_needed(ctx)) { + const DlVertices* vertices = + reinterpret_cast(this + 1); + ctx.dispatcher.drawVertices(vertices, mode); + } } }; // 4 byte header + 12 byte payload packs efficiently into 16 bytes -struct DrawSkVerticesOp final : DLOp { +struct DrawSkVerticesOp final : DrawOpBase { static const auto kType = DisplayListOpType::kDrawSkVertices; DrawSkVerticesOp(sk_sp vertices, SkBlendMode mode) @@ -692,29 +830,33 @@ struct DrawSkVerticesOp final : DLOp { const SkBlendMode mode; const sk_sp vertices; - void dispatch(Dispatcher& dispatcher) const { - dispatcher.drawSkVertices(vertices, mode); + void dispatch(DispatchContext& ctx) const { + if (op_needed(ctx)) { + ctx.dispatcher.drawSkVertices(vertices, mode); + } } }; // 4 byte header + 40 byte payload uses 44 bytes but is rounded up to 48 bytes // (4 bytes unused) -#define DEFINE_DRAW_IMAGE_OP(name, with_attributes) \ - struct name##Op final : DLOp { \ - static const auto kType = DisplayListOpType::k##name; \ - \ - name##Op(const sk_sp image, \ - const SkPoint& point, \ - DlImageSampling sampling) \ - : point(point), sampling(sampling), image(std::move(image)) {} \ - \ - const SkPoint point; \ - const DlImageSampling sampling; \ - const sk_sp image; \ - \ - void dispatch(Dispatcher& dispatcher) const { \ - dispatcher.drawImage(image, point, sampling, with_attributes); \ - } \ +#define DEFINE_DRAW_IMAGE_OP(name, with_attributes) \ + struct name##Op final : DrawOpBase { \ + static const auto kType = DisplayListOpType::k##name; \ + \ + name##Op(const sk_sp image, \ + const SkPoint& point, \ + DlImageSampling sampling) \ + : point(point), sampling(sampling), image(std::move(image)) {} \ + \ + const SkPoint point; \ + const DlImageSampling sampling; \ + const sk_sp image; \ + \ + void dispatch(DispatchContext& ctx) const { \ + if (op_needed(ctx)) { \ + ctx.dispatcher.drawImage(image, point, sampling, with_attributes); \ + } \ + } \ }; DEFINE_DRAW_IMAGE_OP(DrawImage, false) DEFINE_DRAW_IMAGE_OP(DrawImageWithAttr, true) @@ -722,7 +864,7 @@ DEFINE_DRAW_IMAGE_OP(DrawImageWithAttr, true) // 4 byte header + 72 byte payload uses 76 bytes but is rounded up to 80 bytes // (4 bytes unused) -struct DrawImageRectOp final : DLOp { +struct DrawImageRectOp final : DrawOpBase { static const auto kType = DisplayListOpType::kDrawImageRect; DrawImageRectOp(const sk_sp image, @@ -745,15 +887,17 @@ struct DrawImageRectOp final : DLOp { const SkCanvas::SrcRectConstraint constraint; const sk_sp image; - void dispatch(Dispatcher& dispatcher) const { - dispatcher.drawImageRect(image, src, dst, sampling, render_with_attributes, - constraint); + void dispatch(DispatchContext& ctx) const { + if (op_needed(ctx)) { + ctx.dispatcher.drawImageRect(image, src, dst, sampling, + render_with_attributes, constraint); + } } }; // 4 byte header + 44 byte payload packs efficiently into 48 bytes #define DEFINE_DRAW_IMAGE_NINE_OP(name, render_with_attributes) \ - struct name##Op final : DLOp { \ + struct name##Op final : DrawOpBase { \ static const auto kType = DisplayListOpType::k##name; \ \ name##Op(const sk_sp image, \ @@ -767,9 +911,11 @@ struct DrawImageRectOp final : DLOp { const DlFilterMode filter; \ const sk_sp image; \ \ - void dispatch(Dispatcher& dispatcher) const { \ - dispatcher.drawImageNine(image, center, dst, filter, \ - render_with_attributes); \ + void dispatch(DispatchContext& ctx) const { \ + if (op_needed(ctx)) { \ + ctx.dispatcher.drawImageNine(image, center, dst, filter, \ + render_with_attributes); \ + } \ } \ }; DEFINE_DRAW_IMAGE_NINE_OP(DrawImageNine, false) @@ -777,7 +923,7 @@ DEFINE_DRAW_IMAGE_NINE_OP(DrawImageNineWithAttr, true) #undef DEFINE_DRAW_IMAGE_NINE_OP // 4 byte header + 60 byte payload packs evenly into 64 bytes -struct DrawImageLatticeOp final : DLOp { +struct DrawImageLatticeOp final : DrawOpBase { static const auto kType = DisplayListOpType::kDrawImageLattice; DrawImageLatticeOp(const sk_sp image, @@ -806,20 +952,22 @@ struct DrawImageLatticeOp final : DLOp { const SkRect dst; const sk_sp image; - void dispatch(Dispatcher& dispatcher) const { - const int* xDivs = reinterpret_cast(this + 1); - const int* yDivs = reinterpret_cast(xDivs + x_count); - const SkColor* colors = - (cell_count == 0) ? nullptr - : reinterpret_cast(yDivs + y_count); - const SkCanvas::Lattice::RectType* types = - (cell_count == 0) - ? nullptr - : reinterpret_cast(colors + - cell_count); - dispatcher.drawImageLattice( - image, {xDivs, yDivs, types, x_count, y_count, &src, colors}, dst, - filter, with_paint); + void dispatch(DispatchContext& ctx) const { + if (op_needed(ctx)) { + const int* xDivs = reinterpret_cast(this + 1); + const int* yDivs = reinterpret_cast(xDivs + x_count); + const SkColor* colors = + (cell_count == 0) ? nullptr + : reinterpret_cast(yDivs + y_count); + const SkCanvas::Lattice::RectType* types = + (cell_count == 0) + ? nullptr + : reinterpret_cast( + colors + cell_count); + ctx.dispatcher.drawImageLattice( + image, {xDivs, yDivs, types, x_count, y_count, &src, colors}, dst, + filter, with_paint); + } } }; @@ -830,7 +978,7 @@ struct DrawImageLatticeOp final : DLOp { // SkRect list is also a multiple of 16 bytes so it also packs well // DlColor list only packs well if the count is even, otherwise there // can be 4 unusued bytes at the end. -struct DrawAtlasBaseOp : DLOp { +struct DrawAtlasBaseOp : DrawOpBase { DrawAtlasBaseOp(const sk_sp atlas, int count, DlBlendMode mode, @@ -870,14 +1018,16 @@ struct DrawAtlasOp final : DrawAtlasBaseOp { has_colors, render_with_attributes) {} - void dispatch(Dispatcher& dispatcher) const { - const SkRSXform* xform = reinterpret_cast(this + 1); - const SkRect* tex = reinterpret_cast(xform + count); - const DlColor* colors = - has_colors ? reinterpret_cast(tex + count) : nullptr; - const DlBlendMode mode = static_cast(mode_index); - dispatcher.drawAtlas(atlas, xform, tex, colors, count, mode, sampling, - nullptr, render_with_attributes); + void dispatch(DispatchContext& ctx) const { + if (op_needed(ctx)) { + const SkRSXform* xform = reinterpret_cast(this + 1); + const SkRect* tex = reinterpret_cast(xform + count); + const DlColor* colors = + has_colors ? reinterpret_cast(tex + count) : nullptr; + const DlBlendMode mode = static_cast(mode_index); + ctx.dispatcher.drawAtlas(atlas, xform, tex, colors, count, mode, sampling, + nullptr, render_with_attributes); + } } }; @@ -905,19 +1055,21 @@ struct DrawAtlasCulledOp final : DrawAtlasBaseOp { const SkRect cull_rect; - void dispatch(Dispatcher& dispatcher) const { - const SkRSXform* xform = reinterpret_cast(this + 1); - const SkRect* tex = reinterpret_cast(xform + count); - const DlColor* colors = - has_colors ? reinterpret_cast(tex + count) : nullptr; - const DlBlendMode mode = static_cast(mode_index); - dispatcher.drawAtlas(atlas, xform, tex, colors, count, mode, sampling, - &cull_rect, render_with_attributes); + void dispatch(DispatchContext& ctx) const { + if (op_needed(ctx)) { + const SkRSXform* xform = reinterpret_cast(this + 1); + const SkRect* tex = reinterpret_cast(xform + count); + const DlColor* colors = + has_colors ? reinterpret_cast(tex + count) : nullptr; + const DlBlendMode mode = static_cast(mode_index); + ctx.dispatcher.drawAtlas(atlas, xform, tex, colors, count, mode, sampling, + &cull_rect, render_with_attributes); + } } }; // 4 byte header + 12 byte payload packs evenly into 16 bytes -struct DrawSkPictureOp final : DLOp { +struct DrawSkPictureOp final : DrawOpBase { static const auto kType = DisplayListOpType::kDrawSkPicture; DrawSkPictureOp(sk_sp picture, bool render_with_attributes) @@ -927,34 +1079,45 @@ struct DrawSkPictureOp final : DLOp { const bool render_with_attributes; const sk_sp picture; - void dispatch(Dispatcher& dispatcher) const { - dispatcher.drawPicture(picture, nullptr, render_with_attributes); + void dispatch(DispatchContext& ctx) const { + if (op_needed(ctx)) { + ctx.dispatcher.drawPicture(picture, nullptr, render_with_attributes); + } } }; // 4 byte header + 52 byte payload packs evenly into 56 bytes -struct DrawSkPictureMatrixOp final : DLOp { +struct DrawSkPictureMatrixOp final : DrawOpBase { static const auto kType = DisplayListOpType::kDrawSkPictureMatrix; DrawSkPictureMatrixOp(sk_sp picture, - const SkMatrix matrix, + const SkMatrix& matrix, bool render_with_attributes) : render_with_attributes(render_with_attributes), picture(std::move(picture)), - matrix(matrix) {} + matrix(matrix) { + // The copy constructor might copy in an unknown or a resolved + // type depending on whether the source Matrix had been used + // since its last mutating method. Calling getType here forces + // the matrix object into a known state so that it can be bulk + // compared without having to introduce a custom equals() method. + this->matrix.getType(); + } const bool render_with_attributes; const sk_sp picture; const SkMatrix matrix; - void dispatch(Dispatcher& dispatcher) const { - dispatcher.drawPicture(picture, &matrix, render_with_attributes); + void dispatch(DispatchContext& ctx) const { + if (op_needed(ctx)) { + ctx.dispatcher.drawPicture(picture, &matrix, render_with_attributes); + } } }; // 4 byte header + ptr aligned payload uses 12 bytes round up to 16 // (4 bytes unused) -struct DrawDisplayListOp final : DLOp { +struct DrawDisplayListOp final : DrawOpBase { static const auto kType = DisplayListOpType::kDrawDisplayList; explicit DrawDisplayListOp(const sk_sp display_list) @@ -962,8 +1125,10 @@ struct DrawDisplayListOp final : DLOp { sk_sp display_list; - void dispatch(Dispatcher& dispatcher) const { - dispatcher.drawDisplayList(display_list); + void dispatch(DispatchContext& ctx) const { + if (op_needed(ctx)) { + ctx.dispatcher.drawDisplayList(display_list); + } } DisplayListCompare equals(const DrawDisplayListOp* other) const { @@ -975,7 +1140,7 @@ struct DrawDisplayListOp final : DLOp { // 4 byte header + 8 payload bytes + an aligned pointer take 24 bytes // (4 unused to align the pointer) -struct DrawTextBlobOp final : DLOp { +struct DrawTextBlobOp final : DrawOpBase { static const auto kType = DisplayListOpType::kDrawTextBlob; DrawTextBlobOp(const sk_sp blob, SkScalar x, SkScalar y) @@ -985,31 +1150,35 @@ struct DrawTextBlobOp final : DLOp { const SkScalar y; const sk_sp blob; - void dispatch(Dispatcher& dispatcher) const { - dispatcher.drawTextBlob(blob, x, y); + void dispatch(DispatchContext& ctx) const { + if (op_needed(ctx)) { + ctx.dispatcher.drawTextBlob(blob, x, y); + } } }; // 4 byte header + 28 byte payload packs evenly into 32 bytes -#define DEFINE_DRAW_SHADOW_OP(name, transparent_occluder) \ - struct Draw##name##Op final : DLOp { \ - static const auto kType = DisplayListOpType::kDraw##name; \ - \ - Draw##name##Op(const SkPath& path, \ - DlColor color, \ - SkScalar elevation, \ - SkScalar dpr) \ - : color(color), elevation(elevation), dpr(dpr), path(path) {} \ - \ - const DlColor color; \ - const SkScalar elevation; \ - const SkScalar dpr; \ - const SkPath path; \ - \ - void dispatch(Dispatcher& dispatcher) const { \ - dispatcher.drawShadow(path, color, elevation, transparent_occluder, \ - dpr); \ - } \ +#define DEFINE_DRAW_SHADOW_OP(name, transparent_occluder) \ + struct Draw##name##Op final : DrawOpBase { \ + static const auto kType = DisplayListOpType::kDraw##name; \ + \ + Draw##name##Op(const SkPath& path, \ + DlColor color, \ + SkScalar elevation, \ + SkScalar dpr) \ + : color(color), elevation(elevation), dpr(dpr), path(path) {} \ + \ + const DlColor color; \ + const SkScalar elevation; \ + const SkScalar dpr; \ + const SkPath path; \ + \ + void dispatch(DispatchContext& ctx) const { \ + if (op_needed(ctx)) { \ + ctx.dispatcher.drawShadow(path, color, elevation, \ + transparent_occluder, dpr); \ + } \ + } \ }; DEFINE_DRAW_SHADOW_OP(Shadow, false) DEFINE_DRAW_SHADOW_OP(ShadowTransparentOccluder, true) diff --git a/display_list/display_list_rtree.cc b/display_list/display_list_rtree.cc index 2d641925bb419..9979bf1ec6148 100644 --- a/display_list/display_list_rtree.cc +++ b/display_list/display_list_rtree.cc @@ -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; + + // 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; + } -void DlRTree::insert(const SkRect boundsArray[], int N) { - insert(boundsArray, nullptr, N); + 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* 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 DlRTree::searchNonOverlappingDrawnRects( +std::list DlRTree::searchAndConsolidateRects( const SkRect& query) const { // Get the indexes for the operations that intersect with the query rect. std::vector intermediary_results; @@ -39,12 +167,7 @@ std::list DlRTree::searchNonOverlappingDrawnRects( std::list 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 DlRTree::searchNonOverlappingDrawnRects( return final_results; } -size_t DlRTree::bytesUsed() const { - return bbh_->bytesUsed(); -} - -DlRTreeFactory::DlRTreeFactory() { - r_tree_ = sk_make_sp(); -} - -sk_sp DlRTreeFactory::getInstance() { - return r_tree_; -} - -sk_sp DlRTreeFactory::operator()() const { - return r_tree_; +void DlRTree::search(const Node& parent, + const SkRect& query, + std::vector* 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 diff --git a/display_list/display_list_rtree.h b/display_list/display_list_rtree.h index 189c6e4e4d858..d6a3147d4dd81 100644 --- a/display_list/display_list_rtree.h +++ b/display_list/display_list_rtree.h @@ -6,61 +6,125 @@ #define FLUTTER_DISPLAY_LIST_RTREE_H_ #include -#include +#include -#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 { - public: - DlRTree(); - - void insert(const SkRect[], - const SkBBoxHierarchy::Metadata[], - int N) override; - void insert(const SkRect[], int N) override; - void search(const SkRect& query, std::vector* results) const override; - size_t bytesUsed() const override; - - // 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 searchNonOverlappingDrawnRects(const SkRect& query) const; - - // Insertion count (not overall node count, which may be greater). - int getCount() const { return all_ops_count_; } - +/// 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: - // A map containing the draw operation rects keyed off the operation index - // in the insert call. - std::map draw_op_; - sk_sp bbh_; - int all_ops_count_; -}; + 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; + }; + }; -class DlRTreeFactory : public SkBBHFactory { public: - DlRTreeFactory(); + /// 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); + + /// 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* results) 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_; + } - // Gets the instance to the R-tree. - sk_sp getInstance(); - sk_sp operator()() const override; + /// 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 searchAndConsolidateRects(const SkRect& query) const; private: - sk_sp r_tree_; + static constexpr SkRect empty_ = SkRect::MakeEmpty(); + + void search(const Node& parent, + const SkRect& query, + std::vector* results) const; + + std::vector nodes_; + int leaf_count_; + int invalid_id_; }; } // namespace flutter diff --git a/display_list/display_list_rtree_unittests.cc b/display_list/display_list_rtree_unittests.cc new file mode 100644 index 0000000000000..eb610819e0fa7 --- /dev/null +++ b/display_list/display_list_rtree_unittests.cc @@ -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 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 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 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 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 diff --git a/display_list/display_list_test_utils.cc b/display_list/display_list_test_utils.cc index 144fe140d7490..9d8ee65bb9c03 100644 --- a/display_list/display_list_test_utils.cc +++ b/display_list/display_list_test_utils.cc @@ -304,7 +304,7 @@ std::vector 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 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 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 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 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 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 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 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 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); diff --git a/display_list/display_list_unittests.cc b/display_list/display_list_unittests.cc index 21b1ce88dcab4..eca8e94d74909 100644 --- a/display_list/display_list_unittests.cc +++ b/display_list/display_list_unittests.cc @@ -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 dl1 = buider1.Build(); - sk_sp 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 dl1 = builder1.Build(); + sk_sp 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& rtree, rtree->search(query, &indices); EXPECT_EQ(indices, expected_indices); EXPECT_EQ(indices.size(), expected_indices.size()); - std::list rects = rtree->searchNonOverlappingDrawnRects(query); + std::list 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 diff --git a/display_list/display_list_utils.cc b/display_list/display_list_utils.cc index ab8e3c28eff94..e13f640dca2d0 100644 --- a/display_list/display_list_utils.cc +++ b/display_list/display_list_utils.cc @@ -107,7 +107,7 @@ sk_sp 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 RTreeBoundsAccumulator::rtree() const { FML_DCHECK(saved_offsets_.empty()); - DlRTreeFactory factory; - sk_sp rtree = factory.getInstance(); - rtree->insert(rects_.data(), rects_.size()); - return rtree; + return sk_make_sp(rects_.data(), rects_.size(), rect_indices_.data(), + [](int id) { return id >= 0; }); } } // namespace flutter diff --git a/display_list/display_list_utils.h b/display_list/display_list_utils.h index 992d464ff8d38..37222f01bdbdc 100644 --- a/display_list/display_list_utils.h +++ b/display_list/display_list_utils.h @@ -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 rects_; + std::vector rect_indices_; std::vector saved_offsets_; }; diff --git a/flow/embedded_views.cc b/flow/embedded_views.cc index 68f845fb85c3f..a6eb2d3442651 100644 --- a/flow/embedded_views.cc +++ b/flow/embedded_views.cc @@ -60,7 +60,7 @@ void DisplayListEmbedderViewSlice::end_recording() { std::list DisplayListEmbedderViewSlice::searchNonOverlappingDrawnRects( const SkRect& query) const { - return display_list_->rtree()->searchNonOverlappingDrawnRects(query); + return display_list_->rtree()->searchAndConsolidateRects(query); } void DisplayListEmbedderViewSlice::render_into(SkCanvas* canvas) {