Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 83 additions & 11 deletions display_list/display_list.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand All @@ -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;
Expand Down Expand Up @@ -151,47 +163,106 @@ 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<const DLOp*>(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<int> 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,
// next_render_index will be initialized by culler.init()
.next_restore_index = std::numeric_limits<int>::max(),
};
if (!culler.init(context)) {
tracker.current_offset_ = byte_count_;
return;
}
while (ptr < end) {
tracker.current_offset_ = ptr - storage_.get();
auto op = reinterpret_cast<const DLOp*>(ptr);
ptr += op->size;
FML_DCHECK(ptr <= end);
Expand All @@ -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) {
Expand Down
148 changes: 147 additions & 1 deletion display_list/display_list.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<DisplayList> display_list, size_t offset)
: display_list_(std::move(display_list)), offset_(offset) {}

sk_sp<DisplayList> 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<DisplayList> 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<MyBookmarkRecord> my_records_;
/// };
///
/// sk_sp<DisplayList> 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<DisplayList> 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<DisplayList> display_list_;
size_t current_offset_;

friend class DisplayList;
};

private:
DisplayList(DisplayListStorage&& ptr,
size_t byte_count,
Expand All @@ -376,6 +515,11 @@ class DisplayList : public SkRefCnt {
bool root_is_unbounded,
sk_sp<const DlRTree> 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);
Expand Down Expand Up @@ -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
Expand Down
Loading