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