diff --git a/display_list/display_list.cc b/display_list/display_list.cc index ccfd97e7a963e..3ab570e811e33 100644 --- a/display_list/display_list.cc +++ b/display_list/display_list.cc @@ -92,6 +92,14 @@ class NopCuller final : public Culler { // indices will be after it as well so all // clip and transform operations will execute. context.next_render_index = 0; + + // The culling_needed flag is only consulted in + // ops that have a complicated determination + // as to whether they need to be culled (for + // example, save/restore will push/pop an entry + // onto a vector). Consulting this flag can + // save that time. + context.culling_needed = false; return true; } void update(DispatchContext& context) override {} @@ -105,6 +113,10 @@ class VectorCuller final : public Culler { ~VectorCuller() = default; bool init(DispatchContext& context) override { + // No shortcuts for ops that have a complicated "needed" + // determination. + context.culling_needed = true; + if (cur_ < end_) { context.next_render_index = rtree_->id(*cur_++); return true; @@ -151,37 +163,94 @@ class VectorCuller final : public Culler { }; void DisplayList::Dispatch(DlOpReceiver& receiver) const { - const uint8_t* ptr = storage_.get(); - Dispatch(receiver, ptr, ptr + byte_count_, NopCuller::instance); + Dispatcher dispatcher(sk_ref_sp(this)); + dispatcher.Dispatch(receiver); } void DisplayList::Dispatch(DlOpReceiver& receiver, const SkIRect& cull_rect) const { - Dispatch(receiver, SkRect::Make(cull_rect)); + Dispatcher dispatcher(sk_ref_sp(this)); + dispatcher.Dispatch(receiver, SkRect::Make(cull_rect)); } void DisplayList::Dispatch(DlOpReceiver& receiver, const SkRect& cull_rect) const { + Dispatcher dispatcher(sk_ref_sp(this)); + dispatcher.Dispatch(receiver, cull_rect); +} + +bool DisplayList::Bookmark::Dispatch(DlOpReceiver& receiver) const { + if (!DisplayList::IsValidOffset(offset_)) { + FML_LOG(WARNING) << "Dispatching invalid bookmark"; + return false; + } + FML_DCHECK(offset_ < display_list_->byte_count_); + + const uint8_t* ptr = display_list_->storage_.get() + offset_; + auto op = reinterpret_cast(ptr); + FML_DCHECK(offset_ + op->size <= display_list_->byte_count_); + + Dispatcher dispatcher(display_list_); + display_list_->Dispatch(receiver, ptr, ptr + op->size, NopCuller::instance, + dispatcher); + return true; +} + +DisplayList::Bookmark DisplayList::Dispatcher::GetBookmark() const { + if (!DisplayList::IsValidOffset(current_offset_) || + current_offset_ >= display_list_->byte_count_) { + FML_LOG(WARNING) << "Creating bookmark on invalid offset"; + return Bookmark(nullptr, kInvalidOffset); + } + return Bookmark(display_list_, current_offset_); +} + +bool DisplayList::Dispatcher::Dispatch(DlOpReceiver& receiver) { + if (DisplayList::IsValidOffset(current_offset_)) { + FML_LOG(WARNING) << "Attempting to dispatch on used DispatchTracker"; + return false; + } + const uint8_t* ptr = display_list_->storage_.get(); + const uint8_t* end = ptr + display_list_->byte_count_; + display_list_->Dispatch(receiver, ptr, end, NopCuller::instance, *this); + return true; +} + +bool DisplayList::Dispatcher::Dispatch(DlOpReceiver& receiver, + const SkIRect& cull_rect) { + return Dispatch(receiver, SkRect::Make(cull_rect)); +} + +bool DisplayList::Dispatcher::Dispatch(DlOpReceiver& receiver, + const SkRect& cull_rect) { + if (DisplayList::IsValidOffset(current_offset_)) { + FML_LOG(WARNING) << "Attempting to dispatch on used DispatchTracker"; + return false; + } if (cull_rect.isEmpty()) { - return; + current_offset_ = display_list_->byte_count_; + return true; } - if (!has_rtree() || cull_rect.contains(bounds())) { - Dispatch(receiver); - return; + if (!display_list_->has_rtree() || + cull_rect.contains(display_list_->bounds())) { + return Dispatch(receiver); } - const DlRTree* rtree = this->rtree().get(); + const DlRTree* rtree = display_list_->rtree().get(); FML_DCHECK(rtree != nullptr); - const uint8_t* ptr = storage_.get(); + const uint8_t* ptr = display_list_->storage_.get(); + const uint8_t* end = ptr + display_list_->byte_count_; std::vector rect_indices; rtree->search(cull_rect, &rect_indices); VectorCuller culler(rtree, rect_indices); - Dispatch(receiver, ptr, ptr + byte_count_, culler); + display_list_->Dispatch(receiver, ptr, end, culler, *this); + return true; } void DisplayList::Dispatch(DlOpReceiver& receiver, const uint8_t* ptr, const uint8_t* end, - Culler& culler) const { + Culler& culler, + Dispatcher& tracker) const { DispatchContext context = { .receiver = receiver, .cur_index = 0, @@ -189,9 +258,11 @@ void DisplayList::Dispatch(DlOpReceiver& receiver, .next_restore_index = std::numeric_limits::max(), }; if (!culler.init(context)) { + tracker.current_offset_ = byte_count_; return; } while (ptr < end) { + tracker.current_offset_ = ptr - storage_.get(); auto op = reinterpret_cast(ptr); ptr += op->size; FML_DCHECK(ptr <= end); @@ -214,6 +285,7 @@ void DisplayList::Dispatch(DlOpReceiver& receiver, } culler.update(context); } + tracker.current_offset_ = byte_count_; } void DisplayList::DisposeOps(const uint8_t* ptr, const uint8_t* end) { diff --git a/display_list/display_list.h b/display_list/display_list.h index 5c3ef29625988..3b07cc49e3b8d 100644 --- a/display_list/display_list.h +++ b/display_list/display_list.h @@ -360,6 +360,145 @@ class DisplayList : public SkRefCnt { /// be required for the indicated blend mode to do its work. DlBlendMode max_root_blend_mode() const { return max_root_blend_mode_; } + class Dispatcher; + + /// @brief A class that holds a reference to a specific operation + /// recorded within a DisplayList buffer. + /// + /// This bookmark must be obtained from a |DispatchTracker| during an + /// execution of one of the Tracker's Dispatch methods. These objects + /// are not guaranteed to be thread safe, but they can be used during + /// or after the Tracker's Dispatch operation concludes. A reference + /// to the |DisplayList| from which the bookmark comes is maintained so + /// that their lifetime is not affected by the lifetime of the DisplayList + /// or the Tracker. + /// + /// A Bookmark obtained from a tracker that is not currently in the process + /// of one of its Dispatch methods will be a NOP and will log a WARNING. + class Bookmark { + public: + /// Dispatches the single |DlOpReceiver| method referred to by this + /// bookmark and returns true if the bookmark is valid. + bool Dispatch(DlOpReceiver& receiver) const; + + private: + Bookmark(sk_sp display_list, size_t offset) + : display_list_(std::move(display_list)), offset_(offset) {} + + sk_sp display_list_; + const size_t offset_; + + friend class Dispatcher; + }; + + /// @brief A class that manages a trackable traversal of the operations + /// in a |DisplayList| so that |Bookmark|s can be queried during + /// the traversal. + /// + /// Bookmarks must be obtained from this tracker during the execution of + /// one of the its Dispatch methods. A tracker can only be used for one + /// call of any of its Dispatch methods and an attempt to call any of the + /// Dispatch methods after that will be a NOP and will log a WARNING. + /// + /// An example of using a Dispatcher object to save Bookmarks for later + /// playback: + /// + /// { + /// class MyBookmarkRecord { + /// DisplayList::Bookmark bookmark; + /// MyState other_state; + /// }; + /// class MyBookmarkReceiver : public DlOpReceiver { + /// public: + /// void consume(sk_sp display_list) { + /// dispatcher_ = DisplayList::Dispatcher(display_list); + /// dispatcher_.Dispatch(*this); + /// } + /// + /// ... + /// // Remember transient state in current_state_ + /// ... + /// void drawRect(const SkRect& rect) { + /// if (bookmark_needed()) { + /// my_records_.emplace_back(dispatcher_.GetBookmark(), + /// current_state_); + /// } + /// ... + /// } + /// ... + /// + /// private: + /// MyState current_state_; + /// DisplayList::Dispatcher dispatcher_; + /// std::vector my_records_; + /// }; + /// + /// sk_sp my_dl = ...; + /// MyReceiver my_receiver; + /// my_receiver.consume(my_dl); + /// + /// ... later ... + /// + /// for (const auto& my_bookmark : my_receiver.GetRecords()) { + /// my_other_receiver.SetState(my_bookmark.other_state); + /// my_bookmark.bookmark.Dispatch(my_other_receiver); + /// } + /// } + /// + /// Note that the bookmark only dispatches a single DlOpReceiver call and + /// any state that is associated with that call must be remembered along + /// with the bookmark. Also, if a bookmark is saved to a save or saveLayer + /// call then that call must be balanced with a restore call, whether by + /// getting a bookmark for that restore call from the original stream, or + /// by just calling restore() manually on the receiver the bookmark is + /// dispatching to, when appropriate. + class Dispatcher { + public: + explicit Dispatcher(sk_sp display_list) + : display_list_(std::move(display_list)), + current_offset_(kInvalidOffset) {} + + Dispatcher(const Dispatcher& copy) = delete; + Dispatcher(Dispatcher&& move) = delete; + Dispatcher& operator=(const Dispatcher&) = delete; + Dispatcher& operator=(Dispatcher&&) = delete; + + /// @brief Dispatch the tracked display list to the supplied receiver + /// and return true if the operation is successful (i.e. the + /// tracker has not already been used to dispatch the display + /// list). + bool Dispatch(DlOpReceiver& receiver); + + /// @brief Dispatch the tracked display list to the supplied receiver + /// with a supplied culling rectangle and return true if the + /// operation is successful (i.e. the tracker has not already + /// been used to dispatch the display list). + bool Dispatch(DlOpReceiver& receiver, const SkRect& cull_rect); + + /// @brief Dispatch the tracked display list to the supplied receiver + /// with a supplied culling rectangle and return true if the + /// operation is successful (i.e. the tracker has not already + /// been used to dispatch the display list). + bool Dispatch(DlOpReceiver& receiver, const SkIRect& cull_rect); + + /// @brief Obtain a bookmark to the operation currently being + /// dispatched by the tracker. + /// + /// The Bookmark can be used to re-dispatch the current |DlOpReceiver| + /// call and only that one call with no additional calls to re-establish + /// the current DisplayList state. As such, it is the responsibility + /// of the receiver asking for the bookmark to remember any contextual + /// state for that call, such as previous attribute, transform, and clip + /// settings. + Bookmark GetBookmark() const; + + private: + sk_sp display_list_; + size_t current_offset_; + + friend class DisplayList; + }; + private: DisplayList(DisplayListStorage&& ptr, size_t byte_count, @@ -376,6 +515,11 @@ class DisplayList : public SkRefCnt { bool root_is_unbounded, sk_sp rtree); + static constexpr size_t kInvalidOffset = 1u; + static constexpr bool IsValidOffset(size_t offset) { + return (offset & 0x1) == 0; + } + static uint32_t next_unique_id(); static void DisposeOps(const uint8_t* ptr, const uint8_t* end); @@ -404,9 +548,11 @@ class DisplayList : public SkRefCnt { void Dispatch(DlOpReceiver& ctx, const uint8_t* ptr, const uint8_t* end, - Culler& culler) const; + Culler& culler, + Dispatcher& tracker) const; friend class DisplayListBuilder; + friend class Bookmark; }; } // namespace flutter diff --git a/display_list/display_list_unittests.cc b/display_list/display_list_unittests.cc index c1a5f3cd7d93e..2654e7ecb2f05 100644 --- a/display_list/display_list_unittests.cc +++ b/display_list/display_list_unittests.cc @@ -231,6 +231,11 @@ class DisplayListTestBase : public BaseT { }; using DisplayListTest = DisplayListTestBase<::testing::Test>; +class DlOpReceiverNop : public IgnoreAttributeDispatchHelper, + public IgnoreTransformDispatchHelper, + public IgnoreClipDispatchHelper, + public IgnoreDrawDispatchHelper {}; + TEST_F(DisplayListTest, Defaults) { DisplayListBuilder builder; check_defaults(builder); @@ -262,6 +267,248 @@ TEST_F(DisplayListTest, BuilderCanBeReused) { ASSERT_TRUE(dl->Equals(dl2)); } +TEST_F(DisplayListTest, DispatcherInvalidBookmarks) { + DisplayListBuilder builder; + auto dl = builder.Build(); + DisplayList::Dispatcher dispatcher(dl); + DlOpReceiverNop receiver; + EXPECT_FALSE(dispatcher.GetBookmark().Dispatch(receiver)); + EXPECT_TRUE(dispatcher.Dispatch(receiver)); + EXPECT_FALSE(dispatcher.GetBookmark().Dispatch(receiver)); +} + +TEST_F(DisplayListTest, DispatcherNoReDispatch) { + SkIRect irect = SkIRect::MakeLTRB(0, 0, 10, 10); + SkRect rect = SkRect::MakeLTRB(0, 0, 10, 10); + DisplayListBuilder builder; + auto dl = builder.Build(); + DlOpReceiverNop receiver; + + { + DisplayList::Dispatcher dispatcher(dl); + EXPECT_TRUE(dispatcher.Dispatch(receiver)); + EXPECT_FALSE(dispatcher.Dispatch(receiver)); + EXPECT_FALSE(dispatcher.Dispatch(receiver, irect)); + EXPECT_FALSE(dispatcher.Dispatch(receiver, rect)); + } + + { + DisplayList::Dispatcher dispatcher(dl); + EXPECT_TRUE(dispatcher.Dispatch(receiver, irect)); + EXPECT_FALSE(dispatcher.Dispatch(receiver)); + EXPECT_FALSE(dispatcher.Dispatch(receiver, irect)); + EXPECT_FALSE(dispatcher.Dispatch(receiver, rect)); + } + + { + DisplayList::Dispatcher dispatcher(dl); + EXPECT_TRUE(dispatcher.Dispatch(receiver, rect)); + EXPECT_FALSE(dispatcher.Dispatch(receiver)); + EXPECT_FALSE(dispatcher.Dispatch(receiver, irect)); + EXPECT_FALSE(dispatcher.Dispatch(receiver, rect)); + } + + { + DisplayList::Dispatcher dispatcher(dl); + EXPECT_TRUE(dispatcher.Dispatch(receiver, SkIRect::MakeEmpty())); + EXPECT_FALSE(dispatcher.Dispatch(receiver)); + EXPECT_FALSE(dispatcher.Dispatch(receiver, irect)); + EXPECT_FALSE(dispatcher.Dispatch(receiver, rect)); + } + + { + DisplayList::Dispatcher dispatcher(dl); + EXPECT_TRUE(dispatcher.Dispatch(receiver, SkRect::MakeEmpty())); + EXPECT_FALSE(dispatcher.Dispatch(receiver)); + EXPECT_FALSE(dispatcher.Dispatch(receiver, irect)); + EXPECT_FALSE(dispatcher.Dispatch(receiver, rect)); + } +} + +class BookmarkRecorder : public DlOpReceiver { + public: + explicit BookmarkRecorder(sk_sp& display_list) + : dispatcher_(display_list) { + dispatcher_.Dispatch(*this); + } + + void setAntiAlias(bool aa) override { record(); } + void setInvertColors(bool invert) override { record(); } + void setStrokeCap(DlStrokeCap cap) override { record(); } + void setStrokeJoin(DlStrokeJoin join) override { record(); } + void setDrawStyle(DlDrawStyle style) override { record(); } + void setStrokeWidth(float width) override { record(); } + void setStrokeMiter(float limit) override { record(); } + void setColor(DlColor color) override { record(); } + void setBlendMode(DlBlendMode mode) override { record(); } + void setColorSource(const DlColorSource* source) override { record(); } + void setImageFilter(const DlImageFilter* filter) override { record(); } + void setColorFilter(const DlColorFilter* filter) override { record(); } + void setMaskFilter(const DlMaskFilter* filter) override { record(); } + + void translate(SkScalar tx, SkScalar ty) override { record(); } + void scale(SkScalar sx, SkScalar sy) override { record(); } + void rotate(SkScalar degrees) override { record(); } + void skew(SkScalar sx, SkScalar sy) override { record(); } + // clang-format off + // 2x3 2D affine subset of a 4x4 transform in row major order + void transform2DAffine(SkScalar mxx, SkScalar mxy, SkScalar mxt, + SkScalar myx, SkScalar myy, SkScalar myt) override { + record(); + } + // full 4x4 transform in row major order + void transformFullPerspective( + SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt, + SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt, + SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt, + SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) override { + record(); + } + // clang-format on + void transformReset() override { record(); } + + void clipRect(const SkRect& rect, + DlCanvas::ClipOp clip_op, + bool is_aa) override { + record(); + } + void clipOval(const SkRect& bounds, + DlCanvas::ClipOp clip_op, + bool is_aa) override { + record(); + } + void clipRRect(const SkRRect& rrect, + DlCanvas::ClipOp clip_op, + bool is_aa) override { + record(); + } + void clipPath(const SkPath& path, + DlCanvas::ClipOp clip_op, + bool is_aa) override { + record(); + } + + void save() override { record(); } + void saveLayer(const SkRect& bounds, + const SaveLayerOptions options, + const DlImageFilter* backdrop) override { + record(); + } + void restore() override { record(); } + void drawColor(DlColor color, DlBlendMode mode) override { record(); } + void drawPaint() override { record(); } + void drawLine(const SkPoint& p0, const SkPoint& p1) override { record(); } + void drawDashedLine(const DlPoint& p0, + const DlPoint& p1, + DlScalar on_length, + DlScalar off_length) override { + record(); + } + void drawRect(const SkRect& rect) override { record(); } + void drawOval(const SkRect& bounds) override { record(); } + void drawCircle(const SkPoint& center, SkScalar radius) override { record(); } + void drawRRect(const SkRRect& rrect) override { record(); } + void drawDRRect(const SkRRect& outer, const SkRRect& inner) override { + record(); + } + void drawPath(const SkPath& path) override { record(); } + void drawArc(const SkRect& oval_bounds, + SkScalar start_degrees, + SkScalar sweep_degrees, + bool use_center) override { + record(); + } + void drawPoints(DlCanvas::PointMode mode, + uint32_t count, + const SkPoint points[]) override { + record(); + } + void drawVertices(const std::shared_ptr& vertices, + DlBlendMode mode) override { + record(); + } + void drawImage(const sk_sp image, + const SkPoint point, + DlImageSampling sampling, + bool render_with_attributes) override { + record(); + } + void drawImageRect(const sk_sp image, + const SkRect& src, + const SkRect& dst, + DlImageSampling sampling, + bool render_with_attributes, + SrcRectConstraint constraint) override { + record(); + } + void drawImageNine(const sk_sp image, + const SkIRect& center, + const SkRect& dst, + DlFilterMode filter, + bool render_with_attributes) override { + record(); + } + void drawAtlas(const sk_sp atlas, + const SkRSXform xform[], + const SkRect tex[], + const DlColor colors[], + int count, + DlBlendMode mode, + DlImageSampling sampling, + const SkRect* cull_rect, + bool render_with_attributes) override { + record(); + } + void drawDisplayList(const sk_sp display_list, + SkScalar opacity) override { + record(); + } + void drawTextBlob(const sk_sp blob, + SkScalar x, + SkScalar y) override { + record(); + } + void drawTextFrame(const std::shared_ptr& text_frame, + SkScalar x, + SkScalar y) override { + record(); + } + void drawShadow(const SkPath& path, + const DlColor color, + const SkScalar elevation, + bool transparent_occluder, + SkScalar dpr) override { + record(); + } + + const std::vector& GetBookmarks() const { + return bookmarks_; + } + + private: + DisplayList::Dispatcher dispatcher_; + + void record() { bookmarks_.emplace_back(dispatcher_.GetBookmark()); } + + std::vector bookmarks_; +}; + +TEST_F(DisplayListTest, RedispatchOpsThroughBookmarks) { + for (auto& group : allGroups) { + for (size_t i = 0; i < group.variants.size(); i++) { + auto& invocation = group.variants[i]; + sk_sp dl = Build(invocation); + BookmarkRecorder recorder(dl); + DisplayListBuilder builder; + DlOpReceiver& receiver = DisplayListBuilderTestingAccessor(builder); + for (const auto& bookmark : recorder.GetBookmarks()) { + EXPECT_TRUE(bookmark.Dispatch(receiver)); + } + EXPECT_TRUE(DisplayListsEQ_Verbose(dl, builder.Build())); + } + } +} + TEST_F(DisplayListTest, SaveRestoreRestoresTransform) { SkRect cull_rect = SkRect::MakeLTRB(-10.0f, -10.0f, 500.0f, 500.0f); DisplayListBuilder builder(cull_rect); diff --git a/display_list/dl_builder.cc b/display_list/dl_builder.cc index 14893c37c0f8c..9c6fd73f6b32d 100644 --- a/display_list/dl_builder.cc +++ b/display_list/dl_builder.cc @@ -130,6 +130,14 @@ DisplayListBuilder::DisplayListBuilder(const SkRect& cull_rect, } void DisplayListBuilder::Init(bool prepare_rtree) { + // Ensure that DisplayList's invalid offset would not be an aligned + // offset within a display list buffer. + static_assert(SkAlignPtr(DisplayList::kInvalidOffset) != + DisplayList::kInvalidOffset); + // Ensure that aligned values are considered valid offsets within + // a display list buffer. + static_assert( + DisplayList::IsValidOffset(SkAlignPtr(DisplayList::kInvalidOffset))); FML_DCHECK(save_stack_.empty()); FML_DCHECK(!rtree_data_.has_value()); diff --git a/display_list/dl_op_records.h b/display_list/dl_op_records.h index aeb0379e2371f..6cd2d962e5f75 100644 --- a/display_list/dl_op_records.h +++ b/display_list/dl_op_records.h @@ -42,6 +42,8 @@ namespace flutter { struct DispatchContext { DlOpReceiver& receiver; + bool culling_needed; + int cur_index; int next_render_index; @@ -334,6 +336,11 @@ struct SaveOpBase : DLOp { uint32_t total_content_depth; inline bool save_needed(DispatchContext& ctx) const { + if (!ctx.culling_needed) { + FML_DCHECK(ctx.next_restore_index == std::numeric_limits::max()); + FML_DCHECK(ctx.save_infos.empty()); + return true; + } bool needed = ctx.next_render_index <= restore_index; ctx.save_infos.emplace_back(ctx.next_restore_index, needed); ctx.next_restore_index = restore_index; @@ -411,12 +418,20 @@ struct RestoreOp final : DLOp { RestoreOp() {} void dispatch(DispatchContext& ctx) const { - DispatchContext::SaveInfo& info = ctx.save_infos.back(); - if (info.save_was_needed) { + bool save_was_needed; + if (ctx.culling_needed) { + DispatchContext::SaveInfo& info = ctx.save_infos.back(); + save_was_needed = info.save_was_needed; + ctx.next_restore_index = info.previous_restore_index; + ctx.save_infos.pop_back(); + } else { + FML_DCHECK(ctx.save_infos.empty()); + FML_DCHECK(ctx.next_restore_index == std::numeric_limits::max()); + save_was_needed = true; + } + if (save_was_needed) { ctx.receiver.restore(); } - ctx.next_restore_index = info.previous_restore_index; - ctx.save_infos.pop_back(); } };