From b88afe42a733fcf03e66ca40d99c11457fbe6477 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Wed, 21 Aug 2024 11:20:24 -0700 Subject: [PATCH] Reland "[DisplayList] Allow random access to ops through indexing" (#54676) Now also fixes: https://github.com/flutter/flutter/issues/153737 Being able to reorder rendering commands leads to optimization opportunities in the graphics package. A graphics package being fed from a DisplayList either has to take the commands in the order given or implement their own storage format for the rendering data. With this new dispatching mechanism, the graphics package can both query basic information about the recorded ops and even dispatch them by the index into the list. Query information includes either the "category" of the op (clip/transform/render, etc.) or a specific op type enum. The package can dispatch some categories (or ops) immediately and remember other categories (or ops) along with their state for dispatching later. --- .../benchmarking/dl_builder_benchmarks.cc | 94 ++++ display_list/display_list.cc | 392 +++++++++++---- display_list/display_list.h | 181 ++++++- display_list/display_list_unittests.cc | 252 +++++++++- display_list/dl_builder.h | 2 +- display_list/dl_op_records.h | 460 +++++++----------- testing/display_list_testing.cc | 118 +++-- testing/display_list_testing.h | 396 +++++++++++++++ 8 files changed, 1432 insertions(+), 463 deletions(-) diff --git a/display_list/benchmarking/dl_builder_benchmarks.cc b/display_list/benchmarking/dl_builder_benchmarks.cc index 8f9f05ea895dd..3463ba55685cb 100644 --- a/display_list/benchmarking/dl_builder_benchmarks.cc +++ b/display_list/benchmarking/dl_builder_benchmarks.cc @@ -220,6 +220,60 @@ static void BM_DisplayListDispatchDefault( } } +static void BM_DisplayListDispatchByIndexDefault( + benchmark::State& state, + DisplayListDispatchBenchmarkType type) { + bool prepare_rtree = NeedPrepareRTree(type); + DisplayListBuilder builder(prepare_rtree); + for (int i = 0; i < 5; i++) { + InvokeAllOps(builder); + } + auto display_list = builder.Build(); + DlOpReceiverIgnore receiver; + while (state.KeepRunning()) { + DlIndex end = display_list->GetRecordCount(); + for (DlIndex i = 0u; i < end; i++) { + display_list->Dispatch(receiver, i); + } + } +} + +static void BM_DisplayListDispatchByIteratorDefault( + benchmark::State& state, + DisplayListDispatchBenchmarkType type) { + bool prepare_rtree = NeedPrepareRTree(type); + DisplayListBuilder builder(prepare_rtree); + for (int i = 0; i < 5; i++) { + InvokeAllOps(builder); + } + auto display_list = builder.Build(); + DlOpReceiverIgnore receiver; + while (state.KeepRunning()) { + for (DlIndex i : *display_list) { + display_list->Dispatch(receiver, i); + } + } +} + +static void BM_DisplayListDispatchByVectorDefault( + benchmark::State& state, + DisplayListDispatchBenchmarkType type) { + bool prepare_rtree = NeedPrepareRTree(type); + DisplayListBuilder builder(prepare_rtree); + for (int i = 0; i < 5; i++) { + InvokeAllOps(builder); + } + auto display_list = builder.Build(); + DlOpReceiverIgnore receiver; + while (state.KeepRunning()) { + std::vector indices = + display_list->GetCulledIndices(display_list->bounds()); + for (DlIndex index : indices) { + display_list->Dispatch(receiver, index); + } + } +} + static void BM_DisplayListDispatchCull(benchmark::State& state, DisplayListDispatchBenchmarkType type) { bool prepare_rtree = NeedPrepareRTree(type); @@ -236,6 +290,26 @@ static void BM_DisplayListDispatchCull(benchmark::State& state, } } +static void BM_DisplayListDispatchByVectorCull( + benchmark::State& state, + DisplayListDispatchBenchmarkType type) { + bool prepare_rtree = NeedPrepareRTree(type); + DisplayListBuilder builder(prepare_rtree); + for (int i = 0; i < 5; i++) { + InvokeAllOps(builder); + } + auto display_list = builder.Build(); + SkRect rect = SkRect::MakeLTRB(0, 0, 100, 100); + EXPECT_FALSE(rect.contains(display_list->bounds())); + DlOpReceiverIgnore receiver; + while (state.KeepRunning()) { + std::vector indices = display_list->GetCulledIndices(rect); + for (DlIndex index : indices) { + display_list->Dispatch(receiver, index); + } + } +} + BENCHMARK_CAPTURE(BM_DisplayListBuilderDefault, kDefault, DisplayListBuilderBenchmarkType::kDefault) @@ -370,4 +444,24 @@ BENCHMARK_CAPTURE(BM_DisplayListDispatchCull, DisplayListDispatchBenchmarkType::kCulledWithRtree) ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DisplayListDispatchByIndexDefault, + kDefaultNoRtree, + DisplayListDispatchBenchmarkType::kDefaultNoRtree) + ->Unit(benchmark::kMicrosecond); + +BENCHMARK_CAPTURE(BM_DisplayListDispatchByIteratorDefault, + kDefaultNoRtree, + DisplayListDispatchBenchmarkType::kDefaultNoRtree) + ->Unit(benchmark::kMicrosecond); + +BENCHMARK_CAPTURE(BM_DisplayListDispatchByVectorDefault, + kDefaultNoRtree, + DisplayListDispatchBenchmarkType::kDefaultNoRtree) + ->Unit(benchmark::kMicrosecond); + +BENCHMARK_CAPTURE(BM_DisplayListDispatchByVectorCull, + kCulledWithRtree, + DisplayListDispatchBenchmarkType::kCulledWithRtree) + ->Unit(benchmark::kMicrosecond); + } // namespace flutter diff --git a/display_list/display_list.cc b/display_list/display_list.cc index ccfd97e7a963e..9ecc331283eea 100644 --- a/display_list/display_list.cc +++ b/display_list/display_list.cc @@ -29,6 +29,21 @@ DisplayList::DisplayList() root_is_unbounded_(false), max_root_blend_mode_(DlBlendMode::kClear) {} +// Eventually we should rework DisplayListBuilder to compute these and +// deliver the vector alongside the storage. +static std::vector MakeOffsets(const DisplayListStorage& storage, + size_t byte_count) { + std::vector offsets; + const uint8_t* start = storage.get(); + const uint8_t* end = start + byte_count; + const uint8_t* ptr = start; + while (ptr < end) { + offsets.push_back(ptr - start); + ptr += reinterpret_cast(ptr)->size; + } + return offsets; +} + DisplayList::DisplayList(DisplayListStorage&& storage, size_t byte_count, uint32_t op_count, @@ -44,6 +59,7 @@ DisplayList::DisplayList(DisplayListStorage&& storage, bool root_is_unbounded, sk_sp rtree) : storage_(std::move(storage)), + offsets_(MakeOffsets(storage_, byte_count)), byte_count_(byte_count), op_count_(op_count), nested_byte_count_(nested_byte_count), @@ -73,86 +89,114 @@ uint32_t DisplayList::next_unique_id() { return id; } -class Culler { - public: - virtual ~Culler() = default; - virtual bool init(DispatchContext& context) = 0; - virtual void update(DispatchContext& context) = 0; +struct SaveInfo { + SaveInfo(DlIndex previous_restore_index, bool save_was_needed) + : previous_restore_index(previous_restore_index), + save_was_needed(save_was_needed) {} + + DlIndex previous_restore_index; + bool save_was_needed; }; -class NopCuller final : public Culler { - public: - static NopCuller instance; - - ~NopCuller() = default; - - 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 DisplayList::RTreeResultsToIndexVector( + std::vector& indices, + const std::vector& rtree_results) const { + FML_DCHECK(rtree_); + auto cur_rect = rtree_results.begin(); + auto end_rect = rtree_results.end(); + if (cur_rect >= end_rect) { + return; } - void update(DispatchContext& context) override {} -}; -NopCuller NopCuller::instance = NopCuller(); -class VectorCuller final : public Culler { - public: - VectorCuller(const DlRTree* rtree, const std::vector& rect_indices) - : rtree_(rtree), cur_(rect_indices.begin()), end_(rect_indices.end()) {} - - ~VectorCuller() = default; - - 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; + DlIndex next_render_index = rtree_->id(*cur_rect++); + DlIndex next_restore_index = std::numeric_limits::max(); + std::vector save_infos; + for (DlIndex index = 0u; index < offsets_.size(); index++) { + while (index > next_render_index) { + if (cur_rect < end_rect) { + next_render_index = rtree_->id(*cur_rect++); + } else { + // Nothing left to render. + // Nothing left to do, but match our restores from the stack. + while (!save_infos.empty()) { + SaveInfo& info = save_infos.back(); + // stack top boolean tells us whether the local variable + // next_restore_index should be executed. The local variable + // then gets reset to the value stored in the stack top + if (info.save_was_needed) { + FML_DCHECK(next_restore_index < offsets_.size()); + indices.push_back(next_restore_index); + } + next_restore_index = info.previous_restore_index; + save_infos.pop_back(); + } + return; + } } - } - 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; + const uint8_t* ptr = storage_.get() + offsets_[index]; + const DLOp* op = reinterpret_cast(ptr); + switch (GetOpCategory(op->type)) { + case DisplayListOpCategory::kAttribute: + // Attributes are always needed + indices.push_back(index); + break; + + case DisplayListOpCategory::kTransform: + case DisplayListOpCategory::kClip: + if (next_render_index < next_restore_index) { + indices.push_back(index); + } + break; + + case DisplayListOpCategory::kRendering: + case DisplayListOpCategory::kSubDisplayList: + if (index == next_render_index) { + indices.push_back(index); + } + break; + + case DisplayListOpCategory::kSave: + case DisplayListOpCategory::kSaveLayer: { + bool needed = (next_render_index < next_restore_index); + save_infos.emplace_back(next_restore_index, needed); + switch (op->type) { + case DisplayListOpType::kSave: + case DisplayListOpType::kSaveLayer: + case DisplayListOpType::kSaveLayerBackdrop: + next_restore_index = + static_cast(op)->restore_index; + break; + default: + FML_UNREACHABLE(); + } + if (needed) { + indices.push_back(index); } + break; } - context.next_render_index = std::numeric_limits::max(); + + case DisplayListOpCategory::kRestore: { + FML_DCHECK(!save_infos.empty()); + FML_DCHECK(index == next_restore_index); + SaveInfo& info = save_infos.back(); + next_restore_index = info.previous_restore_index; + if (info.save_was_needed) { + indices.push_back(index); + } + save_infos.pop_back(); + break; + } + + case DisplayListOpCategory::kInvalidCategory: + FML_UNREACHABLE(); } } - - private: - const DlRTree* rtree_; - std::vector::const_iterator cur_; - std::vector::const_iterator end_; -}; +} void DisplayList::Dispatch(DlOpReceiver& receiver) const { - const uint8_t* ptr = storage_.get(); - Dispatch(receiver, ptr, ptr + byte_count_, NopCuller::instance); + const uint8_t* base = storage_.get(); + for (size_t offset : offsets_) { + DispatchOneOp(receiver, base + offset); + } } void DisplayList::Dispatch(DlOpReceiver& receiver, @@ -167,52 +211,36 @@ void DisplayList::Dispatch(DlOpReceiver& receiver, } if (!has_rtree() || cull_rect.contains(bounds())) { Dispatch(receiver); - return; + } else { + auto op_indices = GetCulledIndices(cull_rect); + const uint8_t* base = storage_.get(); + for (DlIndex index : op_indices) { + DispatchOneOp(receiver, base + offsets_[index]); + } } - const DlRTree* rtree = this->rtree().get(); - FML_DCHECK(rtree != nullptr); - const uint8_t* ptr = storage_.get(); - std::vector rect_indices; - rtree->search(cull_rect, &rect_indices); - VectorCuller culler(rtree, rect_indices); - Dispatch(receiver, ptr, ptr + byte_count_, culler); } -void DisplayList::Dispatch(DlOpReceiver& receiver, - const uint8_t* ptr, - const uint8_t* end, - Culler& culler) const { - DispatchContext context = { - .receiver = receiver, - .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(context); \ +void DisplayList::DispatchOneOp(DlOpReceiver& receiver, + const uint8_t* ptr) const { + auto op = reinterpret_cast(ptr); + switch (op->type) { +#define DL_OP_DISPATCH(name) \ + case DisplayListOpType::k##name: \ + static_cast(op)->dispatch(receiver); \ break; - FOR_EACH_DISPLAY_LIST_OP(DL_OP_DISPATCH) + FOR_EACH_DISPLAY_LIST_OP(DL_OP_DISPATCH) + #ifdef IMPELLER_ENABLE_3D - DL_OP_DISPATCH(SetSceneColorSource) + DL_OP_DISPATCH(SetSceneColorSource) #endif // IMPELLER_ENABLE_3D #undef DL_OP_DISPATCH - default: - FML_DCHECK(false); - return; - } - culler.update(context); + case DisplayListOpType::kInvalidOp: + default: + FML_DCHECK(false) << "Unrecognized op type: " + << static_cast(op->type); } } @@ -230,6 +258,7 @@ void DisplayList::DisposeOps(const uint8_t* ptr, const uint8_t* end) { break; FOR_EACH_DISPLAY_LIST_OP(DL_OP_DISPOSE) + #ifdef IMPELLER_ENABLE_3D DL_OP_DISPOSE(SetSceneColorSource) #endif // IMPELLER_ENABLE_3D @@ -242,6 +271,154 @@ void DisplayList::DisposeOps(const uint8_t* ptr, const uint8_t* end) { } } +DisplayListOpCategory DisplayList::GetOpCategory(DlIndex index) const { + return GetOpCategory(GetOpType(index)); +} + +DisplayListOpCategory DisplayList::GetOpCategory(DisplayListOpType type) { + switch (type) { + case DisplayListOpType::kSetAntiAlias: + case DisplayListOpType::kSetInvertColors: + case DisplayListOpType::kSetStrokeCap: + case DisplayListOpType::kSetStrokeJoin: + case DisplayListOpType::kSetStyle: + case DisplayListOpType::kSetStrokeWidth: + case DisplayListOpType::kSetStrokeMiter: + case DisplayListOpType::kSetColor: + case DisplayListOpType::kSetBlendMode: + case DisplayListOpType::kClearColorFilter: + case DisplayListOpType::kSetPodColorFilter: + case DisplayListOpType::kClearColorSource: + case DisplayListOpType::kSetPodColorSource: + case DisplayListOpType::kSetImageColorSource: + case DisplayListOpType::kSetRuntimeEffectColorSource: + case DisplayListOpType::kClearImageFilter: + case DisplayListOpType::kSetPodImageFilter: + case DisplayListOpType::kSetSharedImageFilter: + case DisplayListOpType::kClearMaskFilter: + case DisplayListOpType::kSetPodMaskFilter: +#ifdef IMPELLER_ENABLE_3D + case DisplayListOpType::kSetSceneColorSource: +#endif // IMPELLER_ENABLE_3D + return DisplayListOpCategory::kAttribute; + + case DisplayListOpType::kSave: + return DisplayListOpCategory::kSave; + case DisplayListOpType::kSaveLayer: + case DisplayListOpType::kSaveLayerBackdrop: + return DisplayListOpCategory::kSaveLayer; + case DisplayListOpType::kRestore: + return DisplayListOpCategory::kRestore; + + case DisplayListOpType::kTranslate: + case DisplayListOpType::kScale: + case DisplayListOpType::kRotate: + case DisplayListOpType::kSkew: + case DisplayListOpType::kTransform2DAffine: + case DisplayListOpType::kTransformFullPerspective: + case DisplayListOpType::kTransformReset: + return DisplayListOpCategory::kTransform; + + case DisplayListOpType::kClipIntersectRect: + case DisplayListOpType::kClipIntersectOval: + case DisplayListOpType::kClipIntersectRRect: + case DisplayListOpType::kClipIntersectPath: + case DisplayListOpType::kClipDifferenceRect: + case DisplayListOpType::kClipDifferenceOval: + case DisplayListOpType::kClipDifferenceRRect: + case DisplayListOpType::kClipDifferencePath: + return DisplayListOpCategory::kClip; + + case DisplayListOpType::kDrawPaint: + case DisplayListOpType::kDrawColor: + case DisplayListOpType::kDrawLine: + case DisplayListOpType::kDrawDashedLine: + case DisplayListOpType::kDrawRect: + case DisplayListOpType::kDrawOval: + case DisplayListOpType::kDrawCircle: + case DisplayListOpType::kDrawRRect: + case DisplayListOpType::kDrawDRRect: + case DisplayListOpType::kDrawArc: + case DisplayListOpType::kDrawPath: + case DisplayListOpType::kDrawPoints: + case DisplayListOpType::kDrawLines: + case DisplayListOpType::kDrawPolygon: + case DisplayListOpType::kDrawVertices: + case DisplayListOpType::kDrawImage: + case DisplayListOpType::kDrawImageWithAttr: + case DisplayListOpType::kDrawImageRect: + case DisplayListOpType::kDrawImageNine: + case DisplayListOpType::kDrawImageNineWithAttr: + case DisplayListOpType::kDrawAtlas: + case DisplayListOpType::kDrawAtlasCulled: + case DisplayListOpType::kDrawTextBlob: + case DisplayListOpType::kDrawTextFrame: + case DisplayListOpType::kDrawShadow: + case DisplayListOpType::kDrawShadowTransparentOccluder: + return DisplayListOpCategory::kRendering; + + case DisplayListOpType::kDrawDisplayList: + return DisplayListOpCategory::kSubDisplayList; + + case DisplayListOpType::kInvalidOp: + return DisplayListOpCategory::kInvalidCategory; + } +} + +DisplayListOpType DisplayList::GetOpType(DlIndex index) const { + // Assert unsigned type so we can eliminate >= 0 comparison + static_assert(std::is_unsigned_v); + if (index >= offsets_.size()) { + return DisplayListOpType::kInvalidOp; + } + + size_t offset = offsets_[index]; + FML_DCHECK(offset < byte_count_); + auto ptr = storage_.get() + offset; + auto op = reinterpret_cast(ptr); + FML_DCHECK(ptr + op->size <= storage_.get() + byte_count_); + return op->type; +} + +static void FillAllIndices(std::vector& indices, DlIndex size) { + indices.reserve(size); + for (DlIndex i = 0u; i < size; i++) { + indices.push_back(i); + } +} + +std::vector DisplayList::GetCulledIndices( + const SkRect& cull_rect) const { + std::vector indices; + if (!cull_rect.isEmpty()) { + if (rtree_) { + std::vector rect_indices; + rtree_->search(cull_rect, &rect_indices); + RTreeResultsToIndexVector(indices, rect_indices); + } else { + FillAllIndices(indices, offsets_.size()); + } + } + return indices; +} + +bool DisplayList::Dispatch(DlOpReceiver& receiver, DlIndex index) const { + // Assert unsigned type so we can eliminate >= 0 comparison + static_assert(std::is_unsigned_v); + if (index >= offsets_.size()) { + return false; + } + + size_t offset = offsets_[index]; + FML_DCHECK(offset < byte_count_); + auto ptr = storage_.get() + offset; + FML_DCHECK(offset + reinterpret_cast(ptr)->size <= byte_count_); + + DispatchOneOp(receiver, ptr); + + return true; +} + static bool CompareOps(const uint8_t* ptrA, const uint8_t* endA, const uint8_t* ptrB, @@ -270,6 +447,7 @@ static bool CompareOps(const uint8_t* ptrA, break; FOR_EACH_DISPLAY_LIST_OP(DL_OP_EQUALS) + #ifdef IMPELLER_ENABLE_3D DL_OP_EQUALS(SetSceneColorSource) #endif // IMPELLER_ENABLE_3D diff --git a/display_list/display_list.h b/display_list/display_list.h index 5c3ef29625988..303cb581ab5db 100644 --- a/display_list/display_list.h +++ b/display_list/display_list.h @@ -142,12 +142,30 @@ namespace flutter { #define DL_OP_TO_ENUM_VALUE(name) k##name, enum class DisplayListOpType { FOR_EACH_DISPLAY_LIST_OP(DL_OP_TO_ENUM_VALUE) + #ifdef IMPELLER_ENABLE_3D DL_OP_TO_ENUM_VALUE(SetSceneColorSource) #endif // IMPELLER_ENABLE_3D + + // empty comment to make formatter happy + kInvalidOp, + kMaxOp = kInvalidOp, }; #undef DL_OP_TO_ENUM_VALUE +enum class DisplayListOpCategory { + kAttribute, + kTransform, + kClip, + kSave, + kSaveLayer, + kRestore, + kRendering, + kSubDisplayList, + kInvalidCategory, + kMaxCategory = kInvalidCategory, +}; + class DlOpReceiver; class DisplayListBuilder; @@ -270,7 +288,7 @@ class DisplayListStorage { std::unique_ptr ptr_; }; -class Culler; +using DlIndex = uint32_t; // The base class that contains a sequence of rendering operations // for dispatch to a DlOpReceiver. These objects must be instantiated @@ -360,6 +378,158 @@ 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_; } + /// @brief Iterator utility class used for the |DisplayList::begin| + /// and |DisplayList::end| methods. It implements just the + /// basic methods to enable iteration-style for loops. + class Iterator { + public: + DlIndex operator*() const { return value_; } + bool operator!=(const Iterator& other) { return value_ != other.value_; } + Iterator& operator++() { + value_++; + return *this; + } + + private: + explicit Iterator(DlIndex value) : value_(value) {} + + DlIndex value_; + + friend class DisplayList; + }; + + /// @brief Return the number of stored records in the DisplayList. + /// + /// Each stored record represents a dispatchable operation that will be + /// sent to a |DlOpReceiver| by the |Dispatch| method. You can directly + /// simulate the |Dispatch| method using a simple for loop on the indices: + /// + /// { + /// for (DlIndex i = 0u; i < display_list->GetRecordCount(); i++) { + /// display_list->Dispatch(my_receiver, i); + /// } + /// } + /// + /// @see |Dispatch(receiver, index)| + /// @see |begin| + /// @see |end| + /// @see |GetCulledIndices| + DlIndex GetRecordCount() const { return offsets_.size(); } + + /// @brief Return an iterator to the start of the stored records, + /// enabling the iteration form of a for loop. + /// + /// Each stored record represents a dispatchable operation that will be + /// sent to a |DlOpReceiver| by the |Dispatch| method. You can directly + /// simulate the |Dispatch| method using a simple for loop on the indices: + /// + /// { + /// for (DlIndex i : *display_list) { + /// display_list->Dispatch(my_receiver, i); + /// } + /// } + /// + /// @see |end| + /// @see |GetCulledIndices| + Iterator begin() const { return Iterator(0u); } + + /// @brief Return an iterator to the end of the stored records, + /// enabling the iteration form of a for loop. + /// + /// Each stored record represents a dispatchable operation that will be + /// sent to a |DlOpReceiver| by the |Dispatch| method. You can directly + /// simulate the |Dispatch| method using a simple for loop on the indices: + /// + /// { + /// for (DlIndex i : *display_list) { + /// display_list->Dispatch(my_receiver, i); + /// } + /// } + /// + /// @see |begin| + /// @see |GetCulledIndices| + Iterator end() const { return Iterator(offsets_.size()); } + + /// @brief Dispatch a single stored operation by its index. + /// + /// Each stored record represents a dispatchable operation that will be + /// sent to a |DlOpReceiver| by the |Dispatch| method. You can use this + /// method to dispatch a single operation to your receiver with an index + /// between |0u| (inclusive) and |GetRecordCount()| (exclusive), as in: + /// + /// { + /// for (DlIndex i = 0u; i < display_list->GetRecordCount(); i++) { + /// display_list->Dispatch(my_receiver, i); + /// } + /// } + /// + /// If the index is out of the range of the stored records, this method + /// will not call any methods on the receiver and return false. You can + /// check the return value for true if you want to make sure you are + /// using valid indices. + /// + /// @see |GetRecordCount| + /// @see |begin| + /// @see |end| + /// @see |GetCulledIndices| + bool Dispatch(DlOpReceiver& receiver, DlIndex index) const; + + /// @brief Return an enum describing the specific op type stored at + /// the indicated index. + /// + /// The specific types of the records are subject to change without notice + /// as the DisplayList code is developed and optimized. These values are + /// useful mostly for debugging purposes and should not be used in + /// production code. + /// + /// @see |GetOpCategory| for a more stable description of the records + DisplayListOpType GetOpType(DlIndex index) const; + + /// @brief Return an enum describing the general category of the + /// operation record stored at the indicated index. + /// + /// The categories are general and stable and can be used fairly safely + /// in production code to plan how to dispatch or reorder ops during + /// final rendering. + /// + /// @see |GetOpType| for a more detailed description of the records + /// primarily for debugging use + DisplayListOpCategory GetOpCategory(DlIndex index) const; + + /// @brief Return an enum describing the general category of the + /// operation record with the given type. + /// + /// @see |GetOpType| for a more detailed description of the records + /// primarily for debugging use + static DisplayListOpCategory GetOpCategory(DisplayListOpType type); + + /// @brief Return a vector of valid indices for records stored in + /// the DisplayList that must be dispatched if you are + /// restricted to the indicated cull_rect. + /// + /// This method can be used along with indexed dispatching to implement + /// RTree culling while still maintaining control over planning of + /// operations to be rendered, as in: + /// + /// { + /// std::vector indices = + /// display_list->GetCulledIndices(cull-rect); + /// for (DlIndex i : indices) { + /// display_list->Dispatch(my_receiver, i); + /// } + /// } + /// + /// The indices returned in the vector will automatically deal with + /// including or culling related operations such as attributes, clips + /// and transforms that will provide state for any rendering operations + /// selected by the culling checks. + /// + /// @see |GetOpType| for a more detailed description of the records + /// primarily for debugging use + /// + /// @see |Dispatch(receiver, index)| + std::vector GetCulledIndices(const SkRect& cull_rect) const; + private: DisplayList(DisplayListStorage&& ptr, size_t byte_count, @@ -381,6 +551,7 @@ class DisplayList : public SkRefCnt { static void DisposeOps(const uint8_t* ptr, const uint8_t* end); const DisplayListStorage storage_; + const std::vector offsets_; const size_t byte_count_; const uint32_t op_count_; @@ -401,10 +572,10 @@ class DisplayList : public SkRefCnt { const sk_sp rtree_; - void Dispatch(DlOpReceiver& ctx, - const uint8_t* ptr, - const uint8_t* end, - Culler& culler) const; + void DispatchOneOp(DlOpReceiver& receiver, const uint8_t* ptr) const; + + void RTreeResultsToIndexVector(std::vector& indices, + const std::vector& rtree_results) const; friend class DisplayListBuilder; }; diff --git a/display_list/display_list_unittests.cc b/display_list/display_list_unittests.cc index f09844d25c14b..0207d69333803 100644 --- a/display_list/display_list_unittests.cc +++ b/display_list/display_list_unittests.cc @@ -253,6 +253,70 @@ TEST_F(DisplayListTest, EmptyRebuild) { ASSERT_TRUE(dl2->Equals(dl3)); } +TEST_F(DisplayListTest, GeneralReceiverInitialValues) { + DisplayListGeneralReceiver receiver; + + EXPECT_EQ(receiver.GetOpsReceived(), 0u); + + auto max_type = static_cast(DisplayListOpType::kMaxOp); + for (int i = 0; i <= max_type; i++) { + DisplayListOpType type = static_cast(i); + EXPECT_EQ(receiver.GetOpsReceived(type), 0u) << type; + } + + auto max_category = static_cast(DisplayListOpCategory::kMaxCategory); + for (int i = 0; i <= max_category; i++) { + DisplayListOpCategory category = static_cast(i); + EXPECT_EQ(receiver.GetOpsReceived(category), 0u) << category; + } +} + +TEST_F(DisplayListTest, Iteration) { + DisplayListBuilder builder; + builder.DrawRect({10, 10, 20, 20}, DlPaint()); + auto dl = builder.Build(); + for (DlIndex i : *dl) { + EXPECT_EQ(dl->GetOpType(i), DisplayListOpType::kDrawRect) // + << "at " << i; + EXPECT_EQ(dl->GetOpCategory(i), DisplayListOpCategory::kRendering) + << "at " << i; + } +} + +TEST_F(DisplayListTest, InvalidIndices) { + DisplayListBuilder builder; + builder.DrawRect(kTestBounds, DlPaint()); + auto dl = builder.Build(); + DisplayListGeneralReceiver receiver; + + EXPECT_FALSE(dl->Dispatch(receiver, -1)); + EXPECT_FALSE(dl->Dispatch(receiver, dl->GetRecordCount())); + EXPECT_EQ(dl->GetOpType(-1), DisplayListOpType::kInvalidOp); + EXPECT_EQ(dl->GetOpType(dl->GetRecordCount()), DisplayListOpType::kInvalidOp); + EXPECT_EQ(dl->GetOpCategory(-1), DisplayListOpCategory::kInvalidCategory); + EXPECT_EQ(dl->GetOpCategory(dl->GetRecordCount()), + DisplayListOpCategory::kInvalidCategory); + EXPECT_EQ(dl->GetOpCategory(-1), DisplayListOpCategory::kInvalidCategory); + EXPECT_EQ(dl->GetOpCategory(DisplayListOpType::kInvalidOp), + DisplayListOpCategory::kInvalidCategory); + EXPECT_EQ(dl->GetOpCategory(DisplayListOpType::kMaxOp), + DisplayListOpCategory::kInvalidCategory); + EXPECT_EQ(receiver.GetOpsReceived(), 0u); +} + +TEST_F(DisplayListTest, ValidIndices) { + DisplayListBuilder builder; + builder.DrawRect(kTestBounds, DlPaint()); + auto dl = builder.Build(); + DisplayListGeneralReceiver receiver; + + EXPECT_EQ(dl->GetRecordCount(), 1u); + EXPECT_TRUE(dl->Dispatch(receiver, 0u)); + EXPECT_EQ(dl->GetOpType(0u), DisplayListOpType::kDrawRect); + EXPECT_EQ(dl->GetOpCategory(0u), DisplayListOpCategory::kRendering); + EXPECT_EQ(receiver.GetOpsReceived(), 1u); +} + TEST_F(DisplayListTest, BuilderCanBeReused) { DisplayListBuilder builder(kTestBounds); builder.DrawRect(kTestBounds, DlPaint()); @@ -678,6 +742,38 @@ TEST_F(DisplayListTest, SingleOpDisplayListsRecapturedAreEqual) { group.op_name + "(variant " + std::to_string(i + 1) + " == copy)"; ASSERT_TRUE(DisplayListsEQ_Verbose(dl, copy)); ASSERT_EQ(copy->op_count(false), dl->op_count(false)) << desc; + ASSERT_EQ(copy->GetRecordCount(), dl->GetRecordCount()); + ASSERT_EQ(copy->bytes(false), dl->bytes(false)) << desc; + ASSERT_EQ(copy->op_count(true), dl->op_count(true)) << desc; + ASSERT_EQ(copy->bytes(true), dl->bytes(true)) << desc; + ASSERT_EQ(copy->total_depth(), dl->total_depth()) << desc; + ASSERT_EQ(copy->bounds(), dl->bounds()) << desc; + ASSERT_TRUE(copy->Equals(*dl)) << desc; + ASSERT_TRUE(dl->Equals(*copy)) << desc; + } + } +} + +TEST_F(DisplayListTest, SingleOpDisplayListsRecapturedByIndexAreEqual) { + for (auto& group : allGroups) { + for (size_t i = 0; i < group.variants.size(); i++) { + sk_sp dl = Build(group.variants[i]); + // Verify recapturing the replay of the display list is Equals() + // when dispatching directly from the DL to another builder + DisplayListBuilder copy_builder; + DlOpReceiver& r = ToReceiver(copy_builder); + for (DlIndex i = 0; i < dl->GetRecordCount(); i++) { + EXPECT_NE(dl->GetOpType(i), DisplayListOpType::kInvalidOp); + EXPECT_NE(dl->GetOpCategory(i), + DisplayListOpCategory::kInvalidCategory); + EXPECT_TRUE(dl->Dispatch(r, i)); + } + sk_sp copy = copy_builder.Build(); + auto desc = + group.op_name + "(variant " + std::to_string(i + 1) + " == copy)"; + ASSERT_TRUE(DisplayListsEQ_Verbose(dl, copy)); + ASSERT_EQ(copy->op_count(false), dl->op_count(false)) << desc; + ASSERT_EQ(copy->GetRecordCount(), dl->GetRecordCount()); ASSERT_EQ(copy->bytes(false), dl->bytes(false)) << desc; ASSERT_EQ(copy->op_count(true), dl->op_count(true)) << desc; ASSERT_EQ(copy->bytes(true), dl->bytes(true)) << desc; @@ -3140,27 +3236,55 @@ TEST_F(DisplayListTest, RTreeOfClippedSaveLayerFilterScene) { } TEST_F(DisplayListTest, RTreeRenderCulling) { + SkRect rect1 = SkRect::MakeLTRB(0, 0, 10, 10); + SkRect rect2 = SkRect::MakeLTRB(20, 0, 30, 10); + SkRect rect3 = SkRect::MakeLTRB(0, 20, 10, 30); + SkRect rect4 = SkRect::MakeLTRB(20, 20, 30, 30); + DlPaint paint1 = DlPaint().setColor(DlColor::kRed()); + DlPaint paint2 = DlPaint().setColor(DlColor::kGreen()); + DlPaint paint3 = DlPaint().setColor(DlColor::kBlue()); + DlPaint paint4 = DlPaint().setColor(DlColor::kMagenta()); + DisplayListBuilder main_builder(true); - DlOpReceiver& main_receiver = ToReceiver(main_builder); - main_receiver.drawRect({0, 0, 10, 10}); - main_receiver.drawRect({20, 0, 30, 10}); - main_receiver.drawRect({0, 20, 10, 30}); - main_receiver.drawRect({20, 20, 30, 30}); + main_builder.DrawRect(rect1, paint1); + main_builder.DrawRect(rect2, paint2); + main_builder.DrawRect(rect3, paint3); + main_builder.DrawRect(rect4, paint4); auto main = main_builder.Build(); - auto test = [main](SkIRect cull_rect, const sk_sp& expected) { + auto test = [main](SkIRect cull_rect, const sk_sp& expected, + const std::string& label) { + SkRect cull_rectf = SkRect::Make(cull_rect); + { // Test SkIRect culling DisplayListBuilder culling_builder; main->Dispatch(ToReceiver(culling_builder), cull_rect); - EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected)); + EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected)) + << "using cull rect " << cull_rect // + << " where " << label; } { // Test SkRect culling DisplayListBuilder culling_builder; - main->Dispatch(ToReceiver(culling_builder), SkRect::Make(cull_rect)); + main->Dispatch(ToReceiver(culling_builder), cull_rectf); + + EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected)) + << "using cull rect " << cull_rectf // + << " where " << label; + } + + { // Test using vector of culled indices + DisplayListBuilder culling_builder; + DlOpReceiver& receiver = ToReceiver(culling_builder); + auto indices = main->GetCulledIndices(cull_rectf); + for (DlIndex i : indices) { + EXPECT_TRUE(main->Dispatch(receiver, i)); + } - EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected)); + EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected)) + << "using culled indices on cull rect " << cull_rectf // + << " where " << label; } }; @@ -3170,57 +3294,62 @@ TEST_F(DisplayListTest, RTreeRenderCulling) { DisplayListBuilder expected_builder; auto expected = expected_builder.Build(); - test(cull_rect, expected); + test(cull_rect, expected, "no rects intersect"); } { // Rect 1 SkIRect cull_rect = {9, 9, 19, 19}; DisplayListBuilder expected_builder; - DlOpReceiver& expected_receiver = ToReceiver(expected_builder); - expected_receiver.drawRect({0, 0, 10, 10}); + expected_builder.DrawRect(rect1, paint1); auto expected = expected_builder.Build(); - test(cull_rect, expected); + test(cull_rect, expected, "rect 1 intersects"); } { // Rect 2 SkIRect cull_rect = {11, 9, 21, 19}; DisplayListBuilder expected_builder; - DlOpReceiver& expected_receiver = ToReceiver(expected_builder); - expected_receiver.drawRect({20, 0, 30, 10}); + // Unfortunately we don't cull attribute records (yet?) until the last op + ToReceiver(expected_builder).setColor(paint1.getColor()); + expected_builder.DrawRect(rect2, paint2); auto expected = expected_builder.Build(); - test(cull_rect, expected); + test(cull_rect, expected, "rect 2 intersects"); } { // Rect 3 SkIRect cull_rect = {9, 11, 19, 21}; DisplayListBuilder expected_builder; - DlOpReceiver& expected_receiver = ToReceiver(expected_builder); - expected_receiver.drawRect({0, 20, 10, 30}); + // Unfortunately we don't cull attribute records (yet?) until the last op + ToReceiver(expected_builder).setColor(paint1.getColor()); + ToReceiver(expected_builder).setColor(paint2.getColor()); + expected_builder.DrawRect(rect3, paint3); auto expected = expected_builder.Build(); - test(cull_rect, expected); + test(cull_rect, expected, "rect 3 intersects"); } { // Rect 4 SkIRect cull_rect = {11, 11, 21, 21}; DisplayListBuilder expected_builder; - DlOpReceiver& expected_receiver = ToReceiver(expected_builder); - expected_receiver.drawRect({20, 20, 30, 30}); + // Unfortunately we don't cull attribute records (yet?) until the last op + ToReceiver(expected_builder).setColor(paint1.getColor()); + ToReceiver(expected_builder).setColor(paint2.getColor()); + ToReceiver(expected_builder).setColor(paint3.getColor()); + expected_builder.DrawRect(rect4, paint4); auto expected = expected_builder.Build(); - test(cull_rect, expected); + test(cull_rect, expected, "rect 4 intersects"); } { // All 4 rects SkIRect cull_rect = {9, 9, 21, 21}; - test(cull_rect, main); + test(cull_rect, main, "all rects intersect"); } } @@ -5713,5 +5842,82 @@ TEST_F(DisplayListTest, UnboundedRenderOpsAreReportedUnlessClipped) { 1); } +TEST_F(DisplayListTest, BackdropFilterCulledAlongsideClipAndTransform) { + SkRect frame_bounds = SkRect::MakeWH(100.0f, 100.0f); + SkRect frame_clip = frame_bounds.makeInset(0.5f, 0.5f); + + SkRect clip_rect = SkRect::MakeLTRB(40.0f, 40.0f, 60.0f, 60.0f); + SkRect draw_rect1 = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); + SkRect draw_rect2 = SkRect::MakeLTRB(45.0f, 20.0f, 55.0f, 55.0f); + SkRect cull_rect = SkRect::MakeLTRB(1.0f, 1.0f, 99.0f, 30.0f); + auto bdf_filter = DlBlurImageFilter::Make(5.0f, 5.0f, DlTileMode::kClamp); + + ASSERT_TRUE(frame_bounds.contains(clip_rect)); + ASSERT_TRUE(frame_bounds.contains(draw_rect1)); + ASSERT_TRUE(frame_bounds.contains(draw_rect2)); + ASSERT_TRUE(frame_bounds.contains(cull_rect)); + + ASSERT_TRUE(frame_clip.contains(clip_rect)); + ASSERT_TRUE(frame_clip.contains(draw_rect1)); + ASSERT_TRUE(frame_clip.contains(draw_rect2)); + ASSERT_TRUE(frame_clip.contains(cull_rect)); + + ASSERT_FALSE(clip_rect.intersects(draw_rect1)); + ASSERT_TRUE(clip_rect.intersects(draw_rect2)); + + ASSERT_FALSE(cull_rect.intersects(clip_rect)); + ASSERT_TRUE(cull_rect.intersects(draw_rect1)); + ASSERT_TRUE(cull_rect.intersects(draw_rect2)); + + DisplayListBuilder builder(frame_bounds, true); + builder.Save(); + { + builder.Translate(0.1f, 0.1f); + builder.ClipRect(frame_clip); + builder.DrawRect(draw_rect1, DlPaint()); + // Should all be culled below + builder.ClipRect(clip_rect); + builder.Translate(0.1f, 0.1f); + builder.SaveLayer(nullptr, nullptr, bdf_filter.get()); + { // + builder.DrawRect(clip_rect, DlPaint()); + } + builder.Restore(); + // End of culling + } + builder.Restore(); + builder.DrawRect(draw_rect2, DlPaint()); + auto display_list = builder.Build(); + + { + DisplayListBuilder unculled(frame_bounds); + display_list->Dispatch(ToReceiver(unculled), frame_bounds); + auto unculled_dl = unculled.Build(); + + EXPECT_TRUE(DisplayListsEQ_Verbose(display_list, unculled_dl)); + } + + { + DisplayListBuilder culled(frame_bounds); + display_list->Dispatch(ToReceiver(culled), cull_rect); + auto culled_dl = culled.Build(); + + EXPECT_TRUE(DisplayListsNE_Verbose(display_list, culled_dl)); + + DisplayListBuilder expected(frame_bounds); + expected.Save(); + { + expected.Translate(0.1f, 0.1f); + expected.ClipRect(frame_clip); + expected.DrawRect(draw_rect1, DlPaint()); + } + expected.Restore(); + expected.DrawRect(draw_rect2, DlPaint()); + auto expected_dl = expected.Build(); + + EXPECT_TRUE(DisplayListsEQ_Verbose(culled_dl, expected_dl)); + } +} + } // namespace testing } // namespace flutter diff --git a/display_list/dl_builder.h b/display_list/dl_builder.h index 29cc795aeb980..d4944d491dc54 100644 --- a/display_list/dl_builder.h +++ b/display_list/dl_builder.h @@ -510,7 +510,7 @@ class DisplayListBuilder final : public virtual DlCanvas, // Most rendering ops will use 1 depth value, but some attributes may // require an additional depth value (due to implicit saveLayers) uint32_t render_op_depth_cost_ = 1u; - int op_index_ = 0; + DlIndex op_index_ = 0; // bytes and ops from |drawPicture| and |drawDisplayList| size_t nested_bytes_ = 0; diff --git a/display_list/dl_op_records.h b/display_list/dl_op_records.h index aeb0379e2371f..2b5210abcbd51 100644 --- a/display_list/dl_op_records.h +++ b/display_list/dl_op_records.h @@ -18,47 +18,6 @@ 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 { - DlOpReceiver& receiver; - - 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. @@ -116,8 +75,8 @@ struct DLOp { \ const bool value; \ \ - void dispatch(DispatchContext& ctx) const { \ - ctx.receiver.set##name(value); \ + void dispatch(DlOpReceiver& receiver) const { \ + receiver.set##name(value); \ } \ }; DEFINE_SET_BOOL_OP(AntiAlias) @@ -133,8 +92,8 @@ DEFINE_SET_BOOL_OP(InvertColors) \ const DlStroke##name value; \ \ - void dispatch(DispatchContext& ctx) const { \ - ctx.receiver.setStroke##name(value); \ + void dispatch(DlOpReceiver& receiver) const { \ + receiver.setStroke##name(value); \ } \ }; DEFINE_SET_ENUM_OP(Cap) @@ -149,8 +108,8 @@ struct SetStyleOp final : DLOp { const DlDrawStyle style; - void dispatch(DispatchContext& ctx) const { - ctx.receiver.setDrawStyle(style); + void dispatch(DlOpReceiver& receiver) const { // + receiver.setDrawStyle(style); } }; // 4 byte header + 4 byte payload packs into minimum 8 bytes @@ -161,8 +120,8 @@ struct SetStrokeWidthOp final : DLOp { const float width; - void dispatch(DispatchContext& ctx) const { - ctx.receiver.setStrokeWidth(width); + void dispatch(DlOpReceiver& receiver) const { + receiver.setStrokeWidth(width); } }; // 4 byte header + 4 byte payload packs into minimum 8 bytes @@ -173,8 +132,8 @@ struct SetStrokeMiterOp final : DLOp { const float limit; - void dispatch(DispatchContext& ctx) const { - ctx.receiver.setStrokeMiter(limit); + void dispatch(DlOpReceiver& receiver) const { + receiver.setStrokeMiter(limit); } }; @@ -186,7 +145,7 @@ struct SetColorOp final : DLOp { const DlColor color; - void dispatch(DispatchContext& ctx) const { ctx.receiver.setColor(color); } + void dispatch(DlOpReceiver& receiver) const { receiver.setColor(color); } }; // 4 byte header + 4 byte payload packs into minimum 8 bytes struct SetBlendModeOp final : DLOp { @@ -196,8 +155,8 @@ struct SetBlendModeOp final : DLOp { const DlBlendMode mode; - void dispatch(DispatchContext& ctx) const { // - ctx.receiver.setBlendMode(mode); + void dispatch(DlOpReceiver& receiver) const { // + receiver.setBlendMode(mode); } }; @@ -213,8 +172,8 @@ struct SetBlendModeOp final : DLOp { \ Clear##name##Op() {} \ \ - void dispatch(DispatchContext& ctx) const { \ - ctx.receiver.set##name(nullptr); \ + void dispatch(DlOpReceiver& receiver) const { \ + receiver.set##name(nullptr); \ } \ }; \ struct SetPod##name##Op final : DLOp { \ @@ -222,9 +181,9 @@ struct SetBlendModeOp final : DLOp { \ SetPod##name##Op() {} \ \ - void dispatch(DispatchContext& ctx) const { \ + void dispatch(DlOpReceiver& receiver) const { \ const Dl##name* filter = reinterpret_cast(this + 1); \ - ctx.receiver.set##name(filter); \ + receiver.set##name(filter); \ } \ }; DEFINE_SET_CLEAR_DLATTR_OP(ColorFilter, ColorFilter, filter) @@ -247,8 +206,8 @@ struct SetImageColorSourceOp : DLOp { const DlImageColorSource source; - void dispatch(DispatchContext& ctx) const { - ctx.receiver.setColorSource(&source); + void dispatch(DlOpReceiver& receiver) const { + receiver.setColorSource(&source); } }; @@ -265,8 +224,8 @@ struct SetRuntimeEffectColorSourceOp : DLOp { const DlRuntimeEffectColorSource source; - void dispatch(DispatchContext& ctx) const { - ctx.receiver.setColorSource(&source); + void dispatch(DlOpReceiver& receiver) const { + receiver.setColorSource(&source); } DisplayListCompare equals(const SetRuntimeEffectColorSourceOp* other) const { @@ -284,8 +243,8 @@ struct SetSceneColorSourceOp : DLOp { const DlSceneColorSource source; - void dispatch(DispatchContext& ctx) const { - ctx.receiver.setColorSource(&source); + void dispatch(DlOpReceiver& receiver) const { + receiver.setColorSource(&source); } DisplayListCompare equals(const SetSceneColorSourceOp* other) const { @@ -304,8 +263,8 @@ struct SetSharedImageFilterOp : DLOp { const std::shared_ptr filter; - void dispatch(DispatchContext& ctx) const { - ctx.receiver.setImageFilter(filter.get()); + void dispatch(DlOpReceiver& receiver) const { + receiver.setImageFilter(filter.get()); } DisplayListCompare equals(const SetSharedImageFilterOp* other) const { @@ -330,15 +289,8 @@ struct SaveOpBase : DLOp { // of the data here, it can be stored for free and defaulted to 0 for // save operations. SaveLayerOptions options; - int restore_index; + DlIndex restore_index; uint32_t total_content_depth; - - 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; - } }; // 16 byte SaveOpBase with no additional data (options is unsed here) struct SaveOp final : SaveOpBase { @@ -346,10 +298,8 @@ struct SaveOp final : SaveOpBase { SaveOp() : SaveOpBase() {} - void dispatch(DispatchContext& ctx) const { - if (save_needed(ctx)) { - ctx.receiver.save(total_content_depth); - } + void dispatch(DlOpReceiver& receiver) const { + receiver.save(total_content_depth); } }; // The base struct for all saveLayer() ops @@ -369,11 +319,8 @@ struct SaveLayerOp final : SaveLayerOpBase { SaveLayerOp(const SaveLayerOptions& options, const SkRect& rect) : SaveLayerOpBase(options, rect) {} - void dispatch(DispatchContext& ctx) const { - if (save_needed(ctx)) { - ctx.receiver.saveLayer(rect, options, total_content_depth, - max_blend_mode); - } + void dispatch(DlOpReceiver& receiver) const { + receiver.saveLayer(rect, options, total_content_depth, max_blend_mode); } }; // 36 byte SaveLayerOpBase + 4 bytes for alignment + 16 byte payload packs @@ -388,11 +335,9 @@ struct SaveLayerBackdropOp final : SaveLayerOpBase { const std::shared_ptr backdrop; - void dispatch(DispatchContext& ctx) const { - if (save_needed(ctx)) { - ctx.receiver.saveLayer(rect, options, total_content_depth, max_blend_mode, - backdrop.get()); - } + void dispatch(DlOpReceiver& receiver) const { + receiver.saveLayer(rect, options, total_content_depth, max_blend_mode, + backdrop.get()); } DisplayListCompare equals(const SaveLayerBackdropOp* other) const { @@ -410,23 +355,14 @@ struct RestoreOp final : DLOp { RestoreOp() {} - void dispatch(DispatchContext& ctx) const { - DispatchContext::SaveInfo& info = ctx.save_infos.back(); - if (info.save_was_needed) { - ctx.receiver.restore(); - } - ctx.next_restore_index = info.previous_restore_index; - ctx.save_infos.pop_back(); + void dispatch(DlOpReceiver& receiver) const { // + receiver.restore(); } }; struct TransformClipOpBase : DLOp { static constexpr uint32_t kDepthInc = 0; static constexpr uint32_t kRenderOpInc = 1; - - 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) @@ -438,10 +374,8 @@ struct TranslateOp final : TransformClipOpBase { const SkScalar tx; const SkScalar ty; - void dispatch(DispatchContext& ctx) const { - if (op_needed(ctx)) { - ctx.receiver.translate(tx, ty); - } + void dispatch(DlOpReceiver& receiver) const { // + receiver.translate(tx, ty); } }; // 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes @@ -454,10 +388,8 @@ struct ScaleOp final : TransformClipOpBase { const SkScalar sx; const SkScalar sy; - void dispatch(DispatchContext& ctx) const { - if (op_needed(ctx)) { - ctx.receiver.scale(sx, sy); - } + void dispatch(DlOpReceiver& receiver) const { // + receiver.scale(sx, sy); } }; // 4 byte header + 4 byte payload packs into minimum 8 bytes @@ -468,10 +400,8 @@ struct RotateOp final : TransformClipOpBase { const SkScalar degrees; - void dispatch(DispatchContext& ctx) const { - if (op_needed(ctx)) { - ctx.receiver.rotate(degrees); - } + void dispatch(DlOpReceiver& receiver) const { // + receiver.rotate(degrees); } }; // 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes @@ -484,10 +414,8 @@ struct SkewOp final : TransformClipOpBase { const SkScalar sx; const SkScalar sy; - void dispatch(DispatchContext& ctx) const { - if (op_needed(ctx)) { - ctx.receiver.skew(sx, sy); - } + void dispatch(DlOpReceiver& receiver) const { // + receiver.skew(sx, sy); } }; // 4 byte header + 24 byte payload uses 28 bytes but is rounded up to 32 bytes @@ -504,11 +432,9 @@ struct Transform2DAffineOp final : TransformClipOpBase { const SkScalar mxx, mxy, mxt; const SkScalar myx, myy, myt; - void dispatch(DispatchContext& ctx) const { - if (op_needed(ctx)) { - ctx.receiver.transform2DAffine(mxx, mxy, mxt, // - myx, myy, myt); - } + void dispatch(DlOpReceiver& receiver) const { + receiver.transform2DAffine(mxx, mxy, mxt, // + myx, myy, myt); } }; // 4 byte header + 64 byte payload uses 68 bytes which is rounded up to 72 bytes @@ -533,13 +459,11 @@ struct TransformFullPerspectiveOp final : TransformClipOpBase { const SkScalar mzx, mzy, mzz, mzt; const SkScalar mwx, mwy, mwz, mwt; - void dispatch(DispatchContext& ctx) const { - if (op_needed(ctx)) { - ctx.receiver.transformFullPerspective(mxx, mxy, mxz, mxt, // - myx, myy, myz, myt, // - mzx, mzy, mzz, mzt, // - mwx, mwy, mwz, mwt); - } + void dispatch(DlOpReceiver& receiver) const { + receiver.transformFullPerspective(mxx, mxy, mxz, mxt, // + myx, myy, myz, myt, // + mzx, mzy, mzz, mzt, // + mwx, mwy, mwz, mwt); } }; @@ -549,10 +473,8 @@ struct TransformResetOp final : TransformClipOpBase { TransformResetOp() = default; - void dispatch(DispatchContext& ctx) const { - if (op_needed(ctx)) { - ctx.receiver.transformReset(); - } + void dispatch(DlOpReceiver& receiver) const { // + receiver.transformReset(); } }; @@ -576,11 +498,8 @@ struct TransformResetOp final : TransformClipOpBase { const bool is_aa; \ const Sk##shapetype shape; \ \ - void dispatch(DispatchContext& ctx) const { \ - if (op_needed(ctx)) { \ - ctx.receiver.clip##shapename(shape, DlCanvas::ClipOp::k##clipop, \ - is_aa); \ - } \ + void dispatch(DlOpReceiver& receiver) const { \ + receiver.clip##shapename(shape, DlCanvas::ClipOp::k##clipop, is_aa); \ } \ }; DEFINE_CLIP_SHAPE_OP(Rect, Rect, Intersect) @@ -591,33 +510,30 @@ DEFINE_CLIP_SHAPE_OP(Oval, Rect, Difference) DEFINE_CLIP_SHAPE_OP(RRect, RRect, Difference) #undef DEFINE_CLIP_SHAPE_OP -#define DEFINE_CLIP_PATH_OP(clipop) \ - struct Clip##clipop##PathOp final : TransformClipOpBase { \ - static constexpr auto kType = DisplayListOpType::kClip##clipop##Path; \ - \ - Clip##clipop##PathOp(const SkPath& path, bool is_aa) \ - : is_aa(is_aa), cached_path(path) {} \ - \ - const bool is_aa; \ - const DlOpReceiver::CacheablePath cached_path; \ - \ - void dispatch(DispatchContext& ctx) const { \ - if (op_needed(ctx)) { \ - if (ctx.receiver.PrefersImpellerPaths()) { \ - ctx.receiver.clipPath(cached_path, DlCanvas::ClipOp::k##clipop, \ - is_aa); \ - } else { \ - ctx.receiver.clipPath(cached_path.sk_path, \ - DlCanvas::ClipOp::k##clipop, is_aa); \ - } \ - } \ - } \ - \ - DisplayListCompare equals(const Clip##clipop##PathOp* other) const { \ - return is_aa == other->is_aa && cached_path == other->cached_path \ - ? DisplayListCompare::kEqual \ - : DisplayListCompare::kNotEqual; \ - } \ +#define DEFINE_CLIP_PATH_OP(clipop) \ + struct Clip##clipop##PathOp final : TransformClipOpBase { \ + static constexpr auto kType = DisplayListOpType::kClip##clipop##Path; \ + \ + Clip##clipop##PathOp(const SkPath& path, bool is_aa) \ + : is_aa(is_aa), cached_path(path) {} \ + \ + const bool is_aa; \ + const DlOpReceiver::CacheablePath cached_path; \ + \ + void dispatch(DlOpReceiver& receiver) const { \ + if (receiver.PrefersImpellerPaths()) { \ + receiver.clipPath(cached_path, DlCanvas::ClipOp::k##clipop, is_aa); \ + } else { \ + receiver.clipPath(cached_path.sk_path, DlCanvas::ClipOp::k##clipop, \ + is_aa); \ + } \ + } \ + \ + DisplayListCompare equals(const Clip##clipop##PathOp* other) const { \ + return is_aa == other->is_aa && cached_path == other->cached_path \ + ? DisplayListCompare::kEqual \ + : DisplayListCompare::kNotEqual; \ + } \ }; DEFINE_CLIP_PATH_OP(Intersect) DEFINE_CLIP_PATH_OP(Difference) @@ -626,10 +542,6 @@ DEFINE_CLIP_PATH_OP(Difference) struct DrawOpBase : DLOp { static constexpr uint32_t kDepthInc = 1; static constexpr uint32_t kRenderOpInc = 1; - - 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) @@ -638,10 +550,8 @@ struct DrawPaintOp final : DrawOpBase { DrawPaintOp() {} - void dispatch(DispatchContext& ctx) const { - if (op_needed(ctx)) { - ctx.receiver.drawPaint(); - } + void dispatch(DlOpReceiver& receiver) const { // + receiver.drawPaint(); } }; // 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes @@ -654,10 +564,8 @@ struct DrawColorOp final : DrawOpBase { const DlColor color; const DlBlendMode mode; - void dispatch(DispatchContext& ctx) const { - if (op_needed(ctx)) { - ctx.receiver.drawColor(color, mode); - } + void dispatch(DlOpReceiver& receiver) const { + receiver.drawColor(color, mode); } }; @@ -674,10 +582,8 @@ struct DrawColorOp final : DrawOpBase { \ const arg_type arg_name; \ \ - void dispatch(DispatchContext& ctx) const { \ - if (op_needed(ctx)) { \ - ctx.receiver.draw##op_name(arg_name); \ - } \ + void dispatch(DlOpReceiver& receiver) const { \ + receiver.draw##op_name(arg_name); \ } \ }; DEFINE_DRAW_1ARG_OP(Rect, SkRect, rect) @@ -694,13 +600,11 @@ struct DrawPathOp final : DrawOpBase { const DlOpReceiver::CacheablePath cached_path; - void dispatch(DispatchContext& ctx) const { - if (op_needed(ctx)) { - if (ctx.receiver.PrefersImpellerPaths()) { - ctx.receiver.drawPath(cached_path); - } else { - ctx.receiver.drawPath(cached_path.sk_path); - } + void dispatch(DlOpReceiver& receiver) const { + if (receiver.PrefersImpellerPaths()) { + receiver.drawPath(cached_path); + } else { + receiver.drawPath(cached_path.sk_path); } } @@ -726,10 +630,8 @@ struct DrawPathOp final : DrawOpBase { const type1 name1; \ const type2 name2; \ \ - void dispatch(DispatchContext& ctx) const { \ - if (op_needed(ctx)) { \ - ctx.receiver.draw##op_name(name1, name2); \ - } \ + void dispatch(DlOpReceiver& receiver) const { \ + receiver.draw##op_name(name1, name2); \ } \ }; DEFINE_DRAW_2ARG_OP(Line, SkPoint, p0, SkPoint, p1) @@ -752,10 +654,8 @@ struct DrawDashedLineOp final : DrawOpBase { const SkScalar on_length; const SkScalar off_length; - void dispatch(DispatchContext& ctx) const { - if (op_needed(ctx)) { - ctx.receiver.drawDashedLine(p0, p1, on_length, off_length); - } + void dispatch(DlOpReceiver& receiver) const { + receiver.drawDashedLine(p0, p1, on_length, off_length); } }; @@ -771,10 +671,8 @@ struct DrawArcOp final : DrawOpBase { const SkScalar sweep; const bool center; - void dispatch(DispatchContext& ctx) const { - if (op_needed(ctx)) { - ctx.receiver.drawArc(bounds, start, sweep, center); - } + void dispatch(DlOpReceiver& receiver) const { + receiver.drawArc(bounds, start, sweep, center); } }; @@ -784,20 +682,18 @@ struct DrawArcOp final : DrawOpBase { // 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 : DrawOpBase { \ - static constexpr 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.receiver.drawPoints(DlCanvas::PointMode::mode, count, pts); \ - } \ - } \ +#define DEFINE_DRAW_POINTS_OP(name, mode) \ + struct Draw##name##Op final : DrawOpBase { \ + static constexpr auto kType = DisplayListOpType::kDraw##name; \ + \ + explicit Draw##name##Op(uint32_t count) : count(count) {} \ + \ + const uint32_t count; \ + \ + void dispatch(DlOpReceiver& receiver) const { \ + const SkPoint* pts = reinterpret_cast(this + 1); \ + receiver.drawPoints(DlCanvas::PointMode::mode, count, pts); \ + } \ }; DEFINE_DRAW_POINTS_OP(Points, kPoints); DEFINE_DRAW_POINTS_OP(Lines, kLines); @@ -815,40 +711,36 @@ struct DrawVerticesOp final : DrawOpBase { const DlBlendMode mode; const std::shared_ptr vertices; - void dispatch(DispatchContext& ctx) const { - if (op_needed(ctx)) { - ctx.receiver.drawVertices(vertices, mode); - } + void dispatch(DlOpReceiver& receiver) const { + receiver.drawVertices(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 : DrawOpBase { \ - static constexpr 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.receiver.drawImage(image, point, sampling, with_attributes); \ - } \ - } \ - \ - DisplayListCompare equals(const name##Op* other) const { \ - return (point == other->point && sampling == other->sampling && \ - image->Equals(other->image)) \ - ? DisplayListCompare::kEqual \ - : DisplayListCompare::kNotEqual; \ - } \ +#define DEFINE_DRAW_IMAGE_OP(name, with_attributes) \ + struct name##Op final : DrawOpBase { \ + static constexpr 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(DlOpReceiver& receiver) const { \ + receiver.drawImage(image, point, sampling, with_attributes); \ + } \ + \ + DisplayListCompare equals(const name##Op* other) const { \ + return (point == other->point && sampling == other->sampling && \ + image->Equals(other->image)) \ + ? DisplayListCompare::kEqual \ + : DisplayListCompare::kNotEqual; \ + } \ }; DEFINE_DRAW_IMAGE_OP(DrawImage, false) DEFINE_DRAW_IMAGE_OP(DrawImageWithAttr, true) @@ -879,11 +771,9 @@ struct DrawImageRectOp final : DrawOpBase { const DlCanvas::SrcRectConstraint constraint; const sk_sp image; - void dispatch(DispatchContext& ctx) const { - if (op_needed(ctx)) { - ctx.receiver.drawImageRect(image, src, dst, sampling, - render_with_attributes, constraint); - } + void dispatch(DlOpReceiver& receiver) const { + receiver.drawImageRect(image, src, dst, sampling, render_with_attributes, + constraint); } DisplayListCompare equals(const DrawImageRectOp* other) const { @@ -912,11 +802,9 @@ struct DrawImageRectOp final : DrawOpBase { const DlFilterMode mode; \ const sk_sp image; \ \ - void dispatch(DispatchContext& ctx) const { \ - if (op_needed(ctx)) { \ - ctx.receiver.drawImageNine(image, center, dst, mode, \ - render_with_attributes); \ - } \ + void dispatch(DlOpReceiver& receiver) const { \ + receiver.drawImageNine(image, center, dst, mode, \ + render_with_attributes); \ } \ \ DisplayListCompare equals(const name##Op* other) const { \ @@ -994,16 +882,14 @@ struct DrawAtlasOp final : DrawAtlasBaseOp { has_colors, 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.receiver.drawAtlas(atlas, xform, tex, colors, count, mode, sampling, - nullptr, render_with_attributes); - } + void dispatch(DlOpReceiver& receiver) 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); + receiver.drawAtlas(atlas, xform, tex, colors, count, mode, sampling, + nullptr, render_with_attributes); } DisplayListCompare equals(const DrawAtlasOp* other) const { @@ -1039,16 +925,14 @@ struct DrawAtlasCulledOp final : DrawAtlasBaseOp { const SkRect cull_rect; - 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.receiver.drawAtlas(atlas, xform, tex, colors, count, mode, sampling, - &cull_rect, render_with_attributes); - } + void dispatch(DlOpReceiver& receiver) 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); + receiver.drawAtlas(atlas, xform, tex, colors, count, mode, sampling, + &cull_rect, render_with_attributes); } DisplayListCompare equals(const DrawAtlasCulledOp* other) const { @@ -1073,10 +957,8 @@ struct DrawDisplayListOp final : DrawOpBase { SkScalar opacity; const sk_sp display_list; - void dispatch(DispatchContext& ctx) const { - if (op_needed(ctx)) { - ctx.receiver.drawDisplayList(display_list, opacity); - } + void dispatch(DlOpReceiver& receiver) const { + receiver.drawDisplayList(display_list, opacity); } DisplayListCompare equals(const DrawDisplayListOp* other) const { @@ -1099,10 +981,8 @@ struct DrawTextBlobOp final : DrawOpBase { const SkScalar y; const sk_sp blob; - void dispatch(DispatchContext& ctx) const { - if (op_needed(ctx)) { - ctx.receiver.drawTextBlob(blob, x, y); - } + void dispatch(DlOpReceiver& receiver) const { + receiver.drawTextBlob(blob, x, y); } }; @@ -1118,10 +998,8 @@ struct DrawTextFrameOp final : DrawOpBase { const SkScalar y; const std::shared_ptr text_frame; - void dispatch(DispatchContext& ctx) const { - if (op_needed(ctx)) { - ctx.receiver.drawTextFrame(text_frame, x, y); - } + void dispatch(DlOpReceiver& receiver) const { + receiver.drawTextFrame(text_frame, x, y); } }; @@ -1141,15 +1019,13 @@ struct DrawTextFrameOp final : DrawOpBase { const SkScalar dpr; \ const DlOpReceiver::CacheablePath cached_path; \ \ - void dispatch(DispatchContext& ctx) const { \ - if (op_needed(ctx)) { \ - if (ctx.receiver.PrefersImpellerPaths()) { \ - ctx.receiver.drawShadow(cached_path, color, elevation, \ - transparent_occluder, dpr); \ - } else { \ - ctx.receiver.drawShadow(cached_path.sk_path, color, elevation, \ - transparent_occluder, dpr); \ - } \ + void dispatch(DlOpReceiver& receiver) const { \ + if (receiver.PrefersImpellerPaths()) { \ + receiver.drawShadow(cached_path, color, elevation, \ + transparent_occluder, dpr); \ + } else { \ + receiver.drawShadow(cached_path.sk_path, color, elevation, \ + transparent_occluder, dpr); \ } \ } \ \ diff --git a/testing/display_list_testing.cc b/testing/display_list_testing.cc index 1ab19357f8139..0663aea860de9 100644 --- a/testing/display_list_testing.cc +++ b/testing/display_list_testing.cc @@ -55,6 +55,8 @@ using DlVertexMode = flutter::DlVertexMode; using DlTileMode = flutter::DlTileMode; using DlImageSampling = flutter::DlImageSampling; using SaveLayerOptions = flutter::SaveLayerOptions; +using DisplayListOpType = flutter::DisplayListOpType; +using DisplayListOpCategory = flutter::DisplayListOpCategory; using DisplayListStreamDispatcher = flutter::testing::DisplayListStreamDispatcher; @@ -99,43 +101,83 @@ std::ostream& operator<<(std::ostream& os, const DlPaint& paint) { return os << ")"; } +#define DLT_OSTREAM_CASE(enum_name, value_name) \ + case enum_name::k##value_name: return os << #enum_name "::k" #value_name + std::ostream& operator<<(std::ostream& os, const DlBlendMode& mode) { switch (mode) { - case DlBlendMode::kClear: return os << "BlendMode::kClear"; - case DlBlendMode::kSrc: return os << "BlendMode::kSrc"; - case DlBlendMode::kDst: return os << "BlendMode::kDst"; - case DlBlendMode::kSrcOver: return os << "BlendMode::kSrcOver"; - case DlBlendMode::kDstOver: return os << "BlendMode::kDstOver"; - case DlBlendMode::kSrcIn: return os << "BlendMode::kSrcIn"; - case DlBlendMode::kDstIn: return os << "BlendMode::kDstIn"; - case DlBlendMode::kSrcOut: return os << "BlendMode::kSrcOut"; - case DlBlendMode::kDstOut: return os << "BlendMode::kDstOut"; - case DlBlendMode::kSrcATop: return os << "BlendMode::kSrcATop"; - case DlBlendMode::kDstATop: return os << "BlendMode::kDstATop"; - case DlBlendMode::kXor: return os << "BlendMode::kXor"; - case DlBlendMode::kPlus: return os << "BlendMode::kPlus"; - case DlBlendMode::kModulate: return os << "BlendMode::kModulate"; - case DlBlendMode::kScreen: return os << "BlendMode::kScreen"; - - case DlBlendMode::kOverlay: return os << "BlendMode::kOverlay"; - case DlBlendMode::kDarken: return os << "BlendMode::kDarken"; - case DlBlendMode::kLighten: return os << "BlendMode::kLighten"; - case DlBlendMode::kColorDodge: return os << "BlendMode::kColorDodge"; - case DlBlendMode::kColorBurn: return os << "BlendMode::kColorBurn"; - case DlBlendMode::kHardLight: return os << "BlendMode::kHardLight"; - case DlBlendMode::kSoftLight: return os << "BlendMode::kSoftLight"; - case DlBlendMode::kDifference: return os << "BlendMode::kDifference"; - case DlBlendMode::kExclusion: return os << "BlendMode::kExclusion"; - case DlBlendMode::kMultiply: return os << "BlendMode::kMultiply"; - - case DlBlendMode::kHue: return os << "BlendMode::kHue"; - case DlBlendMode::kSaturation: return os << "BlendMode::kSaturation"; - case DlBlendMode::kColor: return os << "BlendMode::kColor"; - case DlBlendMode::kLuminosity: return os << "BlendMode::kLuminosity"; - - default: return os << "BlendMode::????"; - } -} + DLT_OSTREAM_CASE(DlBlendMode, Clear); + DLT_OSTREAM_CASE(DlBlendMode, Src); + DLT_OSTREAM_CASE(DlBlendMode, Dst); + DLT_OSTREAM_CASE(DlBlendMode, SrcOver); + DLT_OSTREAM_CASE(DlBlendMode, DstOver); + DLT_OSTREAM_CASE(DlBlendMode, SrcIn); + DLT_OSTREAM_CASE(DlBlendMode, DstIn); + DLT_OSTREAM_CASE(DlBlendMode, SrcOut); + DLT_OSTREAM_CASE(DlBlendMode, DstOut); + DLT_OSTREAM_CASE(DlBlendMode, SrcATop); + DLT_OSTREAM_CASE(DlBlendMode, DstATop); + DLT_OSTREAM_CASE(DlBlendMode, Xor); + DLT_OSTREAM_CASE(DlBlendMode, Plus); + DLT_OSTREAM_CASE(DlBlendMode, Modulate); + DLT_OSTREAM_CASE(DlBlendMode, Screen); + + DLT_OSTREAM_CASE(DlBlendMode, Overlay); + DLT_OSTREAM_CASE(DlBlendMode, Darken); + DLT_OSTREAM_CASE(DlBlendMode, Lighten); + DLT_OSTREAM_CASE(DlBlendMode, ColorDodge); + DLT_OSTREAM_CASE(DlBlendMode, ColorBurn); + DLT_OSTREAM_CASE(DlBlendMode, HardLight); + DLT_OSTREAM_CASE(DlBlendMode, SoftLight); + DLT_OSTREAM_CASE(DlBlendMode, Difference); + DLT_OSTREAM_CASE(DlBlendMode, Exclusion); + DLT_OSTREAM_CASE(DlBlendMode, Multiply); + + DLT_OSTREAM_CASE(DlBlendMode, Hue); + DLT_OSTREAM_CASE(DlBlendMode, Saturation); + DLT_OSTREAM_CASE(DlBlendMode, Color); + DLT_OSTREAM_CASE(DlBlendMode, Luminosity); + } + // Not a valid enum, should never happen, but in case we encounter bad data. + return os << "DlBlendMode::????"; +} + +extern std::ostream& operator<<(std::ostream& os, + const flutter::DisplayListOpType& type) { + switch (type) { +#define DLT_OP_TYPE_CASE(V) DLT_OSTREAM_CASE(DisplayListOpType, V); + FOR_EACH_DISPLAY_LIST_OP(DLT_OP_TYPE_CASE) + DLT_OP_TYPE_CASE(InvalidOp) + +#ifdef IMPELLER_ENABLE_3D + DLT_OP_TYPE_CASE(SetSceneColorSource) +#endif // IMPELLER_ENABLE_3D + + +#undef DLT_OP_TYPE_CASE + } + // Not a valid enum, should never happen, but in case we encounter bad data. + return os << "DisplayListOpType::???"; +} + +extern std::ostream& operator<<( + std::ostream& os, const flutter::DisplayListOpCategory& category) { + switch (category) { + DLT_OSTREAM_CASE(DisplayListOpCategory, Attribute); + DLT_OSTREAM_CASE(DisplayListOpCategory, Transform); + DLT_OSTREAM_CASE(DisplayListOpCategory, Clip); + DLT_OSTREAM_CASE(DisplayListOpCategory, Save); + DLT_OSTREAM_CASE(DisplayListOpCategory, SaveLayer); + DLT_OSTREAM_CASE(DisplayListOpCategory, Restore); + DLT_OSTREAM_CASE(DisplayListOpCategory, Rendering); + DLT_OSTREAM_CASE(DisplayListOpCategory, SubDisplayList); + DLT_OSTREAM_CASE(DisplayListOpCategory, InvalidCategory); + } + // Not a valid enum, should never happen, but in case we encounter bad data. + return os << "DisplayListOpCategory::???"; +} + +#undef DLT_OSTREAM_CASE std::ostream& operator<<(std::ostream& os, const SaveLayerOptions& options) { return os << "SaveLayerOptions(" @@ -497,6 +539,12 @@ void DisplayListStreamDispatcher::setColorSource(const DlColorSource* source) { << sweep_src->tile_mode() << ", " << sweep_src->matrix_ptr() << ")"; break; } +#ifdef IMPELLER_ENABLE_3D + case DlColorSourceType::kScene: { + os_ << "DlSceneColorSource()"; + break; + } +#endif // IMPELLER_ENABLE_3D default: os_ << "?DlUnknownColorSource?()"; break; diff --git a/testing/display_list_testing.h b/testing/display_list_testing.h index f238360e44e8c..3ce9ec2bfe65e 100644 --- a/testing/display_list_testing.h +++ b/testing/display_list_testing.h @@ -75,6 +75,10 @@ extern std::ostream& operator<<(std::ostream& os, const flutter::DlImage* image); extern std::ostream& operator<<(std::ostream& os, const flutter::SaveLayerOptions& image); +extern std::ostream& operator<<(std::ostream& os, + const flutter::DisplayListOpType& type); +extern std::ostream& operator<<(std::ostream& os, + const flutter::DisplayListOpCategory& category); } // namespace std @@ -209,6 +213,398 @@ class DisplayListStreamDispatcher final : public DlOpReceiver { void out(const DlImageFilter* filter); }; +class DisplayListGeneralReceiver : public DlOpReceiver { + public: + DisplayListGeneralReceiver() { + type_counts_.fill(0u); + category_counts_.fill(0u); + } + + void setAntiAlias(bool aa) override { + RecordByType(DisplayListOpType::kSetAntiAlias); + } + void setInvertColors(bool invert) override { + RecordByType(DisplayListOpType::kSetInvertColors); + } + void setStrokeCap(DlStrokeCap cap) override { + RecordByType(DisplayListOpType::kSetStrokeCap); + } + void setStrokeJoin(DlStrokeJoin join) override { + RecordByType(DisplayListOpType::kSetStrokeJoin); + } + void setDrawStyle(DlDrawStyle style) override { + RecordByType(DisplayListOpType::kSetStyle); + } + void setStrokeWidth(float width) override { + RecordByType(DisplayListOpType::kSetStrokeWidth); + } + void setStrokeMiter(float limit) override { + RecordByType(DisplayListOpType::kSetStrokeMiter); + } + void setColor(DlColor color) override { + RecordByType(DisplayListOpType::kSetColor); + } + void setBlendMode(DlBlendMode mode) override { + RecordByType(DisplayListOpType::kSetBlendMode); + } + void setColorSource(const DlColorSource* source) override { + if (source) { + switch (source->type()) { + case DlColorSourceType::kImage: + RecordByType(DisplayListOpType::kSetImageColorSource); + break; + case DlColorSourceType::kRuntimeEffect: + RecordByType(DisplayListOpType::kSetRuntimeEffectColorSource); + break; + case DlColorSourceType::kColor: + case DlColorSourceType::kLinearGradient: + case DlColorSourceType::kRadialGradient: + case DlColorSourceType::kConicalGradient: + case DlColorSourceType::kSweepGradient: + RecordByType(DisplayListOpType::kSetPodColorSource); + break; +#ifdef IMPELLER_ENABLE_3D + case DlColorSourceType::kScene: + RecordByType(DisplayListOpType::kSetSceneColorSource); + break; +#endif // IMPELLER_ENABLE_3D + } + } else { + RecordByType(DisplayListOpType::kClearColorSource); + } + } + void setImageFilter(const DlImageFilter* filter) override { + if (filter) { + switch (filter->type()) { + case DlImageFilterType::kBlur: + case DlImageFilterType::kDilate: + case DlImageFilterType::kErode: + case DlImageFilterType::kMatrix: + RecordByType(DisplayListOpType::kSetPodImageFilter); + break; + case DlImageFilterType::kCompose: + case DlImageFilterType::kLocalMatrix: + case DlImageFilterType::kColorFilter: + RecordByType(DisplayListOpType::kSetSharedImageFilter); + break; + } + } else { + RecordByType(DisplayListOpType::kClearImageFilter); + } + } + void setColorFilter(const DlColorFilter* filter) override { + if (filter) { + switch (filter->type()) { + case DlColorFilterType::kBlend: + case DlColorFilterType::kMatrix: + case DlColorFilterType::kLinearToSrgbGamma: + case DlColorFilterType::kSrgbToLinearGamma: + RecordByType(DisplayListOpType::kSetPodColorFilter); + break; + } + } else { + RecordByType(DisplayListOpType::kClearColorFilter); + } + } + void setMaskFilter(const DlMaskFilter* filter) override { + if (filter) { + switch (filter->type()) { + case DlMaskFilterType::kBlur: + RecordByType(DisplayListOpType::kSetPodMaskFilter); + break; + } + } else { + RecordByType(DisplayListOpType::kClearMaskFilter); + } + } + + void translate(SkScalar tx, SkScalar ty) override { + RecordByType(DisplayListOpType::kTranslate); + } + void scale(SkScalar sx, SkScalar sy) override { + RecordByType(DisplayListOpType::kScale); + } + void rotate(SkScalar degrees) override { + RecordByType(DisplayListOpType::kRotate); + } + void skew(SkScalar sx, SkScalar sy) override { + RecordByType(DisplayListOpType::kSkew); + } + // 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 { + RecordByType(DisplayListOpType::kTransform2DAffine); + } + // 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 { + RecordByType(DisplayListOpType::kTransformFullPerspective); + } + // clang-format on + void transformReset() override { + RecordByType(DisplayListOpType::kTransformReset); + } + + void clipRect(const SkRect& rect, + DlCanvas::ClipOp clip_op, + bool is_aa) override { + switch (clip_op) { + case DlCanvas::ClipOp::kIntersect: + RecordByType(DisplayListOpType::kClipIntersectRect); + break; + case DlCanvas::ClipOp::kDifference: + RecordByType(DisplayListOpType::kClipDifferenceRect); + break; + } + } + void clipOval(const SkRect& bounds, + DlCanvas::ClipOp clip_op, + bool is_aa) override { + switch (clip_op) { + case DlCanvas::ClipOp::kIntersect: + RecordByType(DisplayListOpType::kClipIntersectOval); + break; + case DlCanvas::ClipOp::kDifference: + RecordByType(DisplayListOpType::kClipDifferenceOval); + break; + } + } + void clipRRect(const SkRRect& rrect, + DlCanvas::ClipOp clip_op, + bool is_aa) override { + switch (clip_op) { + case DlCanvas::ClipOp::kIntersect: + RecordByType(DisplayListOpType::kClipIntersectRRect); + break; + case DlCanvas::ClipOp::kDifference: + RecordByType(DisplayListOpType::kClipDifferenceRRect); + break; + } + } + void clipPath(const SkPath& path, + DlCanvas::ClipOp clip_op, + bool is_aa) override { + switch (clip_op) { + case DlCanvas::ClipOp::kIntersect: + RecordByType(DisplayListOpType::kClipIntersectPath); + break; + case DlCanvas::ClipOp::kDifference: + RecordByType(DisplayListOpType::kClipDifferencePath); + break; + } + } + + void save() override { RecordByType(DisplayListOpType::kSave); } + void saveLayer(const SkRect& bounds, + const SaveLayerOptions options, + const DlImageFilter* backdrop) override { + if (backdrop) { + RecordByType(DisplayListOpType::kSaveLayerBackdrop); + } else { + RecordByType(DisplayListOpType::kSaveLayer); + } + } + void restore() override { RecordByType(DisplayListOpType::kRestore); } + + void drawColor(DlColor color, DlBlendMode mode) override { + RecordByType(DisplayListOpType::kDrawColor); + } + void drawPaint() override { RecordByType(DisplayListOpType::kDrawPaint); } + void drawLine(const SkPoint& p0, const SkPoint& p1) override { + RecordByType(DisplayListOpType::kDrawLine); + } + void drawDashedLine(const DlPoint& p0, + const DlPoint& p1, + DlScalar on_length, + DlScalar off_length) override { + RecordByType(DisplayListOpType::kDrawDashedLine); + } + void drawRect(const SkRect& rect) override { + RecordByType(DisplayListOpType::kDrawRect); + } + void drawOval(const SkRect& bounds) override { + RecordByType(DisplayListOpType::kDrawOval); + } + void drawCircle(const SkPoint& center, SkScalar radius) override { + RecordByType(DisplayListOpType::kDrawCircle); + } + void drawRRect(const SkRRect& rrect) override { + RecordByType(DisplayListOpType::kDrawRRect); + } + void drawDRRect(const SkRRect& outer, const SkRRect& inner) override { + RecordByType(DisplayListOpType::kDrawDRRect); + } + void drawPath(const SkPath& path) override { + RecordByType(DisplayListOpType::kDrawPath); + } + void drawArc(const SkRect& oval_bounds, + SkScalar start_degrees, + SkScalar sweep_degrees, + bool use_center) override { + RecordByType(DisplayListOpType::kDrawArc); + } + void drawPoints(DlCanvas::PointMode mode, + uint32_t count, + const SkPoint points[]) override { + switch (mode) { + case DlCanvas::PointMode::kPoints: + RecordByType(DisplayListOpType::kDrawPoints); + break; + case DlCanvas::PointMode::kLines: + RecordByType(DisplayListOpType::kDrawLines); + break; + case DlCanvas::PointMode::kPolygon: + RecordByType(DisplayListOpType::kDrawPolygon); + break; + } + } + void drawVertices(const std::shared_ptr& vertices, + DlBlendMode mode) override { + RecordByType(DisplayListOpType::kDrawVertices); + } + void drawImage(const sk_sp image, + const SkPoint point, + DlImageSampling sampling, + bool render_with_attributes) override { + if (render_with_attributes) { + RecordByType(DisplayListOpType::kDrawImageWithAttr); + } else { + RecordByType(DisplayListOpType::kDrawImage); + } + } + void drawImageRect(const sk_sp image, + const SkRect& src, + const SkRect& dst, + DlImageSampling sampling, + bool render_with_attributes, + SrcRectConstraint constraint) override { + RecordByType(DisplayListOpType::kDrawImageRect); + } + void drawImageNine(const sk_sp image, + const SkIRect& center, + const SkRect& dst, + DlFilterMode filter, + bool render_with_attributes) override { + if (render_with_attributes) { + RecordByType(DisplayListOpType::kDrawImageNineWithAttr); + } else { + RecordByType(DisplayListOpType::kDrawImageNine); + } + } + 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 { + if (cull_rect) { + RecordByType(DisplayListOpType::kDrawAtlasCulled); + } else { + RecordByType(DisplayListOpType::kDrawAtlas); + } + } + void drawDisplayList(const sk_sp display_list, + SkScalar opacity) override { + RecordByType(DisplayListOpType::kDrawDisplayList); + } + void drawTextBlob(const sk_sp blob, + SkScalar x, + SkScalar y) override { + RecordByType(DisplayListOpType::kDrawTextBlob); + } + void drawTextFrame(const std::shared_ptr& text_frame, + SkScalar x, + SkScalar y) override { + RecordByType(DisplayListOpType::kDrawTextFrame); + } + void drawShadow(const SkPath& path, + const DlColor color, + const SkScalar elevation, + bool transparent_occluder, + SkScalar dpr) override { + if (transparent_occluder) { + RecordByType(DisplayListOpType::kDrawShadowTransparentOccluder); + } else { + RecordByType(DisplayListOpType::kDrawShadow); + } + } + + uint32_t GetOpsReceived() { return op_count_; } + uint32_t GetOpsReceived(DisplayListOpCategory category) { + return category_counts_[static_cast(category)]; + } + uint32_t GetOpsReceived(DisplayListOpType type) { + return type_counts_[static_cast(type)]; + } + + protected: + virtual void RecordByType(DisplayListOpType type) { + type_counts_[static_cast(type)]++; + RecordByCategory(DisplayList::GetOpCategory(type)); + } + + virtual void RecordByCategory(DisplayListOpCategory category) { + category_counts_[static_cast(category)]++; + switch (category) { + case DisplayListOpCategory::kAttribute: + RecordAttribute(); + break; + case DisplayListOpCategory::kTransform: + RecordTransform(); + break; + case DisplayListOpCategory::kClip: + RecordClip(); + break; + case DisplayListOpCategory::kSave: + RecordSave(); + break; + case DisplayListOpCategory::kSaveLayer: + RecordSaveLayer(); + break; + case DisplayListOpCategory::kRestore: + RecordRestore(); + break; + case DisplayListOpCategory::kRendering: + RecordRendering(); + break; + case DisplayListOpCategory::kSubDisplayList: + RecordSubDisplayList(); + break; + case DisplayListOpCategory::kInvalidCategory: + RecordInvalid(); + break; + } + } + + virtual void RecordAttribute() { RecordOp(); } + virtual void RecordTransform() { RecordOp(); } + virtual void RecordClip() { RecordOp(); } + virtual void RecordSave() { RecordOp(); } + virtual void RecordSaveLayer() { RecordOp(); } + virtual void RecordRestore() { RecordOp(); } + virtual void RecordRendering() { RecordOp(); } + virtual void RecordSubDisplayList() { RecordOp(); } + virtual void RecordInvalid() { RecordOp(); } + + virtual void RecordOp() { op_count_++; } + + static constexpr size_t kTypeCount = + static_cast(DisplayListOpType::kMaxOp) + 1; + static constexpr size_t kCategoryCount = + static_cast(DisplayListOpCategory::kMaxCategory) + 1; + + std::array type_counts_; + std::array category_counts_; + uint32_t op_count_ = 0u; +}; + } // namespace testing } // namespace flutter