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));