From 13d64bc737f120988fe67873c847fdd0e0f78128 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Wed, 8 May 2024 08:23:48 -0700 Subject: [PATCH] DisplayListBuilder internal reorganization with better rendering op overlap detection (flutter/engine#52646) This commit makes some long-needed internal improvements to the way that the DisplayListBuilder manages its per-save/saveLayer data. The information for layer bounds and matrix/clips is now maintained in the layer info structure itself rather than shared across a number of stack structures which required careful alignment of the 3 different stacks and made it more difficult to compare and update adjacent layers during save and restore operations. The new code stores all information for a layer within a single structure and the save and restore operations can be more clear about which information they are getting or setting in the current and parent layers. In addition, the layer bounds accumulations were updated to have a more flexible algorithm for detecting overlap of rendering operations for the opacity peephole optimization. Previously, more than one rendering op on a layer would prevent opacity peephole optimizations, but now the condition will be recognized until the first rendering op that overlaps the bounds of the previous rendering operations. This will help for some potentially common cases of 2 non-overlapping ops or even a list of rendering operations laid out in a row. --- .../ci/licenses_golden/licenses_flutter | 10 +- engine/src/flutter/display_list/BUILD.gn | 5 +- .../benchmarking/dl_complexity_unittests.cc | 2 +- .../display_list/display_list_unittests.cc | 99 +- engine/src/flutter/display_list/dl_builder.cc | 959 +++++++++++------- engine/src/flutter/display_list/dl_builder.h | 337 +++--- .../src/flutter/display_list/dl_vertices.cc | 4 +- .../display_list/geometry/dl_geometry_types.h | 72 ++ .../utils/dl_accumulation_rect.cc | 67 ++ .../display_list/utils/dl_accumulation_rect.h | 54 + .../utils/dl_bounds_accumulator.cc | 134 --- .../utils/dl_bounds_accumulator.h | 168 --- .../utils/dl_matrix_clip_tracker.cc | 33 +- .../utils/dl_matrix_clip_tracker.h | 47 +- engine/src/flutter/impeller/geometry/rect.h | 4 +- engine/src/flutter/impeller/geometry/size.h | 4 +- 16 files changed, 1082 insertions(+), 917 deletions(-) create mode 100644 engine/src/flutter/display_list/geometry/dl_geometry_types.h create mode 100644 engine/src/flutter/display_list/utils/dl_accumulation_rect.cc create mode 100644 engine/src/flutter/display_list/utils/dl_accumulation_rect.h delete mode 100644 engine/src/flutter/display_list/utils/dl_bounds_accumulator.cc delete mode 100644 engine/src/flutter/display_list/utils/dl_bounds_accumulator.h diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter index ac492bd5658..4cea8511e76 100644 --- a/engine/src/flutter/ci/licenses_golden/licenses_flutter +++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter @@ -39927,6 +39927,7 @@ ORIGIN: ../../../flutter/display_list/effects/dl_path_effect.cc + ../../../flutt ORIGIN: ../../../flutter/display_list/effects/dl_path_effect.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/effects/dl_runtime_effect.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/effects/dl_runtime_effect.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/geometry/dl_geometry_types.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/geometry/dl_region.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/geometry/dl_region.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/geometry/dl_rtree.cc + ../../../flutter/LICENSE @@ -39944,8 +39945,8 @@ ORIGIN: ../../../flutter/display_list/skia/dl_sk_dispatcher.h + ../../../flutter ORIGIN: ../../../flutter/display_list/skia/dl_sk_paint_dispatcher.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/skia/dl_sk_paint_dispatcher.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/skia/dl_sk_types.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/display_list/utils/dl_bounds_accumulator.cc + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/display_list/utils/dl_bounds_accumulator.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/utils/dl_accumulation_rect.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/utils/dl_accumulation_rect.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/utils/dl_comparable.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/utils/dl_matrix_clip_tracker.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/utils/dl_matrix_clip_tracker.h + ../../../flutter/LICENSE @@ -42804,6 +42805,7 @@ FILE: ../../../flutter/display_list/effects/dl_path_effect.cc FILE: ../../../flutter/display_list/effects/dl_path_effect.h FILE: ../../../flutter/display_list/effects/dl_runtime_effect.cc FILE: ../../../flutter/display_list/effects/dl_runtime_effect.h +FILE: ../../../flutter/display_list/geometry/dl_geometry_types.h FILE: ../../../flutter/display_list/geometry/dl_region.cc FILE: ../../../flutter/display_list/geometry/dl_region.h FILE: ../../../flutter/display_list/geometry/dl_rtree.cc @@ -42821,8 +42823,8 @@ FILE: ../../../flutter/display_list/skia/dl_sk_dispatcher.h FILE: ../../../flutter/display_list/skia/dl_sk_paint_dispatcher.cc FILE: ../../../flutter/display_list/skia/dl_sk_paint_dispatcher.h FILE: ../../../flutter/display_list/skia/dl_sk_types.h -FILE: ../../../flutter/display_list/utils/dl_bounds_accumulator.cc -FILE: ../../../flutter/display_list/utils/dl_bounds_accumulator.h +FILE: ../../../flutter/display_list/utils/dl_accumulation_rect.cc +FILE: ../../../flutter/display_list/utils/dl_accumulation_rect.h FILE: ../../../flutter/display_list/utils/dl_comparable.h FILE: ../../../flutter/display_list/utils/dl_matrix_clip_tracker.cc FILE: ../../../flutter/display_list/utils/dl_matrix_clip_tracker.h diff --git a/engine/src/flutter/display_list/BUILD.gn b/engine/src/flutter/display_list/BUILD.gn index 2bbb1e545e4..dd6aba8d381 100644 --- a/engine/src/flutter/display_list/BUILD.gn +++ b/engine/src/flutter/display_list/BUILD.gn @@ -60,6 +60,7 @@ source_set("display_list") { "effects/dl_path_effect.h", "effects/dl_runtime_effect.cc", "effects/dl_runtime_effect.h", + "geometry/dl_geometry_types.h", "geometry/dl_region.cc", "geometry/dl_region.h", "geometry/dl_rtree.cc", @@ -77,8 +78,8 @@ source_set("display_list") { "skia/dl_sk_paint_dispatcher.cc", "skia/dl_sk_paint_dispatcher.h", "skia/dl_sk_types.h", - "utils/dl_bounds_accumulator.cc", - "utils/dl_bounds_accumulator.h", + "utils/dl_accumulation_rect.cc", + "utils/dl_accumulation_rect.h", "utils/dl_matrix_clip_tracker.cc", "utils/dl_matrix_clip_tracker.h", "utils/dl_receiver_utils.cc", diff --git a/engine/src/flutter/display_list/benchmarking/dl_complexity_unittests.cc b/engine/src/flutter/display_list/benchmarking/dl_complexity_unittests.cc index 3e86622a765..511ba8b1f79 100644 --- a/engine/src/flutter/display_list/benchmarking/dl_complexity_unittests.cc +++ b/engine/src/flutter/display_list/benchmarking/dl_complexity_unittests.cc @@ -102,7 +102,7 @@ TEST(DisplayListComplexity, StrokeWidth) { auto display_list_stroke_0 = builder_stroke_0.Build(); DisplayListBuilder builder_stroke_1; - builder_stroke_0.DrawLine(SkPoint::Make(0, 0), SkPoint::Make(100, 100), + builder_stroke_1.DrawLine(SkPoint::Make(0, 0), SkPoint::Make(100, 100), DlPaint().setStrokeWidth(1.0f)); auto display_list_stroke_1 = builder_stroke_1.Build(); diff --git a/engine/src/flutter/display_list/display_list_unittests.cc b/engine/src/flutter/display_list/display_list_unittests.cc index a149e75acdd..20814eca265 100644 --- a/engine/src/flutter/display_list/display_list_unittests.cc +++ b/engine/src/flutter/display_list/display_list_unittests.cc @@ -532,13 +532,13 @@ TEST_F(DisplayListTest, UnclippedSaveLayerContentAccountsForFilter) { builder.Restore(); auto display_list = builder.Build(); - ASSERT_EQ(display_list->op_count(), 6u); + EXPECT_EQ(display_list->op_count(), 6u); EXPECT_EQ(display_list->total_depth(), 2u); SkRect result_rect = draw_rect.makeOutset(30.0f, 30.0f); ASSERT_TRUE(result_rect.intersect(clip_rect)); ASSERT_EQ(result_rect, SkRect::MakeLTRB(100.0f, 110.0f, 131.0f, 190.0f)); - ASSERT_EQ(display_list->bounds(), result_rect); + EXPECT_EQ(display_list->bounds(), result_rect); } TEST_F(DisplayListTest, ClippedSaveLayerContentAccountsForFilter) { @@ -565,13 +565,72 @@ TEST_F(DisplayListTest, ClippedSaveLayerContentAccountsForFilter) { builder.Restore(); auto display_list = builder.Build(); - ASSERT_EQ(display_list->op_count(), 6u); + EXPECT_EQ(display_list->op_count(), 6u); EXPECT_EQ(display_list->total_depth(), 2u); SkRect result_rect = draw_rect.makeOutset(30.0f, 30.0f); ASSERT_TRUE(result_rect.intersect(clip_rect)); ASSERT_EQ(result_rect, SkRect::MakeLTRB(100.0f, 110.0f, 129.0f, 190.0f)); - ASSERT_EQ(display_list->bounds(), result_rect); + EXPECT_EQ(display_list->bounds(), result_rect); +} + +TEST_F(DisplayListTest, OOBSaveLayerContentCulledWithBlurFilter) { + SkRect cull_rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + SkRect draw_rect = SkRect::MakeLTRB(25.0f, 25.0f, 99.0f, 75.0f); + auto filter = DlBlurImageFilter::Make(10.0f, 10.0f, DlTileMode::kDecal); + DlPaint layer_paint = DlPaint().setImageFilter(filter); + + // We want a draw rect that is outside the layer bounds even though its + // filtered output might be inside. The drawn rect should be culled by + // the expectations of the layer bounds even though it is close enough + // to be visible due to filtering. + ASSERT_FALSE(cull_rect.intersects(draw_rect)); + SkRect mapped_rect; + ASSERT_TRUE(filter->map_local_bounds(draw_rect, mapped_rect)); + ASSERT_TRUE(mapped_rect.intersects(cull_rect)); + + DisplayListBuilder builder; + builder.SaveLayer(&cull_rect, &layer_paint); + { // + builder.DrawRect(draw_rect, DlPaint()); + } + builder.Restore(); + auto display_list = builder.Build(); + + EXPECT_EQ(display_list->op_count(), 2u); + EXPECT_EQ(display_list->total_depth(), 1u); + + EXPECT_TRUE(display_list->bounds().isEmpty()) << display_list->bounds(); +} + +TEST_F(DisplayListTest, OOBSaveLayerContentCulledWithMatrixFilter) { + SkRect cull_rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + SkRect draw_rect = SkRect::MakeLTRB(25.0f, 125.0f, 75.0f, 175.0f); + auto filter = DlMatrixImageFilter::Make(SkMatrix::Translate(100.0f, 0.0f), + DlImageSampling::kLinear); + DlPaint layer_paint = DlPaint().setImageFilter(filter); + + // We want a draw rect that is outside the layer bounds even though its + // filtered output might be inside. The drawn rect should be culled by + // the expectations of the layer bounds even though it is close enough + // to be visible due to filtering. + ASSERT_FALSE(cull_rect.intersects(draw_rect)); + SkRect mapped_rect; + ASSERT_TRUE(filter->map_local_bounds(draw_rect, mapped_rect)); + ASSERT_TRUE(mapped_rect.intersects(cull_rect)); + + DisplayListBuilder builder; + builder.SaveLayer(&cull_rect, &layer_paint); + { // + builder.DrawRect(draw_rect, DlPaint()); + } + builder.Restore(); + auto display_list = builder.Build(); + + EXPECT_EQ(display_list->op_count(), 2u); + EXPECT_EQ(display_list->total_depth(), 1u); + + EXPECT_TRUE(display_list->bounds().isEmpty()) << display_list->bounds(); } TEST_F(DisplayListTest, SingleOpSizes) { @@ -1144,14 +1203,33 @@ TEST_F(DisplayListTest, SingleOpsMightSupportGroupOpacityBlendMode) { TEST_F(DisplayListTest, OverlappingOpsDoNotSupportGroupOpacity) { DisplayListBuilder builder; - DlOpReceiver& receiver = ToReceiver(builder); for (int i = 0; i < 10; i++) { - receiver.drawRect(SkRect::MakeXYWH(i * 10, 0, 30, 30)); + builder.DrawRect(SkRect::MakeXYWH(i * 10, 0, 30, 30), DlPaint()); } auto display_list = builder.Build(); EXPECT_FALSE(display_list->can_apply_group_opacity()); } +TEST_F(DisplayListTest, LineOfNonOverlappingOpsSupportGroupOpacity) { + DisplayListBuilder builder; + for (int i = 0; i < 10; i++) { + builder.DrawRect(SkRect::MakeXYWH(i * 30, 0, 30, 30), DlPaint()); + } + auto display_list = builder.Build(); + EXPECT_TRUE(display_list->can_apply_group_opacity()); +} + +TEST_F(DisplayListTest, CrossOfNonOverlappingOpsSupportGroupOpacity) { + DisplayListBuilder builder; + builder.DrawRect(SkRect::MakeLTRB(200, 200, 300, 300), DlPaint()); // center + builder.DrawRect(SkRect::MakeLTRB(100, 200, 200, 300), DlPaint()); // left + builder.DrawRect(SkRect::MakeLTRB(200, 100, 300, 200), DlPaint()); // above + builder.DrawRect(SkRect::MakeLTRB(300, 200, 400, 300), DlPaint()); // right + builder.DrawRect(SkRect::MakeLTRB(200, 300, 300, 400), DlPaint()); // below + auto display_list = builder.Build(); + EXPECT_TRUE(display_list->can_apply_group_opacity()); +} + TEST_F(DisplayListTest, SaveLayerFalseSupportsGroupOpacityOverlappingChidren) { DisplayListBuilder builder; DlOpReceiver& receiver = ToReceiver(builder); @@ -1248,7 +1326,8 @@ class SaveLayerOptionsExpector : public virtual DlOpReceiver, void saveLayer(const SkRect& bounds, const SaveLayerOptions options, const DlImageFilter* backdrop) override { - EXPECT_EQ(options, expected_[save_layer_count_]); + EXPECT_EQ(options, expected_[save_layer_count_]) + << "index " << save_layer_count_; save_layer_count_++; } @@ -3858,7 +3937,7 @@ TEST_F(DisplayListTest, SaveContentDepthTest) { builder.Save(); // covers depth 1->9 { - builder.Translate(5, 5); + builder.Translate(5, 5); // triggers deferred save at depth 1 builder.DrawRect({10, 10, 20, 20}, DlPaint()); // depth 2 builder.DrawDisplayList(child, 1.0f); // depth 3 (content) + 4 (self) @@ -3868,12 +3947,12 @@ TEST_F(DisplayListTest, SaveContentDepthTest) { builder.DrawRect({12, 12, 22, 22}, DlPaint()); // depth 5 builder.DrawRect({14, 14, 24, 24}, DlPaint()); // depth 6 } - builder.Restore(); // layer is restored with depth 7 + builder.Restore(); // layer is restored with depth 6 builder.DrawRect({16, 16, 26, 26}, DlPaint()); // depth 8 builder.DrawRect({18, 18, 28, 28}, DlPaint()); // depth 9 } - builder.Restore(); + builder.Restore(); // save is restored with depth 9 builder.DrawRect({16, 16, 26, 26}, DlPaint()); // depth 10 builder.DrawRect({18, 18, 28, 28}, DlPaint()); // depth 11 diff --git a/engine/src/flutter/display_list/dl_builder.cc b/engine/src/flutter/display_list/dl_builder.cc index f417fa1bcb6..c0edc341a0a 100644 --- a/engine/src/flutter/display_list/dl_builder.cc +++ b/engine/src/flutter/display_list/dl_builder.cc @@ -9,7 +9,7 @@ #include "flutter/display_list/dl_op_flags.h" #include "flutter/display_list/dl_op_records.h" #include "flutter/display_list/effects/dl_color_source.h" -#include "flutter/display_list/utils/dl_bounds_accumulator.h" +#include "flutter/display_list/utils/dl_accumulation_rect.h" #include "fml/logging.h" #include "third_party/skia/include/core/SkScalar.h" @@ -65,7 +65,7 @@ void* DisplayListBuilder::Push(size_t pod, Args&&... args) { } sk_sp DisplayListBuilder::Build() { - while (layer_stack_.size() > 1) { + while (save_stack_.size() > 1) { restore(); } @@ -74,42 +74,67 @@ sk_sp DisplayListBuilder::Build() { size_t nested_bytes = nested_bytes_; int nested_count = nested_op_count_; uint32_t total_depth = depth_; - bool compatible = current_layer_->is_group_opacity_compatible(); + bool compatible = current_info().is_group_opacity_compatible(); bool is_safe = is_ui_thread_safe_; - bool affects_transparency = current_layer_->affects_transparent_layer(); + bool affects_transparency = current_info().affects_transparent_layer; - sk_sp rtree = this->rtree(); - SkRect bounds = rtree ? rtree->bounds() : this->bounds(); + sk_sp rtree; + SkRect bounds; + if (rtree_data_.has_value()) { + auto& rects = rtree_data_->rects; + auto& indices = rtree_data_->indices; + rtree = sk_make_sp(rects.data(), rects.size(), indices.data(), + [](int id) { return id >= 0; }); + // RTree bounds may be tighter due to applying filter bounds + // adjustments to each op as we restore layers rather than to + // the entire layer bounds. + bounds = rtree->bounds(); + rtree_data_.reset(); + } else { + bounds = current_info().global_space_accumulator->bounds(); + } used_ = allocated_ = render_op_count_ = op_index_ = 0; nested_bytes_ = nested_op_count_ = 0; depth_ = 0; is_ui_thread_safe_ = true; - storage_.realloc(bytes); - layer_stack_.pop_back(); - layer_stack_.emplace_back(); - current_layer_ = &layer_stack_.back(); - tracker_.reset(); - layer_tracker_.reset(); + current_opacity_compatibility_ = true; + render_op_depth_cost_ = 1u; current_ = DlPaint(); + save_stack_.pop_back(); + save_stack_.emplace_back(original_cull_rect_); + current_info().is_nop = original_cull_rect_.IsEmpty(); + if (rtree) { + rtree_data_.emplace(); + } else { + current_info().global_space_accumulator.reset(new AccumulationRect()); + } + + storage_.realloc(bytes); return sk_sp( new DisplayList(std::move(storage_), bytes, count, nested_bytes, nested_count, total_depth, bounds, compatible, is_safe, affects_transparency, std::move(rtree))); } +static constexpr DlRect kEmpty = DlRect(); + +static const DlRect& ProtectEmpty(const SkRect& rect) { + // isEmpty protects us against NaN while we normalize any empty cull rects + return rect.isEmpty() ? kEmpty : ToDlRect(rect); +} + DisplayListBuilder::DisplayListBuilder(const SkRect& cull_rect, bool prepare_rtree) - : tracker_(cull_rect, SkMatrix::I()) { + : original_cull_rect_(ProtectEmpty(cull_rect)) { + save_stack_.emplace_back(original_cull_rect_); + current_info().is_nop = original_cull_rect_.IsEmpty(); if (prepare_rtree) { - accumulator_ = std::make_unique(); + rtree_data_.emplace(); } else { - accumulator_ = std::make_unique(); + current_info().global_space_accumulator.reset(new AccumulationRect()); } - - layer_stack_.emplace_back(); - current_layer_ = &layer_stack_.back(); } DisplayListBuilder::~DisplayListBuilder() { @@ -120,7 +145,7 @@ DisplayListBuilder::~DisplayListBuilder() { } SkISize DisplayListBuilder::GetBaseLayerSize() const { - return tracker_.base_device_cull_rect().roundOut().size(); + return ToSkISize(DlIRect::RoundOut(original_cull_rect_).GetSize()); } SkImageInfo DisplayListBuilder::GetImageInfo() const { @@ -388,193 +413,24 @@ void DisplayListBuilder::SetAttributesFromPaint( } void DisplayListBuilder::checkForDeferredSave() { - if (current_layer_->has_deferred_save_op_) { - size_t save_offset_ = used_; + if (current_info().has_deferred_save_op) { + size_t save_offset = used_; Push(0); - current_layer_->save_offset_ = save_offset_; - current_layer_->start_depth_ = depth_; - current_layer_->has_deferred_save_op_ = false; + current_info().save_offset = save_offset; + current_info().save_depth = depth_; + current_info().has_deferred_save_op = false; } } void DisplayListBuilder::Save() { - layer_stack_.emplace_back(); - current_layer_ = &layer_stack_.back(); + bool was_nop = current_info().is_nop; + save_stack_.emplace_back(¤t_info()); + current_info().is_nop = was_nop; - FML_DCHECK(layer_stack_.size() >= 2u); - // Note we can't use the previous value of current_layer_ because - // the emplace_back() may have moved the storage locations, so we - // recompute the location of the penultimate layer info here. - auto parent_layer = &layer_stack_.end()[-2]; - - current_layer_->has_deferred_save_op_ = true; - current_layer_->is_nop_ = parent_layer->is_nop_; - - if (parent_layer->layer_accumulator_) { - FML_DCHECK(layer_tracker_); - // If the previous layer was using an accumulator, we need to keep - // filling it with content bounds. We reuse the previous accumulator - // for this layer, but clone the associated transform so that new - // transform mutations are restricted to this save/restore context. - current_layer_->layer_accumulator_ = parent_layer->layer_accumulator_; - layer_tracker_->save(); - } else { - FML_DCHECK(!layer_tracker_); - } - - tracker_.save(); - accumulator()->save(); + FML_DCHECK(save_stack_.size() >= 2u); + FML_DCHECK(current_info().has_deferred_save_op); } -void DisplayListBuilder::Restore() { - if (layer_stack_.size() <= 1) { - return; - } - - SaveOpBase* op = reinterpret_cast(storage_.get() + - current_layer_->save_offset()); - - if (!current_layer_->has_deferred_save_op_) { - op->restore_index = op_index_; - op->total_content_depth = depth_ - current_layer_->start_depth_; - Push(0); - if (current_layer_->is_save_layer()) { - // A saveLayer will usually do a final copy to the main buffer in - // addition to its content, but that is accounted for outside of - // the total content depth computed above. - depth_ += render_op_depth_cost_; - } - } - - std::shared_ptr filter = current_layer_->filter(); - { - // We should not pop the stack until we are done synching up the current - // and parent layers. - auto parent_layer = &layer_stack_.end()[-2]; - - if (current_layer_->is_save_layer()) { - // Layers are never deferred for now, we need to update the - // following code if we ever do saveLayer culling... - FML_DCHECK(!current_layer_->has_deferred_save_op_); - FML_DCHECK(current_layer_->layer_accumulator_); - - SkRect content_bounds = current_layer_->layer_accumulator_->bounds(); - - switch (op->type) { - case DisplayListOpType::kSaveLayer: - case DisplayListOpType::kSaveLayerBackdrop: { - SaveLayerOpBase* layer_op = reinterpret_cast(op); - if (op->options.bounds_from_caller()) { - if (!content_bounds.isEmpty() && - !layer_op->rect.contains(content_bounds)) { - op->options = op->options.with_content_is_clipped(); - content_bounds.intersect(layer_op->rect); - } - } - layer_op->rect = content_bounds; - break; - } - default: - FML_UNREACHABLE(); - } - - if (layer_tracker_->getSaveCount() > 1) { - layer_tracker_->restore(); - } else { - // If this was the last layer in the tracker, then there should - // be no parent saveLayer. - FML_DCHECK(!parent_layer->layer_accumulator_); - layer_tracker_.reset(); - } - - if (parent_layer->layer_accumulator_) { - SkRect bounds_for_parent = content_bounds; - if (filter) { - if (!filter->map_local_bounds(bounds_for_parent, bounds_for_parent)) { - parent_layer->set_unbounded(); - } - } - // The content_bounds were accumulated in the base coordinate system - // of the current layer, and have been adjusted there according to - // its image filter. - // The content bounds accumulation of the parent layer is relative - // to the parent's base coordinate system, so we need to adjust - // bounds_for_parent to that coordinate space. - FML_DCHECK(layer_tracker_); - layer_tracker_->mapRect(&bounds_for_parent); - parent_layer->layer_accumulator_->accumulate(bounds_for_parent); - } - - if (current_layer_->is_group_opacity_compatible()) { - // We are now going to go back and modify the matching saveLayer - // call to add the option indicating it can distribute an opacity - // value to its children. - // - // Note that this operation cannot and does not change the size - // or structure of the SaveLayerOp record. It only sets an option - // flag on an existing field. - // - // Note that these kinds of modification operations on data already - // in the DisplayList are only allowed *during* the build phase. - // Once built, the DisplayList records must remain read only to - // ensure consistency of rendering and |Equals()| behavior. - op->options = op->options.with_can_distribute_opacity(); - } - } else { - if (layer_tracker_) { - FML_DCHECK(layer_tracker_->getSaveCount() > 1); - layer_tracker_->restore(); - } - // For regular save() ops there was no protecting layer so we have to - // accumulate the inheritance properties into the enclosing layer. - if (current_layer_->cannot_inherit_opacity()) { - parent_layer->mark_incompatible(); - } else if (current_layer_->has_compatible_op()) { - parent_layer->add_compatible_op(); - } - } - } - - // Remember whether the outgoing layer was unbounded so we can adjust - // for it below after we apply the outgoing layer's filter to the bounds. - bool popped_was_unbounded = current_layer_->is_unbounded(); - - // parent_layer is no longer in scope, time to pop the layer. - layer_stack_.pop_back(); - tracker_.restore(); - current_layer_ = &layer_stack_.back(); - - // As we pop the accumulator, use the filter that was applied to the - // outgoing layer (saved above, if any) to adjust the bounds that - // were accumulated while that layer was active. - if (filter) { - const SkRect clip = tracker_.device_cull_rect(); - if (!accumulator()->restore( - [filter = filter, matrix = GetTransform()](const SkRect& input, - SkRect& output) { - SkIRect output_bounds; - bool ret = filter->map_device_bounds(input.roundOut(), matrix, - output_bounds); - output.set(output_bounds); - return ret; - }, - &clip)) { - popped_was_unbounded = true; - } - } else { - accumulator()->restore(); - } - - if (popped_was_unbounded) { - AccumulateUnbounded(); - } -} -void DisplayListBuilder::RestoreToCount(int restore_count) { - FML_DCHECK(restore_count <= GetSaveCount()); - while (restore_count < GetSaveCount() && GetSaveCount() > 1) { - restore(); - } -} void DisplayListBuilder::saveLayer(const SkRect& bounds, const SaveLayerOptions in_options, const DlImageFilter* backdrop) { @@ -584,75 +440,122 @@ void DisplayListBuilder::saveLayer(const SkRect& bounds, : kSaveLayerFlags; OpResult result = PaintResult(current_, flags); if (result == OpResult::kNoEffect) { - save(); - current_layer_->is_nop_ = true; + // If we can't render, whether because we were already in a no-render + // state from the parent or because our own attributes make us a nop, + // we can just simplify this whole layer to a regular save that has + // nop state. We need to have a SaveInfo for the eventual restore(), + // but no rendering ops should be accepted between now and then so + // it doesn't need any of the data associated with a layer SaveInfo. + Save(); + current_info().is_nop = true; return; } - size_t save_layer_offset = used_; + // Snapshot these values before we do any work as we need the values + // from before the method was called, but some of the operations below + // might update them. + size_t save_offset = used_; + uint32_t save_depth = depth_; + + // A backdrop will affect up to the entire surface, bounded by the clip + bool will_be_unbounded = (backdrop != nullptr); + std::shared_ptr filter; if (options.renders_with_attributes()) { - // The actual flood of the outer layer clip will occur after the - // (eventual) corresponding restore is called, but rather than - // remember this information in the LayerInfo until the restore - // method is processed, we just mark the unbounded state up front. - // Another reason to accumulate the clip here rather than in - // restore is so that this savelayer will be tagged in the rtree - // with its full bounds and the right op_index so that it doesn't - // get culled during rendering. if (!paint_nops_on_transparency()) { // We will fill the clip of the outer layer when we restore. - // Accumulate should always return true here because if the - // clip was empty then that would have been caught up above - // when we tested the PaintResult. - [[maybe_unused]] bool unclipped = AccumulateUnbounded(); - FML_DCHECK(unclipped); + will_be_unbounded = true; } + filter = current_.getImageFilter(); CheckLayerOpacityCompatibility(true); - layer_stack_.emplace_back(save_layer_offset, depth_); - layer_stack_.back().filter_ = current_.getImageFilter(); } else { CheckLayerOpacityCompatibility(false); - layer_stack_.emplace_back(save_layer_offset, depth_); - } - current_layer_ = &layer_stack_.back(); - current_layer_->is_save_layer_ = true; - - tracker_.save(); - accumulator()->save(); - - SkRect record_bounds; - if (in_options.bounds_from_caller()) { - options = options.with_bounds_from_caller(); - record_bounds = bounds; - } else { - FML_DCHECK(record_bounds.isEmpty()); - } - current_layer_->layer_accumulator_.reset(new RectBoundsAccumulator()); - if (layer_tracker_) { - layer_tracker_->save(); - layer_tracker_->setTransform(SkMatrix::I()); - } else { - SkRect cull_rect; - if (in_options.bounds_from_caller()) { - cull_rect = bounds; - } else { - cull_rect = tracker_.local_cull_rect(); - } - layer_tracker_.reset( - new DisplayListMatrixClipTracker(cull_rect, SkMatrix::I())); } - if (backdrop) { - // A backdrop will affect up to the entire surface, bounded by the clip + // The actual flood of the outer layer clip will occur after the + // (eventual) corresponding restore is called, but rather than + // remember this information in the LayerInfo until the restore + // method is processed, we just mark the unbounded state up front. + // Another reason to accumulate the clip here rather than in + // restore is so that this savelayer will be tagged in the rtree + // with its full bounds and the right op_index so that it doesn't + // get culled during rendering. + if (will_be_unbounded) { // Accumulate should always return true here because if the // clip was empty then that would have been caught up above // when we tested the PaintResult. [[maybe_unused]] bool unclipped = AccumulateUnbounded(); FML_DCHECK(unclipped); - Push(0, options, record_bounds, backdrop); - } else { - Push(0, options, record_bounds); + } + + // Accumulate information for the SaveInfo we are about to push onto the + // stack. + { + size_t rtree_index = + rtree_data_.has_value() ? rtree_data_->rects.size() : 0u; + + save_stack_.emplace_back(¤t_info(), filter, rtree_index); + current_info().is_nop = false; + FML_DCHECK(!current_info().has_deferred_save_op); + current_info().save_offset = save_offset; + current_info().save_depth = save_depth; + + if (filter && !rtree_data_.has_value()) { + // By default the new SaveInfo shares the global accumulation rect with + // the parent layer and will only have one if the rtree_data is not + // being accumulated. + // + // But, if we have a filter and we are not accumulating rtree data, + // then we'll need to adjust all of the bounds accumulated via this + // new layer by the filter so we need to use a separate global + // accumulation rect for this layer and adjust it during RestoreLayer() + // before accumulating it into the parent layer. + current_info().global_space_accumulator.reset(new AccumulationRect()); + } + + // If we inherit some culling bounds and we have a filter then we need + // to adjust them so that we cull for the correct input space for the + // output of the filter. + if (filter) { + SkRect outer_cull_rect = current_info().global_state.local_cull_rect(); + + SkIRect output_bounds = outer_cull_rect.roundOut(); + SkIRect input_bounds; + if (filter->get_input_device_bounds(output_bounds, SkMatrix::I(), + input_bounds)) { + current_info().global_state.resetLocalCullRect( + SkRect::Make(input_bounds)); + } else { + // Filter could not make any promises about the bounds it needs to + // fill the output space, so we use a maximal rect to accumulate + // the layer bounds. + current_info().global_state.resetDeviceCullRect(kMaxCullRect); + } + } + + // We always want to cull based on user provided bounds, though, as + // that is legacy behavior even if it doesn't always work precisely + // in a rotated or skewed coordinate system. + if (in_options.bounds_from_caller()) { + current_info().global_state.clipRect(bounds, ClipOp::kIntersect, false); + } + } + + // Accumulate options to store in the SaveLayer op record. + { + SkRect record_bounds; + if (in_options.bounds_from_caller()) { + options = options.with_bounds_from_caller(); + record_bounds = bounds; + } else { + FML_DCHECK(record_bounds.isEmpty()); + } + + if (backdrop) { + Push(0, options, record_bounds, backdrop); + } else { + Push(0, options, record_bounds); + } } if (options.renders_with_attributes()) { @@ -660,32 +563,12 @@ void DisplayListBuilder::saveLayer(const SkRect& bounds, // account because an individual primitive with an ImageFilter can apply // opacity on top of it. But, if the layer is applying the ImageFilter // then it cannot pass the opacity on. - if (!current_opacity_compatibility_ || - current_.getImageFilter() != nullptr) { + if (!current_opacity_compatibility_ || filter) { UpdateLayerOpacityCompatibility(false); } } + // REMIND: NEEDED? UpdateLayerResult(result); - - if (options.renders_with_attributes() && current_.getImageFilter()) { - // We use |resetCullRect| here because we will be accumulating bounds of - // primitives before applying the filter to those bounds. We might - // encounter a primitive whose bounds are clipped, but whose filtered - // bounds will not be clipped. If the individual rendering ops bounds - // are clipped, it will not contribute to the overall bounds which - // could lead to inaccurate (subset) bounds of the DisplayList. - // We need to reset the cull rect here to avoid this premature clipping. - // The filtered bounds will be clipped to the existing clip rect when - // this layer is restored. - // If bounds is null then the original cull_rect will be used. - tracker_.resetLocalCullRect(in_options.bounds_from_caller() ? &bounds - : nullptr); - } else if (in_options.bounds_from_caller()) { - // Even though Skia claims that the bounds are only a hint, they actually - // use them as the temporary layer bounds during rendering the layer, so - // we set them as if a clip operation were performed. - tracker_.clipRect(bounds, ClipOp::kIntersect, false); - } } void DisplayListBuilder::SaveLayer(const SkRect* bounds, const DlPaint* paint, @@ -706,44 +589,300 @@ void DisplayListBuilder::SaveLayer(const SkRect* bounds, saveLayer(temp_bounds, options, backdrop); } +void DisplayListBuilder::Restore() { + if (save_stack_.size() <= 1) { + return; + } + + { + // The current_info will have a lifetime that does not extend past the + // pop_back() method below. + auto& current_info = this->current_info(); + + if (!current_info.has_deferred_save_op) { + SaveOpBase* op = reinterpret_cast(storage_.get() + + current_info.save_offset); + FML_DCHECK(op->type == DisplayListOpType::kSave || + op->type == DisplayListOpType::kSaveLayer || + op->type == DisplayListOpType::kSaveLayerBackdrop); + + op->restore_index = op_index_; + op->total_content_depth = depth_ - current_info.save_depth; + + Push(0); + + if (current_info.is_save_layer) { + RestoreLayer(current_info, parent_info(), op); + } else { + // We only propagate these values through a regular save() + if (current_info.opacity_incompatible_op_detected) { + parent_info().opacity_incompatible_op_detected = true; + } + } + } else { + FML_DCHECK(!current_info.is_save_layer); + } + } + + save_stack_.pop_back(); +} + +void DisplayListBuilder::RestoreLayer(const SaveInfo& current_info, + SaveInfo& parent_info, + void* base_op) { + FML_DCHECK(save_stack_.size() > 1); + FML_DCHECK(!current_info.has_deferred_save_op); + + // A saveLayer will usually do a final copy to the main buffer in + // addition to its content, but that is accounted for outside of + // the total content depth computed above in Restore. + depth_ += render_op_depth_cost_; + + SkRect content_bounds = current_info.layer_local_accumulator->bounds(); + + SaveLayerOpBase* layer_op = reinterpret_cast(base_op); + FML_DCHECK(layer_op->type == DisplayListOpType::kSaveLayer || + layer_op->type == DisplayListOpType::kSaveLayerBackdrop); + + switch (layer_op->type) { + case DisplayListOpType::kSaveLayer: + case DisplayListOpType::kSaveLayerBackdrop: { + if (layer_op->options.bounds_from_caller()) { + if (!content_bounds.isEmpty() && + !layer_op->rect.contains(content_bounds)) { + layer_op->options = layer_op->options.with_content_is_clipped(); + content_bounds.intersect(layer_op->rect); + } + } + layer_op->rect = content_bounds; + break; + } + default: + FML_UNREACHABLE(); + } + + if (current_info.is_group_opacity_compatible()) { + // We are now going to go back and modify the matching saveLayer + // call to add the option indicating it can distribute an opacity + // value to its children. + layer_op->options = layer_op->options.with_can_distribute_opacity(); + } + + TransferLayerBounds(current_info, parent_info, content_bounds); +} + +// There are a few different conditions and corresponding operations to +// consider when transferring bounds from one layer to another. The current +// layer will have accumulated its bounds into 2 potential places: +// +// - Its own private layer local bounds, which were potentially clipped by +// the supplied bounds and passed here as the content_bounds. +// +// - Either the rtree rect list, or the global space accumulator, one or +// the other. +// +// If there is no filter then the private layer bounds are complete and +// they simply need to be passed along to the parent into its layer local +// accumulator. Also, if there was no filter then the existing bounds +// recorded in either the rtree rects or the layer's global space accumulator +// (shared with its parent) need no updating so no global space transfer +// has to occur. +// +// If there is a filter then the global content bounds will need to be +// adjusted in one of two ways (rtree vs non-rtree): +// +// - If we are accumulating rtree rects then each of the rects accumulated +// during this current layer will need to be updated by the filter in the +// global coordinate space in which they were accumulated. In this mode +// we should never have a global space accumulator on the layer. +// +// - Otherwise we were accumulating global bounds into our own private +// global space accumulator which need to be adjusted in the global space +// coordinate system by the filter. +// +// Finally, we will have to adjust the layer's content bounds by the filter +// and accumulate those into the parent layer's local bounds. +void DisplayListBuilder::TransferLayerBounds(const SaveInfo& current_info, + SaveInfo& parent_info, + const SkRect& content_bounds) { + auto& filter = current_info.filter; + + if (!filter) { + // One or the other of the rtree data or the global space accumulator + // must be non-null, and the other must be null. + FML_DCHECK(rtree_data_.has_value() != + static_cast(current_info.global_space_accumulator)); + + // The current and parent global space accumulators either must both be + // null, or they must both point to the same accumulator. + FML_DCHECK(current_info.global_space_accumulator.get() == + parent_info.global_space_accumulator.get()); + + // If we have no filter then the global bounds were already accumulated + // into the parent's global accumulator, but we need to update the local + // bounds of the parent for the results of the saveLayer call. + parent_info.AccumulateBoundsLocal(content_bounds); + return; + } + + bool parent_is_flooded = false; + SkRect bounds_for_parent = content_bounds; + + // First, let's adjust or transfer the global bounds. + + if (rtree_data_.has_value()) { + // Neither current or parent layer should have a global space accumulator + FML_DCHECK(!current_info.global_space_accumulator); + FML_DCHECK(!parent_info.global_space_accumulator); + + // The rtree rects were accumulated without the bounds modification of + // the filter applied to the layer so they may fail to trigger on a + // culled dispatch if their filter "fringes" are in the dispatch scope + // but their base rendering bounds are not. (Also, they will not + // contribute fully when we compute the overall bounds of this DL.) + // + // To make sure they are rendered in the culled dispatch situation, we + // revisit all of the RTree rects accumulated during the current layer + // (indicated by rtree_rects_start_index) and expand them by the filter. + + // Matrix and Clip are the global values from just before our saveLayer + // and should still be the current values present in the parent layer. + const SkRect clip = parent_info.global_state.device_cull_rect(); + const SkMatrix matrix = parent_info.global_state.matrix_3x3(); + + // Starting rect index was snapshotted to this layer's data during + // saveLayer. + auto rect_start_index = current_info.rtree_rects_start_index; + + if (AdjustRTreeRects(rtree_data_.value(), *filter, matrix, clip, + rect_start_index)) { + parent_is_flooded = true; + } + } else { + // Both current or parent layer should have a global space accumulator + FML_DCHECK(current_info.global_space_accumulator); + FML_DCHECK(parent_info.global_space_accumulator); + + // And they should not be the same accumulator + FML_DCHECK(current_info.global_space_accumulator.get() != + parent_info.global_space_accumulator.get()); + + SkRect global_bounds = current_info.global_space_accumulator->bounds(); + if (!global_bounds.isEmpty()) { + SkIRect global_ibounds = global_bounds.roundOut(); + if (!filter->map_device_bounds(global_ibounds, + parent_info.global_state.matrix_3x3(), + global_ibounds)) { + parent_is_flooded = true; + } else { + global_bounds.set(global_ibounds); + const SkRect clip = parent_info.global_state.device_cull_rect(); + if (global_bounds.intersect(clip)) { + parent_info.global_space_accumulator->accumulate(global_bounds); + } + } + } + } + + // Now we visit the layer bounds which are in the layer's local coordinate + // system must be accumulated into the parent layer's bounds while + // adjusting them by the layer's local coordinate system (handled by the + // Accumulate() methods). + + // A filter will happily adjust empty bounds to be non-empty, so we + // specifically avoid that case here. Also, if we are already planning + // to flood the parent due to any of the cases above, we don't need to + // run the filter on the content bounds only to discover the same + // condition. + if (!parent_is_flooded && !bounds_for_parent.isEmpty()) { + if (!filter->map_local_bounds(bounds_for_parent, bounds_for_parent)) { + parent_is_flooded = true; + } + } + + if (parent_is_flooded) { + // All of the above computations deferred the flooded parent status + // to here. We need to mark the parent as flooded in both its layer + // and global accumulators. Note that even though the rtree rects + // were expanded to the size of the clip above, this method will still + // add one more rect to the rtree with the op index of the restore + // command to prevent the saveLayer itself from being elided in the + // rare case that there are no rendering ops in it, or somehow none + // of them were chosen by the rtree search (unlikely). The saveLayer + // must be processed for the parent flood to happen. + AccumulateUnbounded(parent_info); + } else { + parent_info.AccumulateBoundsLocal(bounds_for_parent); + } +} + +bool DisplayListBuilder::AdjustRTreeRects(RTreeData& data, + const DlImageFilter& filter, + const SkMatrix& matrix, + const SkRect& clip, + size_t rect_start_index) { + auto& rects = data.rects; + auto& indices = data.indices; + FML_DCHECK(rects.size() == indices.size()); + int ret = false; + auto rect_keep = rect_start_index; + for (size_t i = rect_start_index; i < rects.size(); i++) { + SkRect bounds = rects[i]; + SkIRect ibounds; + if (filter.map_device_bounds(bounds.roundOut(), matrix, ibounds)) { + bounds.set(ibounds); + } else { + bounds = clip; + ret = true; + } + if (bounds.intersect(clip)) { + indices[rect_keep] = indices[i]; + rects[rect_keep] = bounds; + rect_keep++; + } + } + indices.resize(rect_keep); + rects.resize(rect_keep); + return ret; +} + +void DisplayListBuilder::RestoreToCount(int restore_count) { + FML_DCHECK(restore_count <= GetSaveCount()); + while (restore_count < GetSaveCount() && GetSaveCount() > 1) { + restore(); + } +} + void DisplayListBuilder::Translate(SkScalar tx, SkScalar ty) { if (std::isfinite(tx) && std::isfinite(ty) && (tx != 0.0 || ty != 0.0)) { checkForDeferredSave(); Push(0, tx, ty); - tracker_.translate(tx, ty); - if (layer_tracker_) { - layer_tracker_->translate(tx, ty); - } + global_state().translate(tx, ty); + layer_local_state().translate(tx, ty); } } void DisplayListBuilder::Scale(SkScalar sx, SkScalar sy) { if (std::isfinite(sx) && std::isfinite(sy) && (sx != 1.0 || sy != 1.0)) { checkForDeferredSave(); Push(0, sx, sy); - tracker_.scale(sx, sy); - if (layer_tracker_) { - layer_tracker_->scale(sx, sy); - } + global_state().scale(sx, sy); + layer_local_state().scale(sx, sy); } } void DisplayListBuilder::Rotate(SkScalar degrees) { if (SkScalarMod(degrees, 360.0) != 0.0) { checkForDeferredSave(); Push(0, degrees); - tracker_.rotate(degrees); - if (layer_tracker_) { - layer_tracker_->rotate(degrees); - } + global_state().rotate(degrees); + layer_local_state().rotate(degrees); } } void DisplayListBuilder::Skew(SkScalar sx, SkScalar sy) { if (std::isfinite(sx) && std::isfinite(sy) && (sx != 0.0 || sy != 0.0)) { checkForDeferredSave(); Push(0, sx, sy); - tracker_.skew(sx, sy); - if (layer_tracker_) { - layer_tracker_->skew(sx, sy); - } + global_state().skew(sx, sy); + layer_local_state().skew(sx, sy); } } @@ -764,12 +903,10 @@ void DisplayListBuilder::Transform2DAffine( Push(0, mxx, mxy, mxt, myx, myy, myt); - tracker_.transform2DAffine(mxx, mxy, mxt, - myx, myy, myt); - if (layer_tracker_) { - layer_tracker_->transform2DAffine(mxx, mxy, mxt, - myx, myy, myt); - } + global_state().transform2DAffine(mxx, mxy, mxt, + myx, myy, myt); + layer_local_state().transform2DAffine(mxx, mxy, mxt, + myx, myy, myt); } } } @@ -799,44 +936,42 @@ void DisplayListBuilder::TransformFullPerspective( myx, myy, myz, myt, mzx, mzy, mzz, mzt, mwx, mwy, mwz, mwt); - tracker_.transformFullPerspective(mxx, mxy, mxz, mxt, - myx, myy, myz, myt, - mzx, mzy, mzz, mzt, - mwx, mwy, mwz, mwt); - if (layer_tracker_) { - layer_tracker_->transformFullPerspective(mxx, mxy, mxz, mxt, - myx, myy, myz, myt, - mzx, mzy, mzz, mzt, - mwx, mwy, mwz, mwt); - } + global_state().transformFullPerspective(mxx, mxy, mxz, mxt, + myx, myy, myz, myt, + mzx, mzy, mzz, mzt, + mwx, mwy, mwz, mwt); + layer_local_state().transformFullPerspective(mxx, mxy, mxz, mxt, + myx, myy, myz, myt, + mzx, mzy, mzz, mzt, + mwx, mwy, mwz, mwt); } } // clang-format on void DisplayListBuilder::TransformReset() { checkForDeferredSave(); Push(0); - if (layer_tracker_) { - // The matrices in layer_tracker_ and tracker_ are similar, but - // start at a different base transform. The tracker_ potentially - // has some number of transform operations on it that prefix the - // operations accumulated in layer_tracker_. So we can't set them both - // to identity in parallel as they would no longer maintain their - // relationship to each other. - // Instead we reinterpret this operation as transforming by the - // inverse of the current transform. Doing so to tracker_ sets it - // to identity so we can avoid the math there, but we must do the - // math the long way for layer_tracker_. This becomes: - // layer_tracker_.transform(tracker_.inverse()); - if (!layer_tracker_->inverseTransform(tracker_)) { - // If the inverse operation failed then that means that either - // the matrix above the current layer was singular, or the matrix - // became singular while we were accumulating the current layer. - // In either case, we should no longer be accumulating any - // contents so we set the layer tracking transform to a singular one. - layer_tracker_->setTransform(SkMatrix::Scale(0.0f, 0.0f)); - } + + // The matrices in layer_tracker_ and tracker_ are similar, but + // start at a different base transform. The tracker_ potentially + // has some number of transform operations on it that prefix the + // operations accumulated in layer_tracker_. So we can't set them both + // to identity in parallel as they would no longer maintain their + // relationship to each other. + // Instead we reinterpret this operation as transforming by the + // inverse of the current transform. Doing so to tracker_ sets it + // to identity so we can avoid the math there, but we must do the + // math the long way for layer_tracker_. This becomes: + // layer_tracker_.transform(tracker_.inverse()); + if (!layer_local_state().inverseTransform(global_state())) { + // If the inverse operation failed then that means that either + // the matrix above the current layer was singular, or the matrix + // became singular while we were accumulating the current layer. + // In either case, we should no longer be accumulating any + // contents so we set the layer tracking transform to a singular one. + layer_local_state().setTransform(SkMatrix::Scale(0.0f, 0.0f)); } - tracker_.setIdentity(); + + global_state().setIdentity(); } void DisplayListBuilder::Transform(const SkMatrix* matrix) { if (matrix != nullptr) { @@ -859,11 +994,13 @@ void DisplayListBuilder::ClipRect(const SkRect& rect, if (!rect.isFinite()) { return; } - tracker_.clipRect(rect, clip_op, is_aa); - if (current_layer_->is_nop_ || tracker_.is_cull_rect_empty()) { - current_layer_->is_nop_ = true; + global_state().clipRect(rect, clip_op, is_aa); + if (current_info().is_nop || + current_info().global_state.is_cull_rect_empty()) { + current_info().is_nop = true; return; } + layer_local_state().clipRect(rect, clip_op, is_aa); checkForDeferredSave(); switch (clip_op) { case ClipOp::kIntersect: @@ -880,11 +1017,13 @@ void DisplayListBuilder::ClipRRect(const SkRRect& rrect, if (rrect.isRect()) { clipRect(rrect.rect(), clip_op, is_aa); } else { - tracker_.clipRRect(rrect, clip_op, is_aa); - if (current_layer_->is_nop_ || tracker_.is_cull_rect_empty()) { - current_layer_->is_nop_ = true; + global_state().clipRRect(rrect, clip_op, is_aa); + if (current_info().is_nop || + current_info().global_state.is_cull_rect_empty()) { + current_info().is_nop = true; return; } + layer_local_state().clipRRect(rrect, clip_op, is_aa); checkForDeferredSave(); switch (clip_op) { case ClipOp::kIntersect: @@ -916,11 +1055,13 @@ void DisplayListBuilder::ClipPath(const SkPath& path, return; } } - tracker_.clipPath(path, clip_op, is_aa); - if (current_layer_->is_nop_ || tracker_.is_cull_rect_empty()) { - current_layer_->is_nop_ = true; + global_state().clipPath(path, clip_op, is_aa); + if (current_info().is_nop || + current_info().global_state.is_cull_rect_empty()) { + current_info().is_nop = true; return; } + layer_local_state().clipPath(path, clip_op, is_aa); checkForDeferredSave(); switch (clip_op) { case ClipOp::kIntersect: @@ -933,7 +1074,7 @@ void DisplayListBuilder::ClipPath(const SkPath& path, } bool DisplayListBuilder::QuickReject(const SkRect& bounds) const { - return tracker_.content_culled(bounds); + return global_state().content_culled(bounds); } void DisplayListBuilder::drawPaint() { @@ -1135,11 +1276,11 @@ void DisplayListBuilder::drawPoints(PointMode mode, FML_DCHECK(count < DlOpReceiver::kMaxDrawPointsCount); int bytes = count * sizeof(SkPoint); - RectBoundsAccumulator ptBounds; + AccumulationRect accumulator; for (size_t i = 0; i < count; i++) { - ptBounds.accumulate(pts[i]); + accumulator.accumulate(pts[i]); } - SkRect point_bounds = ptBounds.bounds(); + SkRect point_bounds = accumulator.bounds(); if (!AccumulateOpBounds(point_bounds, flags)) { return; } @@ -1165,7 +1306,12 @@ void DisplayListBuilder::drawPoints(PointMode mode, // distribution of group opacity without analyzing the mode and the // bounds of every sub-primitive. // See: https://fiddle.skia.org/c/228459001d2de8db117ce25ef5cedb0c - UpdateLayerOpacityCompatibility(false); + current_info().layer_local_accumulator->record_overlapping_bounds(); + // Even though we've eliminated the possibility of opacity peephole + // optimizations above, we still set the appropriate flags based on + // the rendering attributes in case we solve the overlapping points + // problem above. + CheckLayerOpacityCompatibility(); UpdateLayerResult(result); } void DisplayListBuilder::DrawPoints(PointMode mode, @@ -1189,6 +1335,15 @@ void DisplayListBuilder::drawVertices(const DlVertices* vertices, // cases. UpdateLayerOpacityCompatibility(false); UpdateLayerResult(result); + // Even though we already eliminated opacity peephole optimization + // due to the color issues identified above, drawVertices also fails + // based on the fact that the vertices are rendered independently + // so we cannot guarantee the non-overlapping condition. We record + // both conditions in case a solution is found to applying the + // colors above - both conditions must be analyzed sufficiently + // and implemented accordingly before drawVertices is compatible with + // opacity peephole optimizations. + current_info().layer_local_accumulator->record_overlapping_bounds(); } } void DisplayListBuilder::DrawVertices(const DlVertices* vertices, @@ -1312,18 +1467,31 @@ void DisplayListBuilder::drawAtlas(const sk_sp atlas, return; } SkPoint quad[4]; - RectBoundsAccumulator atlasBounds; + AccumulationRect accumulator; for (int i = 0; i < count; i++) { const SkRect& src = tex[i]; xform[i].toQuad(src.width(), src.height(), quad); for (int j = 0; j < 4; j++) { - atlasBounds.accumulate(quad[j]); + accumulator.accumulate(quad[j]); } } - if (atlasBounds.is_empty() || - !AccumulateOpBounds(atlasBounds.bounds(), flags)) { + if (accumulator.is_empty() || + !AccumulateOpBounds(accumulator.bounds(), flags)) { return; } + // Accumulating the bounds might not trip the overlap condition if the + // whole atlas operation is separated from other rendering calls, but + // since each atlas op is treated as an independent operation, we have + // to pass along our locally computed overlap condition for the individual + // atlas operations to the layer accumulator. + // Note that the above accumulation may falsely trigger the overlapping + // state as it is done quad corner by quad corner and an entire quad may + // be non-overlapping with the layer bounds, but as we add each point + // independently it might expand the bounds on one corner and then flag + // the condition when the next corner is added. + if (accumulator.overlap_detected()) { + current_info().layer_local_accumulator->record_overlapping_bounds(); + } int bytes = count * (sizeof(SkRSXform) + sizeof(SkRect)); void* data_ptr; @@ -1380,32 +1548,25 @@ void DisplayListBuilder::DrawDisplayList(const sk_sp display_list, SkScalar opacity) { if (!std::isfinite(opacity) || opacity <= SK_ScalarNearlyZero || display_list->op_count() == 0 || display_list->bounds().isEmpty() || - current_layer_->is_nop_) { + current_info().is_nop) { return; } const SkRect bounds = display_list->bounds(); bool accumulated; - switch (accumulator()->type()) { - case BoundsAccumulatorType::kRect: - accumulated = AccumulateOpBounds(bounds, kDrawDisplayListFlags); - break; - case BoundsAccumulatorType::kRTree: - auto rtree = display_list->rtree(); - if (rtree) { - std::list rects = - rtree->searchAndConsolidateRects(GetLocalClipBounds(), false); - accumulated = false; - for (const SkRect& rect : rects) { - // TODO (https://github.com/flutter/flutter/issues/114919): Attributes - // are not necessarily `kDrawDisplayListFlags`. - if (AccumulateOpBounds(rect, kDrawDisplayListFlags)) { - accumulated = true; - } - } - } else { - accumulated = AccumulateOpBounds(bounds, kDrawDisplayListFlags); + sk_sp rtree; + if (!rtree_data_.has_value() || !(rtree = display_list->rtree())) { + accumulated = AccumulateOpBounds(bounds, kDrawDisplayListFlags); + } else { + std::list rects = + rtree->searchAndConsolidateRects(GetLocalClipBounds(), false); + accumulated = false; + for (const SkRect& rect : rects) { + // TODO (https://github.com/flutter/flutter/issues/114919): Attributes + // are not necessarily `kDrawDisplayListFlags`. + if (AccumulateOpBounds(rect, kDrawDisplayListFlags)) { + accumulated = true; } - break; + } } if (!accumulated) { return; @@ -1608,17 +1769,21 @@ bool DisplayListBuilder::AdjustBoundsForPaint(SkRect& bounds, return true; } -bool DisplayListBuilder::AccumulateUnbounded() { - SkRect clip = tracker_.device_cull_rect(); +bool DisplayListBuilder::AccumulateUnbounded(SaveInfo& layer) { + SkRect clip = layer.global_state.device_cull_rect(); if (clip.isEmpty()) { return false; } - accumulator()->accumulate(clip, op_index_); - if (current_layer_->layer_accumulator_) { - FML_DCHECK(layer_tracker_); - current_layer_->layer_accumulator_->accumulate( - layer_tracker_->device_cull_rect()); + if (rtree_data_.has_value()) { + FML_DCHECK(!layer.global_space_accumulator); + rtree_data_->rects.push_back(clip); + rtree_data_->indices.push_back(op_index_); + } else { + FML_DCHECK(layer.global_space_accumulator); + layer.global_space_accumulator->accumulate(clip); } + clip = layer.layer_state.device_cull_rect(); + layer.layer_local_accumulator->accumulate(clip); return true; } @@ -1630,22 +1795,44 @@ bool DisplayListBuilder::AccumulateOpBounds(SkRect& bounds, return AccumulateUnbounded(); } } -bool DisplayListBuilder::AccumulateBounds(SkRect& bounds) { - if (!bounds.isEmpty()) { - SkRect device_bounds; - tracker_.mapRect(bounds, &device_bounds); - if (device_bounds.intersect(tracker_.device_cull_rect())) { - accumulator()->accumulate(device_bounds, op_index_); - if (current_layer_->layer_accumulator_) { - FML_DCHECK(layer_tracker_); - SkRect layer_bounds; - layer_tracker_->mapRect(bounds, &layer_bounds); - current_layer_->layer_accumulator_->accumulate(layer_bounds); - } - return true; - } + +bool DisplayListBuilder::AccumulateBounds(const SkRect& bounds, + SaveInfo& layer, + int id) { + if (bounds.isEmpty()) { + return false; } - return false; + SkRect device_bounds; + layer.global_state.mapRect(bounds, &device_bounds); + if (!device_bounds.intersect(layer.global_state.device_cull_rect())) { + return false; + } + if (rtree_data_.has_value()) { + FML_DCHECK(!layer.global_space_accumulator); + if (id >= 0) { + rtree_data_->rects.push_back(device_bounds); + rtree_data_->indices.push_back(id); + } + } else { + FML_DCHECK(layer.global_space_accumulator); + layer.global_space_accumulator->accumulate(device_bounds); + } + SkRect layer_bounds; + layer.layer_state.mapRect(bounds, &layer_bounds); + layer.layer_local_accumulator->accumulate(layer_bounds); + return true; +} + +bool DisplayListBuilder::SaveInfo::AccumulateBoundsLocal(const SkRect& bounds) { + if (bounds.isEmpty()) { + return false; + } + SkRect local_bounds; + layer_state.mapRect(bounds, &local_bounds); + if (local_bounds.intersect(layer_state.device_cull_rect())) { + layer_local_accumulator->accumulate(local_bounds); + } + return true; } bool DisplayListBuilder::paint_nops_on_transparency() { @@ -1769,7 +1956,7 @@ DlColor DisplayListBuilder::GetEffectiveColor(const DlPaint& paint, DisplayListBuilder::OpResult DisplayListBuilder::PaintResult( const DlPaint& paint, DisplayListAttributeFlags flags) { - if (current_layer_->is_nop_) { + if (current_info().is_nop) { return OpResult::kNoEffect; } if (flags.applies_blend()) { diff --git a/engine/src/flutter/display_list/dl_builder.h b/engine/src/flutter/display_list/dl_builder.h index 64ccd65e216..54fdf6b2c1e 100644 --- a/engine/src/flutter/display_list/dl_builder.h +++ b/engine/src/flutter/display_list/dl_builder.h @@ -13,8 +13,9 @@ #include "flutter/display_list/dl_paint.h" #include "flutter/display_list/dl_sampling_options.h" #include "flutter/display_list/effects/dl_path_effect.h" +#include "flutter/display_list/geometry/dl_geometry_types.h" #include "flutter/display_list/image/dl_image.h" -#include "flutter/display_list/utils/dl_bounds_accumulator.h" +#include "flutter/display_list/utils/dl_accumulation_rect.h" #include "flutter/display_list/utils/dl_comparable.h" #include "flutter/display_list/utils/dl_matrix_clip_tracker.h" #include "flutter/fml/macros.h" @@ -55,7 +56,7 @@ class DisplayListBuilder final : public virtual DlCanvas, // |DlCanvas| void Restore() override; // |DlCanvas| - int GetSaveCount() const override { return layer_stack_.size(); } + int GetSaveCount() const override { return save_stack_.size(); } // |DlCanvas| void RestoreToCount(int restore_count) override; @@ -104,13 +105,13 @@ class DisplayListBuilder final : public virtual DlCanvas, /// save stack. // |DlCanvas| SkM44 GetTransformFullPerspective() const override { - return tracker_.matrix_4x4(); + return global_state().matrix_4x4(); } /// Returns the 3x3 partial perspective transform representing all transform /// operations executed so far in this DisplayList within the enclosing /// save stack. // |DlCanvas| - SkMatrix GetTransform() const override { return tracker_.matrix_3x3(); } + SkMatrix GetTransform() const override { return global_state().matrix_3x3(); } // |DlCanvas| void ClipRect(const SkRect& rect, @@ -130,14 +131,14 @@ class DisplayListBuilder final : public virtual DlCanvas, /// be rendered. // |DlCanvas| SkRect GetDestinationClipBounds() const override { - return tracker_.device_cull_rect(); + return global_state().device_cull_rect(); } /// Conservative estimate of the bounds of all outstanding clip operations /// transformed into the local coordinate space in which currently /// recorded rendering operations are interpreted. // |DlCanvas| SkRect GetLocalClipBounds() const override { - return tracker_.local_cull_rect(); + return global_state().local_cull_rect(); } /// Return true iff the supplied bounds are easily shown to be outside @@ -504,114 +505,174 @@ class DisplayListBuilder final : public virtual DlCanvas, template void* Push(size_t extra, Args&&... args); - void intersect(const SkRect& rect); - - // kInvalidSigma is used to indicate that no MaskBlur is currently set. - static constexpr SkScalar kInvalidSigma = 0.0; - static bool mask_sigma_valid(SkScalar sigma) { - return std::isfinite(sigma) && sigma > 0.0; - } - - class SaveInfo { - public: - explicit SaveInfo(size_t save_offset = 0, uint32_t start_depth = 0) - : save_offset_(save_offset), start_depth_(start_depth) {} - - // The offset into the memory buffer where the save DLOp record - // for this save() call is placed. This may be needed if the - // eventual restore() call has discovered important information about - // the records inside the saveLayer that may impact how the saveLayer - // is handled (e.g., |cannot_inherit_opacity| == false). - // This offset is only valid if |has_layer| is true. - size_t save_offset() const { return save_offset_; } - - bool is_save_layer() const { return is_save_layer_; } - bool cannot_inherit_opacity() const { return cannot_inherit_opacity_; } - bool has_compatible_op() const { return has_compatible_op_; } - bool affects_transparent_layer() const { - return affects_transparent_layer_; - } - - bool is_group_opacity_compatible() const { - return !cannot_inherit_opacity_; - } - - void mark_incompatible() { cannot_inherit_opacity_ = true; } - - // For now this only allows a single compatible op to mark the - // layer as being compatible with group opacity. If we start - // computing bounds of ops in the Builder methods then we - // can upgrade this to checking for overlapping ops. - // See https://github.com/flutter/flutter/issues/93899 - void add_compatible_op() { - if (!cannot_inherit_opacity_) { - if (has_compatible_op_) { - cannot_inherit_opacity_ = true; - } else { - has_compatible_op_ = true; - } - } - } - - // Records that the current layer contains an op that produces visible - // output on a transparent surface. - void add_visible_op() { - affects_transparent_layer_ = true; - } - - // The filter to apply to the layer bounds when it is restored - std::shared_ptr filter() { return filter_; } - - // is_unbounded should be set to true if we ever encounter an operation - // on a layer that either is unrestricted (|drawColor| or |drawPaint|) - // or cannot compute its bounds (some effects and filters) and there - // was no outstanding clip op at the time. - // When the layer is restored, the outer layer may then process this - // unbounded state by accumulating its own clip or transferring the - // unbounded state to its own outer layer. - // Typically the DisplayList will have been constructed with a cull - // rect which will act as a default clip for the outermost layer and - // the unbounded state of all sub layers will eventually be caught by - // that cull rect so that the overall unbounded state of the entire - // DisplayList will never be true. - // - // For historical consistency it is worth noting that SkPicture used - // to treat these same conditions as a Nop (they accumulate the - // SkPicture cull rect, but if no cull rect was specified then it is - // an empty Rect and so has no effect on the bounds). - // - // Flutter is unlikely to ever run into this as the Dart mechanisms - // all supply a non-null cull rect for all Dart Picture objects, - // even if that cull rect is kGiantRect. - void set_unbounded() { is_unbounded_ = true; } - - // |is_unbounded| should be called after |getLayerBounds| in case - // a problem was found during the computation of those bounds, - // the layer will have one last chance to flag an unbounded state. - bool is_unbounded() const { return is_unbounded_; } - - private: - size_t save_offset_; - uint32_t start_depth_; - bool is_save_layer_ = false; - bool cannot_inherit_opacity_ = false; - bool has_compatible_op_ = false; - std::shared_ptr filter_; - bool is_unbounded_ = false; - bool has_deferred_save_op_ = false; - bool is_nop_ = false; - bool affects_transparent_layer_ = false; - std::shared_ptr layer_accumulator_; - - friend class DisplayListBuilder; + struct RTreeData { + std::vector rects; + std::vector indices; }; - std::vector layer_stack_; - SaveInfo* current_layer_; - DisplayListMatrixClipTracker tracker_; - std::unique_ptr layer_tracker_; - std::unique_ptr accumulator_; - BoundsAccumulator* accumulator() { return accumulator_.get(); } + // The SaveInfo class stores internal data for both Save and SaveLayer calls + class SaveInfo { + public: + // For vector reallocation calls to copy vector data + SaveInfo(const SaveInfo& copy) = default; + SaveInfo(SaveInfo&& copy) = default; + + // For constructor (root layer) initialization + explicit SaveInfo(const DlRect& cull_rect) + : is_root_layer(true), + is_save_layer(true), + global_state(cull_rect), + layer_state(cull_rect), + layer_local_accumulator(new AccumulationRect()) {} + + // For regular save calls: + // Passing a pointer to the parent_info so as to distinguish this + // call from the copy constructor used above in vector reallocations + explicit SaveInfo(const SaveInfo* parent_info) + : is_root_layer(false), + is_save_layer(false), + has_deferred_save_op(true), + global_state(parent_info->global_state), + layer_state(parent_info->layer_state), + global_space_accumulator(parent_info->global_space_accumulator), + layer_local_accumulator(parent_info->layer_local_accumulator) {} + + // For saveLayer calls: + explicit SaveInfo(const SaveInfo* parent_info, + const std::shared_ptr& filter, + int rtree_rect_index) + : is_root_layer(false), + is_save_layer(true), + rtree_rects_start_index(rtree_rect_index), + global_state(parent_info->global_state), + layer_state(parent_info->global_state.local_cull_rect()), + global_space_accumulator(parent_info->global_space_accumulator), + layer_local_accumulator(new AccumulationRect()), + filter(filter) {} + + bool is_group_opacity_compatible() const { + return !opacity_incompatible_op_detected && + !layer_local_accumulator->overlap_detected(); + } + + // Records the given bounds after transforming by the global and + // layer matrices. + bool AccumulateBoundsLocal(const SkRect& bounds); + + // Simply transfers the local bounds to the parent + void TransferBoundsToParent(const SaveInfo& parent); + + const bool is_root_layer; + const bool is_save_layer; + + bool has_deferred_save_op = false; + bool is_nop = false; + bool opacity_incompatible_op_detected = false; + bool is_unbounded = false; + bool affects_transparent_layer = false; + + // The offset into the buffer where the associated save op is recorded + // (which is not necessarily the same as when the Save() method is called) + size_t save_offset = 0; + // The depth when the save call is recorded, used to compute the total + // depth of its content when the associated restore is called. + uint32_t save_depth = 0; + + // The index of the rtree rects when the saveLayer was called, used + // only in the case that the saveLayer has a filter so that the + // accumulated rects can be updated in the corresponding restore call. + const size_t rtree_rects_start_index = 0; + + // The transform and clip accumulated since the root of the DisplayList + DisplayListMatrixClipState global_state; + + // The transform and clip accumulated since the most recent saveLayer, + // used to compute and update its bounds when the restore is called. + DisplayListMatrixClipState layer_state; + + // Not every layer needs its own accumulator(s). In particular, the + // global accumulator is only used if we are not construting an rtree. + // Regular save calls will share both accumulators with their parent. + // Additionally, a saveLayer will separate its global accumulator from + // its parent (if not constructing an rtree) when it has a filter which + // requires it to post-adjust the bounds accumulated while recording + // its content. Finally, every saveLayer has its own local accumulator. + // + // All accumulations could occur in the local layer space, and then be + // transformed and accumulated into the parent as each layer is restored, + // but that technique would compound the bounds errors that happen when + // a list of transforms is performed serially on a rectangle (mainly + // when multiple rotation or skew transforms are involved). + + // The bounds accumulator for the entire DisplayList, relative to its root + std::shared_ptr global_space_accumulator; + + // The bounds accumulator to set/verify the bounds of the most recently + // invoked saveLayer call, relative to the root of that saveLayer + std::shared_ptr layer_local_accumulator; + + // The filter that will be applied to the contents of the saveLayer + // when it is restored into the parent layer. + const std::shared_ptr filter; + }; + + const DlRect original_cull_rect_; + std::vector save_stack_; + std::optional rtree_data_; + + DlPaint current_; + + // Returns a reference to the SaveInfo structure at the top of the current + // save_stack state. Note that the clip and matrix state can be accessed + // more directly through global_state() and layer_state(). + SaveInfo& current_info() { return save_stack_.back(); } + const SaveInfo& current_info() const { return save_stack_.back(); } + + // Returns a reference to the SaveInfo structure just below the top + // of the current save_stack state. + SaveInfo& parent_info() { return *std::prev(save_stack_.end(), 2); } + const SaveInfo& parent_info() const { + return *std::prev(save_stack_.end(), 2); + } + + // Returns a reference to the matrix and clip state for the entire + // DisplayList. The initial transform of this state is identity and + // the initial cull_rect is the root original_cull_rect supplied + // in the constructor. It is a summary of all transform and clip + // calls that have happened since the DisplayList was created + // (and have not yet been removed by a restore() call). + DisplayListMatrixClipState& global_state() { + return current_info().global_state; + } + const DisplayListMatrixClipState& global_state() const { + return current_info().global_state; + } + + // Returns a reference to the matrix and clip state relative to the + // current layer, whether that is defined by the most recent saveLayer + // call, or by the initial root state of the entire DisplayList for + // calls not surrounded by a saveLayer/restore pair. It is a summary + // of only those transform and clip calls that have happened since + // the creation of the DisplayList or since the most recent saveLayer + // (and have not yet been removed by a restore() call). + DisplayListMatrixClipState& layer_local_state() { + return current_info().layer_state; + } + const DisplayListMatrixClipState& layer_local_state() const { + return current_info().layer_state; + } + + void RestoreLayer(const SaveInfo& current_info, + SaveInfo& parent_info, + void* base_op); + void TransferLayerBounds(const SaveInfo& current_info, + SaveInfo& parent_info, + const SkRect& content_bounds); + bool AdjustRTreeRects(RTreeData& data, + const DlImageFilter& filter, + const SkMatrix& matrix, + const SkRect& clip, + size_t rect_index); // This flag indicates whether or not the current rendering attributes // are compatible with rendering ops applying an inherited opacity. @@ -637,10 +698,8 @@ class DisplayListBuilder final : public virtual DlCanvas, // Update the opacity compatibility flags of the current layer for an op // that has determined its compatibility as indicated by |compatible|. void UpdateLayerOpacityCompatibility(bool compatible) { - if (compatible) { - current_layer_->add_compatible_op(); - } else { - current_layer_->mark_incompatible(); + if (!compatible) { + current_info().opacity_incompatible_op_detected = true; } } @@ -683,38 +742,6 @@ class DisplayListBuilder final : public virtual DlCanvas, void onSetPathEffect(const DlPathEffect* effect); void onSetMaskFilter(const DlMaskFilter* filter); - // The DisplayList had an unbounded call with no cull rect or clip - // to contain it. Should only be called after the stream is fully - // built. - // Unbounded operations are calls like |drawColor| which are defined - // to flood the entire surface, or calls that relied on a rendering - // attribute which is unable to compute bounds (should be rare). - // In those cases the bounds will represent only the accumulation - // of the bounded calls and this flag will be set to indicate that - // condition. - bool is_unbounded() const { - FML_DCHECK(layer_stack_.size() == 1); - return layer_stack_.front().is_unbounded(); - } - - SkRect bounds() const { - FML_DCHECK(layer_stack_.size() == 1); - if (is_unbounded()) { - FML_LOG(INFO) << "returning partial bounds for unbounded DisplayList"; - } - - return accumulator_->bounds(); - } - - sk_sp rtree() { - FML_DCHECK(layer_stack_.size() == 1); - if (is_unbounded()) { - FML_LOG(INFO) << "returning partial rtree for unbounded DisplayList"; - } - - return accumulator_->rtree(); - } - static DisplayListAttributeFlags FlagsForPointMode(PointMode mode); enum class OpResult { @@ -733,7 +760,7 @@ class DisplayListBuilder final : public virtual DlCanvas, case OpResult::kPreservesTransparency: break; case OpResult::kAffectsAll: - current_layer_->add_visible_op(); + current_info().affects_transparent_layer = true; break; } } @@ -752,7 +779,10 @@ class DisplayListBuilder final : public virtual DlCanvas, // Records the fact that we encountered an op that either could not // estimate its bounds or that fills all of the destination space. - bool AccumulateUnbounded(); + bool AccumulateUnbounded(SaveInfo& layer); + bool AccumulateUnbounded() { + return AccumulateUnbounded(current_info()); + } // Records the bounds for an op after modifying them according to the // supplied attribute flags and transforming by the current matrix. @@ -769,9 +799,10 @@ class DisplayListBuilder final : public virtual DlCanvas, // Records the given bounds after transforming by the current matrix // and clipping against the current clip. - bool AccumulateBounds(SkRect& bounds); - - DlPaint current_; + bool AccumulateBounds(const SkRect& bounds, SaveInfo& layer, int id); + bool AccumulateBounds(const SkRect& bounds) { + return AccumulateBounds(bounds, current_info(), op_index_); + } }; } // namespace flutter diff --git a/engine/src/flutter/display_list/dl_vertices.cc b/engine/src/flutter/display_list/dl_vertices.cc index cbbe6e1ea4a..c338a02a97a 100644 --- a/engine/src/flutter/display_list/dl_vertices.cc +++ b/engine/src/flutter/display_list/dl_vertices.cc @@ -4,7 +4,7 @@ #include "flutter/display_list/dl_vertices.h" -#include "flutter/display_list/utils/dl_bounds_accumulator.h" +#include "flutter/display_list/utils/dl_accumulation_rect.h" #include "flutter/fml/logging.h" namespace flutter { @@ -86,7 +86,7 @@ size_t DlVertices::size() const { } static SkRect compute_bounds(const SkPoint* points, int count) { - RectBoundsAccumulator accumulator; + AccumulationRect accumulator; for (int i = 0; i < count; i++) { accumulator.accumulate(points[i]); } diff --git a/engine/src/flutter/display_list/geometry/dl_geometry_types.h b/engine/src/flutter/display_list/geometry/dl_geometry_types.h new file mode 100644 index 00000000000..34b431cde4c --- /dev/null +++ b/engine/src/flutter/display_list/geometry/dl_geometry_types.h @@ -0,0 +1,72 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_DISPLAY_LIST_GEOMETRY_DL_GEOMETRY_TYPES_H_ +#define FLUTTER_DISPLAY_LIST_GEOMETRY_DL_GEOMETRY_TYPES_H_ + +#include "flutter/impeller/geometry/matrix.h" +#include "flutter/impeller/geometry/rect.h" +#include "flutter/impeller/geometry/scalar.h" + +#include "flutter/third_party/skia/include/core/SkM44.h" +#include "flutter/third_party/skia/include/core/SkMatrix.h" +#include "flutter/third_party/skia/include/core/SkRect.h" +#include "flutter/third_party/skia/include/core/SkSize.h" + +namespace flutter { + +using DlScalar = impeller::Scalar; +using DlDegrees = impeller::Degrees; +using DlRadians = impeller::Radians; + +using DlISize = impeller::ISize32; +using DlSize = impeller::Size; +using DlRect = impeller::Rect; +using DlIRect = impeller::IRect32; +using DlMatrix = impeller::Matrix; + +static_assert(sizeof(SkRect) == sizeof(DlRect)); + +inline const DlRect& ToDlRect(const SkRect& rect) { + return *reinterpret_cast(&rect); +} + +inline constexpr DlMatrix ToDlMatrix(const SkMatrix& matrix) { + // clang-format off + return DlMatrix::MakeColumn( + matrix[SkMatrix::kMScaleX], matrix[SkMatrix::kMSkewY], 0.0f, matrix[SkMatrix::kMPersp0], + matrix[SkMatrix::kMSkewX], matrix[SkMatrix::kMScaleY], 0.0f, matrix[SkMatrix::kMPersp1], + 0.0f, 0.0f, 1.0f, 0.0f, + matrix[SkMatrix::kMTransX], matrix[SkMatrix::kMTransY], 0.0f, matrix[SkMatrix::kMPersp2] + ); + // clang-format on +} + +inline constexpr DlMatrix ToDlMatrix(const SkM44& matrix) { + DlMatrix dl_matrix; + matrix.getColMajor(dl_matrix.m); + return dl_matrix; +} + +inline const SkRect& ToSkRect(const DlRect& rect) { + return *reinterpret_cast(&rect); +} + +inline const SkISize& ToSkISize(const DlISize& size) { + return *reinterpret_cast(&size); +} + +inline constexpr SkMatrix ToSkMatrix(const DlMatrix& matrix) { + return SkMatrix::MakeAll(matrix.m[0], matrix.m[4], matrix.m[12], // + matrix.m[1], matrix.m[5], matrix.m[13], // + matrix.m[3], matrix.m[7], matrix.m[15]); +} + +inline constexpr SkM44 ToSkM44(const DlMatrix& matrix) { + return SkM44::ColMajor(matrix.m); +} + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_GEOMETRY_DL_GEOMETRY_TYPES_H_ diff --git a/engine/src/flutter/display_list/utils/dl_accumulation_rect.cc b/engine/src/flutter/display_list/utils/dl_accumulation_rect.cc new file mode 100644 index 00000000000..cd882895d7f --- /dev/null +++ b/engine/src/flutter/display_list/utils/dl_accumulation_rect.cc @@ -0,0 +1,67 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/display_list/utils/dl_accumulation_rect.h" + +namespace flutter { + +void AccumulationRect::accumulate(SkScalar x, SkScalar y) { + if (!std::isfinite(x) || !std::isfinite(y)) { + return; + } + if (x >= min_x_ && x < max_x_ && y >= min_y_ && y < max_y_) { + record_overlapping_bounds(); + return; + } + if (min_x_ > x) { + min_x_ = x; + } + if (min_y_ > y) { + min_y_ = y; + } + if (max_x_ < x) { + max_x_ = x; + } + if (max_y_ < y) { + max_y_ = y; + } +} + +void AccumulationRect::accumulate(SkRect r) { + if (r.isEmpty()) { + return; + } + if (r.fLeft < max_x_ && r.fRight > min_x_ && // + r.fTop < max_y_ && r.fBottom > min_y_) { + record_overlapping_bounds(); + } + if (min_x_ > r.fLeft) { + min_x_ = r.fLeft; + } + if (min_y_ > r.fTop) { + min_y_ = r.fTop; + } + if (max_x_ < r.fRight) { + max_x_ = r.fRight; + } + if (max_y_ < r.fBottom) { + max_y_ = r.fBottom; + } +} + +SkRect AccumulationRect::bounds() const { + return (max_x_ >= min_x_ && max_y_ >= min_y_) + ? SkRect::MakeLTRB(min_x_, min_y_, max_x_, max_y_) + : SkRect::MakeEmpty(); +} + +void AccumulationRect::reset() { + min_x_ = std::numeric_limits::infinity(); + min_y_ = std::numeric_limits::infinity(); + max_x_ = -std::numeric_limits::infinity(); + max_y_ = -std::numeric_limits::infinity(); + overlap_detected_ = false; +} + +} // namespace flutter diff --git a/engine/src/flutter/display_list/utils/dl_accumulation_rect.h b/engine/src/flutter/display_list/utils/dl_accumulation_rect.h new file mode 100644 index 00000000000..ca492c17e9d --- /dev/null +++ b/engine/src/flutter/display_list/utils/dl_accumulation_rect.h @@ -0,0 +1,54 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_DISPLAY_LIST_UTILS_DL_ACCUMULATION_RECT_H_ +#define FLUTTER_DISPLAY_LIST_UTILS_DL_ACCUMULATION_RECT_H_ + +#include + +#include "flutter/display_list/geometry/dl_geometry_types.h" +#include "flutter/display_list/geometry/dl_rtree.h" +#include "flutter/fml/logging.h" + +namespace flutter { + +// Utility class to collect bounds from a bunch of rectangles and points +// while also noting if there might be any overlap between any of the data +// point/rects. Note that the overlap protection is not sophisticated, +// simply noting if the new data intersects with the already accumulated +// bounds. This can successfully detect non-overlap of a linear sequence +// of non-overlapping objects, or even a cross of non-overlapping objects +// as long as they are built out from the center in the right order. True +// detection of non-overlapping objects would require much more time and/or +// space. +class AccumulationRect { + public: + AccumulationRect() { reset(); } + + void accumulate(SkScalar x, SkScalar y); + void accumulate(SkPoint p) { accumulate(p.fX, p.fY); } + void accumulate(SkRect r); + void accumulate(DlRect r) { accumulate(ToSkRect(r)); } + + bool is_empty() const { return min_x_ >= max_x_ || min_y_ >= max_y_; } + bool is_not_empty() const { return min_x_ < max_x_ && min_y_ < max_y_; } + + SkRect bounds() const; + + void reset(); + + bool overlap_detected() const { return overlap_detected_; } + void record_overlapping_bounds() { overlap_detected_ = true; } + + private: + DlScalar min_x_; + DlScalar min_y_; + DlScalar max_x_; + DlScalar max_y_; + bool overlap_detected_; +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_UTILS_DL_ACCUMULATION_RECT_H_ diff --git a/engine/src/flutter/display_list/utils/dl_bounds_accumulator.cc b/engine/src/flutter/display_list/utils/dl_bounds_accumulator.cc deleted file mode 100644 index 34750ed4bd5..00000000000 --- a/engine/src/flutter/display_list/utils/dl_bounds_accumulator.cc +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "flutter/display_list/utils/dl_bounds_accumulator.h" - -namespace flutter { - -void RectBoundsAccumulator::accumulate(const SkRect& r, int index) { - if (r.fLeft < r.fRight && r.fTop < r.fBottom) { - rect_.accumulate(r.fLeft, r.fTop); - rect_.accumulate(r.fRight, r.fBottom); - } -} - -void RectBoundsAccumulator::save() { - saved_rects_.emplace_back(rect_); - rect_ = AccumulationRect(); -} -void RectBoundsAccumulator::restore() { - if (!saved_rects_.empty()) { - SkRect layer_bounds = rect_.bounds(); - pop_and_accumulate(layer_bounds, nullptr); - } -} -bool RectBoundsAccumulator::restore( - std::function mapper, - const SkRect* clip) { - bool success = true; - if (!saved_rects_.empty()) { - SkRect layer_bounds = rect_.bounds(); - success = mapper(layer_bounds, layer_bounds); - pop_and_accumulate(layer_bounds, clip); - } - return success; -} -void RectBoundsAccumulator::pop_and_accumulate(SkRect& layer_bounds, - const SkRect* clip) { - FML_DCHECK(!saved_rects_.empty()); - - rect_ = saved_rects_.back(); - saved_rects_.pop_back(); - - if (clip == nullptr || layer_bounds.intersect(*clip)) { - accumulate(layer_bounds, -1); - } -} - -RectBoundsAccumulator::AccumulationRect::AccumulationRect() { - min_x_ = std::numeric_limits::infinity(); - min_y_ = std::numeric_limits::infinity(); - max_x_ = -std::numeric_limits::infinity(); - max_y_ = -std::numeric_limits::infinity(); -} -void RectBoundsAccumulator::AccumulationRect::accumulate(SkScalar x, - SkScalar y) { - if (min_x_ > x) { - min_x_ = x; - } - if (min_y_ > y) { - min_y_ = y; - } - if (max_x_ < x) { - max_x_ = x; - } - if (max_y_ < y) { - max_y_ = y; - } -} -SkRect RectBoundsAccumulator::AccumulationRect::bounds() const { - return (max_x_ >= min_x_ && max_y_ >= min_y_) - ? SkRect::MakeLTRB(min_x_, min_y_, max_x_, max_y_) - : SkRect::MakeEmpty(); -} - -void RTreeBoundsAccumulator::accumulate(const SkRect& r, int index) { - if (r.fLeft < r.fRight && r.fTop < r.fBottom) { - rects_.push_back(r); - rect_indices_.push_back(index); - } -} -void RTreeBoundsAccumulator::save() { - saved_offsets_.push_back(rects_.size()); -} -void RTreeBoundsAccumulator::restore() { - if (saved_offsets_.empty()) { - return; - } - - saved_offsets_.pop_back(); -} -bool RTreeBoundsAccumulator::restore( - std::function map, - const SkRect* clip) { - if (saved_offsets_.empty()) { - return true; - } - - size_t previous_size = saved_offsets_.back(); - saved_offsets_.pop_back(); - - bool success = true; - for (size_t i = previous_size; i < rects_.size(); i++) { - SkRect original = rects_[i]; - if (!map(original, original)) { - success = false; - } - if (clip == nullptr || original.intersect(*clip)) { - rect_indices_[previous_size] = rect_indices_[i]; - rects_[previous_size] = original; - previous_size++; - } - } - rects_.resize(previous_size); - rect_indices_.resize(previous_size); - return success; -} - -SkRect RTreeBoundsAccumulator::bounds() const { - FML_DCHECK(saved_offsets_.empty()); - RectBoundsAccumulator accumulator; - for (auto& rect : rects_) { - accumulator.accumulate(rect, 0); - } - return accumulator.bounds(); -} - -sk_sp RTreeBoundsAccumulator::rtree() const { - FML_DCHECK(saved_offsets_.empty()); - return sk_make_sp(rects_.data(), rects_.size(), rect_indices_.data(), - [](int id) { return id >= 0; }); -} - -} // namespace flutter diff --git a/engine/src/flutter/display_list/utils/dl_bounds_accumulator.h b/engine/src/flutter/display_list/utils/dl_bounds_accumulator.h deleted file mode 100644 index db4ceda2698..00000000000 --- a/engine/src/flutter/display_list/utils/dl_bounds_accumulator.h +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_DISPLAY_LIST_UTILS_DL_BOUNDS_ACCUMULATOR_H_ -#define FLUTTER_DISPLAY_LIST_UTILS_DL_BOUNDS_ACCUMULATOR_H_ - -#include - -#include "flutter/display_list/geometry/dl_rtree.h" -#include "flutter/fml/logging.h" - -// This file contains various utility classes to ease implementing -// a Flutter DisplayList DlOpReceiver, including: -// -// IgnoreAttributeDispatchHelper: -// IgnoreClipDispatchHelper: -// IgnoreTransformDispatchHelper -// Empty overrides of all of the associated methods of DlOpReceiver -// for receivers that only track some of the rendering operations - -namespace flutter { - -enum class BoundsAccumulatorType { - kRect, - kRTree, -}; - -class BoundsAccumulator { - public: - /// function definition for modifying the bounds of a rectangle - /// during a restore operation. The function is used primarily - /// to account for the bounds impact of an ImageFilter on a - /// saveLayer on a per-rect basis. The implementation may apply - /// this function at whatever granularity it can manage easily - /// (for example, a Rect accumulator might apply it to the entire - /// local bounds being restored, whereas an RTree accumulator might - /// apply it individually to each element in the local RTree). - /// - /// The function will do a best faith attempt at determining the - /// modified bounds and store the results in the supplied |dest| - /// rectangle and return true. If the function is unable to - /// accurately determine the modifed bounds, it will set the - /// |dest| rectangle to a copy of the input bounds (or a best - /// guess) and return false to indicate that the bounds should not - /// be trusted. - typedef bool BoundsModifier(const SkRect& original, SkRect* dest); - - virtual ~BoundsAccumulator() = default; - - virtual void accumulate(const SkRect& r, int index = 0) = 0; - - /// Save aside the rects/bounds currently being accumulated and start - /// accumulating a new set of rects/bounds. When restore is called, - /// some additional modifications may be applied to these new bounds - /// before they are accumulated back into the surrounding bounds. - virtual void save() = 0; - - /// Restore to the previous accumulation and incorporate the bounds of - /// the primitives that were recorded since the last save (if needed). - virtual void restore() = 0; - - /// Restore the previous set of accumulation rects/bounds and accumulate - /// the current rects/bounds that were accumulated since the most recent - /// call to |save| into them with modifications specified by the |map| - /// parameter and clipping to the clip parameter if it is not null. - /// - /// The indicated map function is applied to the various rects and bounds - /// that have been accumulated in this save/restore cycle before they - /// are then accumulated into the previous accumulations. The granularity - /// of the application of the map function to the rectangles that were - /// accumulated during the save period is left up to the implementation. - /// - /// This method will return true if the map function returned true on - /// every single invocation. A false return value means that the - /// bounds accumulated during this restore may not be trusted (as - /// determined by the map function). - /// - /// If there are no saved accumulations to restore to, this method will - /// NOP ignoring the map function and the optional clip entirely. - virtual bool restore( - std::function map, - const SkRect* clip = nullptr) = 0; - - virtual SkRect bounds() const = 0; - - virtual sk_sp rtree() const = 0; - - virtual BoundsAccumulatorType type() const = 0; -}; - -class RectBoundsAccumulator final : public virtual BoundsAccumulator { - public: - void accumulate(SkScalar x, SkScalar y) { rect_.accumulate(x, y); } - void accumulate(const SkPoint& p) { rect_.accumulate(p.fX, p.fY); } - void accumulate(const SkRect& r, int index) override; - - bool is_empty() const { return rect_.is_empty(); } - bool is_not_empty() const { return rect_.is_not_empty(); } - - void save() override; - void restore() override; - bool restore(std::function mapper, - const SkRect* clip) override; - - SkRect bounds() const override { - FML_DCHECK(saved_rects_.empty()); - return rect_.bounds(); - } - - BoundsAccumulatorType type() const override { - return BoundsAccumulatorType::kRect; - } - - sk_sp rtree() const override { return nullptr; } - - private: - class AccumulationRect { - public: - AccumulationRect(); - - void accumulate(SkScalar x, SkScalar y); - - bool is_empty() const { return min_x_ >= max_x_ || min_y_ >= max_y_; } - bool is_not_empty() const { return min_x_ < max_x_ && min_y_ < max_y_; } - - SkRect bounds() const; - - private: - SkScalar min_x_; - SkScalar min_y_; - SkScalar max_x_; - SkScalar max_y_; - }; - - void pop_and_accumulate(SkRect& layer_bounds, const SkRect* clip); - - AccumulationRect rect_; - std::vector saved_rects_; -}; - -class RTreeBoundsAccumulator final : public virtual BoundsAccumulator { - public: - void accumulate(const SkRect& r, int index) override; - void save() override; - void restore() override; - - bool restore( - std::function map, - const SkRect* clip = nullptr) override; - - SkRect bounds() const override; - - sk_sp rtree() const override; - - BoundsAccumulatorType type() const override { - return BoundsAccumulatorType::kRTree; - } - - private: - std::vector rects_; - std::vector rect_indices_; - std::vector saved_offsets_; -}; - -} // namespace flutter - -#endif // FLUTTER_DISPLAY_LIST_UTILS_DL_BOUNDS_ACCUMULATOR_H_ diff --git a/engine/src/flutter/display_list/utils/dl_matrix_clip_tracker.cc b/engine/src/flutter/display_list/utils/dl_matrix_clip_tracker.cc index 6464889c0aa..9ed7f5d7ece 100644 --- a/engine/src/flutter/display_list/utils/dl_matrix_clip_tracker.cc +++ b/engine/src/flutter/display_list/utils/dl_matrix_clip_tracker.cc @@ -18,24 +18,37 @@ bool DisplayListMatrixClipTracker::is_3x3(const SkM44& m) { // clang-format on } +static constexpr DlRect kEmpty = DlRect(); + +static const DlRect& ProtectEmpty(const SkRect& rect) { + // isEmpty protects us against NaN while we normalize any empty cull rects + return rect.isEmpty() ? kEmpty : ToDlRect(rect); +} + +static const DlRect& ProtectEmpty(const DlRect& rect) { + // isEmpty protects us against NaN while we normalize any empty cull rects + return rect.IsEmpty() ? kEmpty : rect; +} + DisplayListMatrixClipState::DisplayListMatrixClipState(const DlRect& cull_rect, const DlMatrix& matrix) - : cull_rect_(cull_rect), matrix_(matrix) {} + : cull_rect_(ProtectEmpty(cull_rect)), matrix_(matrix) {} + +DisplayListMatrixClipState::DisplayListMatrixClipState(const SkRect& cull_rect) + : cull_rect_(ProtectEmpty(cull_rect)), matrix_(DlMatrix()) {} DisplayListMatrixClipState::DisplayListMatrixClipState(const SkRect& cull_rect, const SkMatrix& matrix) - : cull_rect_(ToDlRect(cull_rect)), matrix_(ToDlMatrix(matrix)) {} + : cull_rect_(ProtectEmpty(cull_rect)), matrix_(ToDlMatrix(matrix)) {} DisplayListMatrixClipState::DisplayListMatrixClipState(const SkRect& cull_rect, const SkM44& matrix) - : cull_rect_(ToDlRect(cull_rect)), matrix_(ToDlMatrix(matrix)) {} + : cull_rect_(ProtectEmpty(cull_rect)), matrix_(ToDlMatrix(matrix)) {} DisplayListMatrixClipTracker::DisplayListMatrixClipTracker( const DlRect& cull_rect, const DlMatrix& matrix) { - // isEmpty protects us against NaN as we normalize any empty cull rects - DlRect cull = cull_rect.IsEmpty() ? DlRect() : cull_rect; - saved_.emplace_back(cull, matrix); + saved_.emplace_back(cull_rect, matrix); current_ = &saved_.back(); save(); // saved_[0] will always be the initial settings } @@ -43,9 +56,7 @@ DisplayListMatrixClipTracker::DisplayListMatrixClipTracker( DisplayListMatrixClipTracker::DisplayListMatrixClipTracker( const SkRect& cull_rect, const SkMatrix& matrix) { - // isEmpty protects us against NaN as we normalize any empty cull rects - SkRect cull = cull_rect.isEmpty() ? SkRect::MakeEmpty() : cull_rect; - saved_.emplace_back(cull, matrix); + saved_.emplace_back(cull_rect, matrix); current_ = &saved_.back(); save(); // saved_[0] will always be the initial settings } @@ -53,9 +64,7 @@ DisplayListMatrixClipTracker::DisplayListMatrixClipTracker( DisplayListMatrixClipTracker::DisplayListMatrixClipTracker( const SkRect& cull_rect, const SkM44& m44) { - // isEmpty protects us against NaN as we normalize any empty cull rects - SkRect cull = cull_rect.isEmpty() ? SkRect::MakeEmpty() : cull_rect; - saved_.emplace_back(cull, m44); + saved_.emplace_back(cull_rect, m44); current_ = &saved_.back(); save(); // saved_[0] will always be the initial settings } diff --git a/engine/src/flutter/display_list/utils/dl_matrix_clip_tracker.h b/engine/src/flutter/display_list/utils/dl_matrix_clip_tracker.h index 286b395fb33..1c8f33992f6 100644 --- a/engine/src/flutter/display_list/utils/dl_matrix_clip_tracker.h +++ b/engine/src/flutter/display_list/utils/dl_matrix_clip_tracker.h @@ -8,9 +8,8 @@ #include #include "flutter/display_list/dl_canvas.h" +#include "flutter/display_list/geometry/dl_geometry_types.h" #include "flutter/fml/logging.h" -#include "flutter/impeller/geometry/matrix.h" -#include "flutter/impeller/geometry/rect.h" #include "third_party/skia/include/core/SkM44.h" #include "third_party/skia/include/core/SkMatrix.h" @@ -24,49 +23,11 @@ namespace flutter { class DisplayListMatrixClipState { private: using ClipOp = DlCanvas::ClipOp; - using DlRect = impeller::Rect; - using DlMatrix = impeller::Matrix; - using DlDegrees = impeller::Degrees; - - static_assert(sizeof(SkRect) == sizeof(DlRect)); - - static const DlRect& ToDlRect(const SkRect& rect) { - return *reinterpret_cast(&rect); - } - - static constexpr DlMatrix ToDlMatrix(const SkMatrix& matrix) { - // clang-format off - return DlMatrix::MakeColumn( - matrix[SkMatrix::kMScaleX], matrix[SkMatrix::kMSkewY], 0.0f, matrix[SkMatrix::kMPersp0], - matrix[SkMatrix::kMSkewX], matrix[SkMatrix::kMScaleY], 0.0f, matrix[SkMatrix::kMPersp1], - 0.0f, 0.0f, 1.0f, 0.0f, - matrix[SkMatrix::kMTransX], matrix[SkMatrix::kMTransY], 0.0f, matrix[SkMatrix::kMPersp2] - ); - // clang-format on - } - - static constexpr DlMatrix ToDlMatrix(const SkM44& matrix) { - DlMatrix dl_matrix; - matrix.getColMajor(dl_matrix.m); - return dl_matrix; - } - - static const SkRect& ToSkRect(const DlRect& rect) { - return *reinterpret_cast(&rect); - } - - static constexpr SkMatrix ToSkMatrix(const DlMatrix& matrix) { - return SkMatrix::MakeAll(matrix.m[0], matrix.m[4], matrix.m[12], - matrix.m[1], matrix.m[5], matrix.m[13], - matrix.m[3], matrix.m[7], matrix.m[15]); - } - - static constexpr SkM44 ToSkM44(const DlMatrix& matrix) { - return SkM44::ColMajor(matrix.m); - } public: - DisplayListMatrixClipState(const DlRect& cull_rect, const DlMatrix& matrix); + explicit DisplayListMatrixClipState(const DlRect& cull_rect, + const DlMatrix& matrix = DlMatrix()); + explicit DisplayListMatrixClipState(const SkRect& cull_rect); DisplayListMatrixClipState(const SkRect& cull_rect, const SkMatrix& matrix); DisplayListMatrixClipState(const SkRect& cull_rect, const SkM44& matrix); DisplayListMatrixClipState(const DisplayListMatrixClipState& other) = default; diff --git a/engine/src/flutter/impeller/geometry/rect.h b/engine/src/flutter/impeller/geometry/rect.h index 9757eb9b7bd..919141c160c 100644 --- a/engine/src/flutter/impeller/geometry/rect.h +++ b/engine/src/flutter/impeller/geometry/rect.h @@ -744,7 +744,9 @@ struct TRect { }; using Rect = TRect; -using IRect = TRect; +using IRect32 = TRect; +using IRect64 = TRect; +using IRect = IRect64; #undef ONLY_ON_FLOAT #undef ONLY_ON_FLOAT_M diff --git a/engine/src/flutter/impeller/geometry/size.h b/engine/src/flutter/impeller/geometry/size.h index 9841b63d326..45554734a15 100644 --- a/engine/src/flutter/impeller/geometry/size.h +++ b/engine/src/flutter/impeller/geometry/size.h @@ -135,7 +135,9 @@ constexpr TSize operator/(U s, const TSize& p) { } using Size = TSize; -using ISize = TSize; +using ISize32 = TSize; +using ISize64 = TSize; +using ISize = ISize64; static_assert(sizeof(Size) == 2 * sizeof(Scalar));