From f2ee79012e278c5c16cca80d97d96e36ed26d8c2 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Fri, 23 Feb 2024 17:25:35 -0800 Subject: [PATCH 1/2] [DisplayList] Compute saveLayer bounds in DLBuilder when not provided --- display_list/benchmarking/dl_complexity_gl.cc | 5 +- display_list/benchmarking/dl_complexity_gl.h | 2 +- .../benchmarking/dl_complexity_metal.cc | 5 +- .../benchmarking/dl_complexity_metal.h | 2 +- display_list/display_list.h | 32 +- display_list/display_list_unittests.cc | 453 +++++++++++++++++- display_list/dl_builder.cc | 324 ++++++++++--- display_list/dl_builder.h | 34 +- display_list/dl_op_receiver.h | 33 +- display_list/dl_op_records.h | 73 +-- display_list/skia/dl_sk_dispatcher.cc | 7 +- display_list/skia/dl_sk_dispatcher.h | 2 +- display_list/testing/dl_test_snippets.cc | 8 +- display_list/utils/dl_matrix_clip_tracker.cc | 18 + display_list/utils/dl_matrix_clip_tracker.h | 8 + display_list/utils/dl_receiver_utils.h | 2 +- impeller/aiks/canvas.cc | 24 +- impeller/aiks/canvas.h | 24 +- impeller/aiks/canvas_recorder.h | 11 +- impeller/aiks/canvas_recorder_unittests.cc | 2 + impeller/aiks/paint_pass_delegate.cc | 5 +- impeller/aiks/trace_serializer.cc | 4 + impeller/aiks/trace_serializer.h | 2 + impeller/display_list/dl_dispatcher.cc | 7 +- impeller/display_list/dl_dispatcher.h | 2 +- impeller/entity/entity_pass.cc | 22 + impeller/entity/entity_pass.h | 38 ++ impeller/entity/entity_unittests.cc | 45 +- shell/common/dl_op_spy.cc | 2 +- shell/common/dl_op_spy.h | 2 +- .../verifyb143464703_soft_noxform.png | Bin 14266 -> 15747 bytes testing/display_list_testing.cc | 2 +- testing/display_list_testing.h | 2 +- 33 files changed, 1010 insertions(+), 192 deletions(-) diff --git a/display_list/benchmarking/dl_complexity_gl.cc b/display_list/benchmarking/dl_complexity_gl.cc index 53b70e2c24e47..a1f4c79de5d19 100644 --- a/display_list/benchmarking/dl_complexity_gl.cc +++ b/display_list/benchmarking/dl_complexity_gl.cc @@ -49,7 +49,7 @@ unsigned int DisplayListGLComplexityCalculator::GLHelper::BatchedComplexity() { } void DisplayListGLComplexityCalculator::GLHelper::saveLayer( - const SkRect* bounds, + const SkRect& bounds, const SaveLayerOptions options, const DlImageFilter* backdrop) { if (IsComplex()) { @@ -615,7 +615,8 @@ void DisplayListGLComplexityCalculator::GLHelper::drawDisplayList( } GLHelper helper(Ceiling() - CurrentComplexityScore()); if (opacity < SK_Scalar1 && !display_list->can_apply_group_opacity()) { - helper.saveLayer(nullptr, SaveLayerOptions::kWithAttributes, nullptr); + auto bounds = display_list->bounds(); + helper.saveLayer(bounds, SaveLayerOptions::kWithAttributes, nullptr); } display_list->Dispatch(helper); AccumulateComplexity(helper.ComplexityScore()); diff --git a/display_list/benchmarking/dl_complexity_gl.h b/display_list/benchmarking/dl_complexity_gl.h index d388c6f6a2547..c30926902ade3 100644 --- a/display_list/benchmarking/dl_complexity_gl.h +++ b/display_list/benchmarking/dl_complexity_gl.h @@ -35,7 +35,7 @@ class DisplayListGLComplexityCalculator explicit GLHelper(unsigned int ceiling) : ComplexityCalculatorHelper(ceiling) {} - void saveLayer(const SkRect* bounds, + void saveLayer(const SkRect& bounds, const SaveLayerOptions options, const DlImageFilter* backdrop) override; diff --git a/display_list/benchmarking/dl_complexity_metal.cc b/display_list/benchmarking/dl_complexity_metal.cc index c6b6547afee56..d24c1c643425e 100644 --- a/display_list/benchmarking/dl_complexity_metal.cc +++ b/display_list/benchmarking/dl_complexity_metal.cc @@ -63,7 +63,7 @@ DisplayListMetalComplexityCalculator::MetalHelper::BatchedComplexity() { } void DisplayListMetalComplexityCalculator::MetalHelper::saveLayer( - const SkRect* bounds, + const SkRect& bounds, const SaveLayerOptions options, const DlImageFilter* backdrop) { if (IsComplex()) { @@ -559,7 +559,8 @@ void DisplayListMetalComplexityCalculator::MetalHelper::drawDisplayList( } MetalHelper helper(Ceiling() - CurrentComplexityScore()); if (opacity < SK_Scalar1 && !display_list->can_apply_group_opacity()) { - helper.saveLayer(nullptr, SaveLayerOptions::kWithAttributes, nullptr); + auto bounds = display_list->bounds(); + helper.saveLayer(bounds, SaveLayerOptions::kWithAttributes, nullptr); } display_list->Dispatch(helper); AccumulateComplexity(helper.ComplexityScore()); diff --git a/display_list/benchmarking/dl_complexity_metal.h b/display_list/benchmarking/dl_complexity_metal.h index 4663b47836cd0..303e9c645d996 100644 --- a/display_list/benchmarking/dl_complexity_metal.h +++ b/display_list/benchmarking/dl_complexity_metal.h @@ -35,7 +35,7 @@ class DisplayListMetalComplexityCalculator explicit MetalHelper(unsigned int ceiling) : ComplexityCalculatorHelper(ceiling) {} - void saveLayer(const SkRect* bounds, + void saveLayer(const SkRect& bounds, const SaveLayerOptions options, const DlImageFilter* backdrop) override; diff --git a/display_list/display_list.h b/display_list/display_list.h index d7225865edb92..fe12048418972 100644 --- a/display_list/display_list.h +++ b/display_list/display_list.h @@ -88,9 +88,7 @@ namespace flutter { \ V(Save) \ V(SaveLayer) \ - V(SaveLayerBounds) \ V(SaveLayerBackdrop) \ - V(SaveLayerBackdropBounds) \ V(Restore) \ \ V(Translate) \ @@ -165,6 +163,7 @@ class SaveLayerOptions { SaveLayerOptions without_optimizations() const { SaveLayerOptions options; options.fRendersWithAttributes = fRendersWithAttributes; + options.fBoundsFromCaller = fBoundsFromCaller; return options; } @@ -182,6 +181,33 @@ class SaveLayerOptions { return options; } + // Returns true iff the bounds for the saveLayer operation were provided + // by the caller, otherwise the bounds will have been computed by the + // DisplayListBuilder and provided for reference. + bool bounds_from_caller() const { return fBoundsFromCaller; } + SaveLayerOptions with_bounds_from_caller() const { + SaveLayerOptions options(this); + options.fBoundsFromCaller = true; + return options; + } + SaveLayerOptions without_bounds_from_caller() const { + SaveLayerOptions options(this); + options.fBoundsFromCaller = false; + return options; + } + bool bounds_were_calculated() const { return !fBoundsFromCaller; } + + // Returns true iff the bounds for the saveLayer do not fully cover the + // contained rendering operations. This will only occur if the original + // caller supplied bounds and those bounds were not a strict superset + // of the content bounds computed by the DisplayListBuilder. + bool content_is_clipped() const { return fContentIsClipped; } + SaveLayerOptions with_content_is_clipped() const { + SaveLayerOptions options(this); + options.fContentIsClipped = true; + return options; + } + SaveLayerOptions& operator=(const SaveLayerOptions& other) { flags_ = other.flags_; return *this; @@ -198,6 +224,8 @@ class SaveLayerOptions { struct { unsigned fRendersWithAttributes : 1; unsigned fCanDistributeOpacity : 1; + unsigned fBoundsFromCaller : 1; + unsigned fContentIsClipped : 1; }; uint32_t flags_; }; diff --git a/display_list/display_list_unittests.cc b/display_list/display_list_unittests.cc index 242b34359a5e8..cc1912e877220 100644 --- a/display_list/display_list_unittests.cc +++ b/display_list/display_list_unittests.cc @@ -1227,7 +1227,7 @@ class SaveLayerOptionsExpector : public virtual DlOpReceiver, explicit SaveLayerOptionsExpector(std::vector expected) : expected_(std::move(expected)) {} - void saveLayer(const SkRect* bounds, + void saveLayer(const SkRect& bounds, const SaveLayerOptions options, const DlImageFilter* backdrop) override { EXPECT_EQ(options, expected_[save_layer_count_]); @@ -1555,7 +1555,7 @@ TEST_F(DisplayListTest, FlutterSvgIssue661BoundsWereEmpty) { // This is the more practical result. The bounds are "almost" 0,0,100x100 EXPECT_EQ(display_list->bounds().roundOut(), SkIRect::MakeWH(100, 100)); EXPECT_EQ(display_list->op_count(), 19u); - EXPECT_EQ(display_list->bytes(), sizeof(DisplayList) + 384u); + EXPECT_EQ(display_list->bytes(), sizeof(DisplayList) + 400u); } TEST_F(DisplayListTest, TranslateAffectsCurrentTransform) { @@ -3332,5 +3332,454 @@ TEST_F(DisplayListTest, ImpellerPathPreferenceIsHonored) { } } +class SaveLayerBoundsExpector : public virtual DlOpReceiver, + public IgnoreAttributeDispatchHelper, + public IgnoreClipDispatchHelper, + public IgnoreTransformDispatchHelper, + public IgnoreDrawDispatchHelper { + public: + explicit SaveLayerBoundsExpector() {} + + SaveLayerBoundsExpector& addComputedExpectation(const SkRect& bounds) { + expected_.emplace_back(BoundsExpectation{ + .bounds = bounds, + .options = SaveLayerOptions(), + }); + return *this; + } + + SaveLayerBoundsExpector& addSuppliedExpectation(const SkRect& bounds, + bool clipped = false) { + SaveLayerOptions options; + options = options.with_bounds_from_caller(); + if (clipped) { + options = options.with_content_is_clipped(); + } + expected_.emplace_back(BoundsExpectation{ + .bounds = bounds, + .options = options, + }); + return *this; + } + + void saveLayer(const SkRect& bounds, + const SaveLayerOptions options, + const DlImageFilter* backdrop) override { + ASSERT_LT(save_layer_count_, expected_.size()); + auto expected = expected_[save_layer_count_]; + EXPECT_EQ(options.bounds_from_caller(), + expected.options.bounds_from_caller()) + << "expected bounds index " << save_layer_count_; + EXPECT_EQ(options.content_is_clipped(), + expected.options.content_is_clipped()) + << "expected bounds index " << save_layer_count_; + if (!SkScalarNearlyEqual(bounds.fLeft, expected.bounds.fLeft) || + !SkScalarNearlyEqual(bounds.fTop, expected.bounds.fTop) || + !SkScalarNearlyEqual(bounds.fRight, expected.bounds.fRight) || + !SkScalarNearlyEqual(bounds.fBottom, expected.bounds.fBottom)) { + EXPECT_EQ(bounds, expected.bounds) + << "expected bounds index " << save_layer_count_; + } + save_layer_count_++; + } + + bool all_bounds_checked() const { + return save_layer_count_ == expected_.size(); + } + + private: + struct BoundsExpectation { + const SkRect bounds; + const SaveLayerOptions options; + }; + + std::vector expected_; + size_t save_layer_count_ = 0; +}; + +TEST_F(DisplayListTest, SaveLayerBoundsComputationOfSimpleRect) { + SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + + DisplayListBuilder builder; + builder.SaveLayer(nullptr, nullptr); + { // + builder.DrawRect(rect, DlPaint()); + } + builder.Restore(); + auto display_list = builder.Build(); + + SaveLayerBoundsExpector expector; + expector.addComputedExpectation(rect); + display_list->Dispatch(expector); + EXPECT_TRUE(expector.all_bounds_checked()); +} + +TEST_F(DisplayListTest, SaveLayerBoundsComputationOfMaskBlurredRect) { + SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + DlPaint draw_paint; + auto mask_filter = DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 2.0f); + draw_paint.setMaskFilter(mask_filter); + + DisplayListBuilder builder; + builder.SaveLayer(nullptr, nullptr); + { // + builder.DrawRect(rect, draw_paint); + } + builder.Restore(); + auto display_list = builder.Build(); + + SaveLayerBoundsExpector expector; + expector.addComputedExpectation(rect.makeOutset(6.0f, 6.0f)); + display_list->Dispatch(expector); + EXPECT_TRUE(expector.all_bounds_checked()); +} + +TEST_F(DisplayListTest, SaveLayerBoundsComputationOfImageBlurredRect) { + SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + DlPaint draw_paint; + auto image_filter = DlBlurImageFilter::Make(2.0f, 3.0f, DlTileMode::kDecal); + draw_paint.setImageFilter(image_filter); + + DisplayListBuilder builder; + builder.SaveLayer(nullptr, nullptr); + { // + builder.DrawRect(rect, draw_paint); + } + builder.Restore(); + auto display_list = builder.Build(); + + SaveLayerBoundsExpector expector; + expector.addComputedExpectation(rect.makeOutset(6.0f, 9.0f)); + display_list->Dispatch(expector); + EXPECT_TRUE(expector.all_bounds_checked()); +} + +TEST_F(DisplayListTest, SaveLayerBoundsComputationOfStrokedRect) { + SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + DlPaint draw_paint; + draw_paint.setStrokeWidth(5.0f); + draw_paint.setDrawStyle(DlDrawStyle::kStroke); + + DisplayListBuilder builder; + builder.SaveLayer(nullptr, nullptr); + { // + builder.DrawRect(rect, draw_paint); + } + builder.Restore(); + auto display_list = builder.Build(); + + SaveLayerBoundsExpector expector; + expector.addComputedExpectation(rect.makeOutset(2.5f, 2.5f)); + display_list->Dispatch(expector); + EXPECT_TRUE(expector.all_bounds_checked()); +} + +TEST_F(DisplayListTest, TranslatedSaveLayerBoundsComputationOfSimpleRect) { + SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + + DisplayListBuilder builder; + builder.Translate(10.0f, 10.0f); + builder.SaveLayer(nullptr, nullptr); + { // + builder.DrawRect(rect, DlPaint()); + } + builder.Restore(); + auto display_list = builder.Build(); + + SaveLayerBoundsExpector expector; + expector.addComputedExpectation(rect); + display_list->Dispatch(expector); + EXPECT_TRUE(expector.all_bounds_checked()); +} + +TEST_F(DisplayListTest, ScaledSaveLayerBoundsComputationOfSimpleRect) { + SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + + DisplayListBuilder builder; + builder.Scale(10.0f, 10.0f); + builder.SaveLayer(nullptr, nullptr); + { // + builder.DrawRect(rect, DlPaint()); + } + builder.Restore(); + auto display_list = builder.Build(); + + SaveLayerBoundsExpector expector; + expector.addComputedExpectation(rect); + display_list->Dispatch(expector); + EXPECT_TRUE(expector.all_bounds_checked()); +} + +TEST_F(DisplayListTest, RotatedSaveLayerBoundsComputationOfSimpleRect) { + SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + + DisplayListBuilder builder; + builder.Rotate(45.0f); + builder.SaveLayer(nullptr, nullptr); + { // + builder.DrawRect(rect, DlPaint()); + } + builder.Restore(); + auto display_list = builder.Build(); + + SaveLayerBoundsExpector expector; + expector.addComputedExpectation(rect); + display_list->Dispatch(expector); + EXPECT_TRUE(expector.all_bounds_checked()); +} + +TEST_F(DisplayListTest, TransformResetSaveLayerBoundsComputationOfSimpleRect) { + SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + SkRect rect_doubled = SkMatrix::Scale(2.0f, 2.0f).mapRect(rect); + + DisplayListBuilder builder; + builder.Scale(10.0f, 10.0f); + builder.SaveLayer(nullptr, nullptr); + builder.TransformReset(); + builder.Scale(20.0f, 20.0f); + // Net local transform for saveLayer is Scale(2, 2) + { // + builder.DrawRect(rect, DlPaint()); + } + builder.Restore(); + auto display_list = builder.Build(); + + SaveLayerBoundsExpector expector; + expector.addComputedExpectation(rect_doubled); + display_list->Dispatch(expector); + EXPECT_TRUE(expector.all_bounds_checked()); +} + +TEST_F(DisplayListTest, SaveLayerBoundsComputationOfTranslatedSimpleRect) { + SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + + DisplayListBuilder builder; + builder.SaveLayer(nullptr, nullptr); + { // + builder.Translate(10.0f, 10.0f); + builder.DrawRect(rect, DlPaint()); + } + builder.Restore(); + auto display_list = builder.Build(); + + SaveLayerBoundsExpector expector; + expector.addComputedExpectation(rect.makeOffset(10.0f, 10.0f)); + display_list->Dispatch(expector); + EXPECT_TRUE(expector.all_bounds_checked()); +} + +TEST_F(DisplayListTest, SaveLayerBoundsComputationOfScaledSimpleRect) { + SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + + DisplayListBuilder builder; + builder.SaveLayer(nullptr, nullptr); + { // + builder.Scale(10.0f, 10.0f); + builder.DrawRect(rect, DlPaint()); + } + builder.Restore(); + auto display_list = builder.Build(); + + SaveLayerBoundsExpector expector; + expector.addComputedExpectation( + SkRect::MakeLTRB(1000.0f, 1000.0f, 2000.0f, 2000.0f)); + display_list->Dispatch(expector); + EXPECT_TRUE(expector.all_bounds_checked()); +} + +TEST_F(DisplayListTest, SaveLayerBoundsComputationOfRotatedSimpleRect) { + SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + + DisplayListBuilder builder; + builder.SaveLayer(nullptr, nullptr); + { // + builder.Rotate(45.0f); + builder.DrawRect(rect, DlPaint()); + } + builder.Restore(); + auto display_list = builder.Build(); + + SkMatrix matrix = SkMatrix::RotateDeg(45.0f); + SaveLayerBoundsExpector expector; + expector.addComputedExpectation(matrix.mapRect(rect)); + display_list->Dispatch(expector); + EXPECT_TRUE(expector.all_bounds_checked()); +} + +TEST_F(DisplayListTest, SaveLayerBoundsComputationOfNestedSimpleRect) { + SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + + DisplayListBuilder builder; + builder.SaveLayer(nullptr, nullptr); + { // + builder.SaveLayer(nullptr, nullptr); + { // + builder.DrawRect(rect, DlPaint()); + } + builder.Restore(); + } + builder.Restore(); + auto display_list = builder.Build(); + + SaveLayerBoundsExpector expector; + expector.addComputedExpectation(rect); + expector.addComputedExpectation(rect); + display_list->Dispatch(expector); + EXPECT_TRUE(expector.all_bounds_checked()); +} + +TEST_F(DisplayListTest, FloodingSaveLayerBoundsComputationOfSimpleRect) { + SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + DlPaint save_paint; + auto color_filter = + DlBlendColorFilter::Make(DlColor::kRed(), DlBlendMode::kSrc); + ASSERT_TRUE(color_filter->modifies_transparent_black()); + save_paint.setColorFilter(color_filter); + SkRect clip_rect = rect.makeOutset(100.0f, 100.0f); + ASSERT_NE(clip_rect, rect); + ASSERT_TRUE(clip_rect.contains(rect)); + + DisplayListBuilder builder; + builder.ClipRect(clip_rect); + builder.SaveLayer(nullptr, &save_paint); + { // + builder.DrawRect(rect, DlPaint()); + } + builder.Restore(); + auto display_list = builder.Build(); + + SaveLayerBoundsExpector expector; + expector.addComputedExpectation(rect); + display_list->Dispatch(expector); + EXPECT_TRUE(expector.all_bounds_checked()); +} + +TEST_F(DisplayListTest, NestedFloodingSaveLayerBoundsComputationOfSimpleRect) { + SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + DlPaint save_paint; + auto color_filter = + DlBlendColorFilter::Make(DlColor::kRed(), DlBlendMode::kSrc); + ASSERT_TRUE(color_filter->modifies_transparent_black()); + save_paint.setColorFilter(color_filter); + SkRect clip_rect = rect.makeOutset(100.0f, 100.0f); + ASSERT_NE(clip_rect, rect); + ASSERT_TRUE(clip_rect.contains(rect)); + + DisplayListBuilder builder; + builder.ClipRect(clip_rect); + builder.SaveLayer(nullptr, nullptr); + { + builder.SaveLayer(nullptr, &save_paint); + { // + builder.DrawRect(rect, DlPaint()); + } + builder.Restore(); + } + builder.Restore(); + auto display_list = builder.Build(); + + SaveLayerBoundsExpector expector; + expector.addComputedExpectation(clip_rect); + expector.addComputedExpectation(rect); + display_list->Dispatch(expector); + EXPECT_TRUE(expector.all_bounds_checked()); +} + +TEST_F(DisplayListTest, SaveLayerBoundsComputationOfFloodingImageFilter) { + SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + DlPaint draw_paint; + auto color_filter = + DlBlendColorFilter::Make(DlColor::kRed(), DlBlendMode::kSrc); + ASSERT_TRUE(color_filter->modifies_transparent_black()); + auto image_filter = DlColorFilterImageFilter::Make(color_filter); + draw_paint.setImageFilter(image_filter); + SkRect clip_rect = rect.makeOutset(100.0f, 100.0f); + ASSERT_NE(clip_rect, rect); + ASSERT_TRUE(clip_rect.contains(rect)); + + DisplayListBuilder builder; + builder.ClipRect(clip_rect); + builder.SaveLayer(nullptr, nullptr); + { // + builder.DrawRect(rect, draw_paint); + } + builder.Restore(); + auto display_list = builder.Build(); + + SaveLayerBoundsExpector expector; + expector.addComputedExpectation(clip_rect); + display_list->Dispatch(expector); + EXPECT_TRUE(expector.all_bounds_checked()); +} + +TEST_F(DisplayListTest, SaveLayerBoundsComputationOfFloodingColorFilter) { + SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + DlPaint draw_paint; + auto color_filter = + DlBlendColorFilter::Make(DlColor::kRed(), DlBlendMode::kSrc); + ASSERT_TRUE(color_filter->modifies_transparent_black()); + draw_paint.setColorFilter(color_filter); + SkRect clip_rect = rect.makeOutset(100.0f, 100.0f); + ASSERT_NE(clip_rect, rect); + ASSERT_TRUE(clip_rect.contains(rect)); + + DisplayListBuilder builder; + builder.ClipRect(clip_rect); + builder.SaveLayer(nullptr, nullptr); + { // + builder.DrawRect(rect, draw_paint); + } + builder.Restore(); + auto display_list = builder.Build(); + + // A color filter is implicitly clipped to the draw bounds so the layer + // bounds will be the same as the draw bounds. + SaveLayerBoundsExpector expector; + expector.addComputedExpectation(rect); + display_list->Dispatch(expector); + EXPECT_TRUE(expector.all_bounds_checked()); +} + +TEST_F(DisplayListTest, SaveLayerBoundsClipDetectionSimpleUnclippedRect) { + SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + SkRect save_rect = SkRect::MakeLTRB(50.0f, 50.0f, 250.0f, 250.0f); + + DisplayListBuilder builder; + builder.SaveLayer(&save_rect, nullptr); + { // + builder.DrawRect(rect, DlPaint()); + } + builder.Restore(); + auto display_list = builder.Build(); + + // A color filter is implicitly clipped to the draw bounds so the layer + // bounds will be the same as the draw bounds. + SaveLayerBoundsExpector expector; + expector.addSuppliedExpectation(rect); + display_list->Dispatch(expector); + EXPECT_TRUE(expector.all_bounds_checked()); +} + +TEST_F(DisplayListTest, SaveLayerBoundsClipDetectionSimpleClippedRect) { + SkRect rect = SkRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + SkRect save_rect = SkRect::MakeLTRB(50.0f, 50.0f, 110.0f, 110.0f); + SkRect content_rect = SkRect::MakeLTRB(100.0f, 100.0f, 110.0f, 110.0f); + + DisplayListBuilder builder; + builder.SaveLayer(&save_rect, nullptr); + { // + builder.DrawRect(rect, DlPaint()); + } + builder.Restore(); + auto display_list = builder.Build(); + + // A color filter is implicitly clipped to the draw bounds so the layer + // bounds will be the same as the draw bounds. + SaveLayerBoundsExpector expector; + expector.addSuppliedExpectation(content_rect, true); + display_list->Dispatch(expector); + EXPECT_TRUE(expector.all_bounds_checked()); +} + } // namespace testing } // namespace flutter diff --git a/display_list/dl_builder.cc b/display_list/dl_builder.cc index e6ba24ba7f3a3..cefca96901706 100644 --- a/display_list/dl_builder.cc +++ b/display_list/dl_builder.cc @@ -85,7 +85,9 @@ sk_sp DisplayListBuilder::Build() { storage_.realloc(bytes); layer_stack_.pop_back(); layer_stack_.emplace_back(); + current_layer_ = &layer_stack_.back(); tracker_.reset(); + layer_tracker_.reset(); current_ = DlPaint(); return sk_sp(new DisplayList( @@ -389,62 +391,107 @@ void DisplayListBuilder::checkForDeferredSave() { } void DisplayListBuilder::Save() { - bool is_nop = current_layer_->is_nop_; layer_stack_.emplace_back(); current_layer_ = &layer_stack_.back(); + + 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_ = is_nop; + 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(); } void DisplayListBuilder::Restore() { - if (layer_stack_.size() > 1) { - SaveOpBase* op = reinterpret_cast( - storage_.get() + current_layer_->save_offset()); - if (!current_layer_->has_deferred_save_op_) { - op->restore_index = op_index_; - Push(0, 1); - } - // Grab the current layer info before we push the restore - // on the stack. - LayerInfo layer_info = layer_stack_.back(); - - tracker_.restore(); - layer_stack_.pop_back(); - current_layer_ = &layer_stack_.back(); - bool is_unbounded = layer_info.is_unbounded(); - - // Before we pop_back we will get the current layer bounds from the - // current accumulator and adjust it as required based on the filter. - std::shared_ptr filter = layer_info.filter(); - 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)) { - is_unbounded = true; - } - } else { - accumulator()->restore(); - } + if (layer_stack_.size() <= 1) { + return; + } - if (is_unbounded) { - AccumulateUnbounded(); - } + SaveOpBase* op = reinterpret_cast(storage_.get() + + current_layer_->save_offset()); - if (layer_info.has_layer()) { + if (!current_layer_->has_deferred_save_op_) { + op->restore_index = op_index_; + Push(0, 1); + } + + 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(!layer_info.has_deferred_save_op_); - if (layer_info.is_group_opacity_compatible()) { + 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. @@ -460,15 +507,53 @@ void DisplayListBuilder::Restore() { 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 values into the enclosing layer. - if (layer_info.cannot_inherit_opacity()) { - current_layer_->mark_incompatible(); - } else if (layer_info.has_compatible_op()) { - current_layer_->add_compatible_op(); + // 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()); @@ -476,7 +561,7 @@ void DisplayListBuilder::RestoreToCount(int restore_count) { restore(); } } -void DisplayListBuilder::saveLayer(const SkRect* bounds, +void DisplayListBuilder::saveLayer(const SkRect& bounds, const SaveLayerOptions in_options, const DlImageFilter* backdrop) { SaveLayerOptions options = in_options.without_optimizations(); @@ -489,7 +574,9 @@ void DisplayListBuilder::saveLayer(const SkRect* bounds, current_layer_->is_nop_ = true; return; } + size_t save_layer_offset = used_; + 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 @@ -508,17 +595,40 @@ void DisplayListBuilder::saveLayer(const SkRect* bounds, FML_DCHECK(unclipped); } CheckLayerOpacityCompatibility(true); - layer_stack_.emplace_back(save_layer_offset, true, - current_.getImageFilter()); + layer_stack_.emplace_back(save_layer_offset); + layer_stack_.back().filter_ = current_.getImageFilter(); } else { CheckLayerOpacityCompatibility(false); - layer_stack_.emplace_back(save_layer_offset, true, nullptr); + layer_stack_.emplace_back(save_layer_offset); } 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 // Accumulate should always return true here because if the @@ -526,13 +636,9 @@ void DisplayListBuilder::saveLayer(const SkRect* bounds, // when we tested the PaintResult. [[maybe_unused]] bool unclipped = AccumulateUnbounded(); FML_DCHECK(unclipped); - bounds // - ? Push(0, 1, options, *bounds, backdrop) - : Push(0, 1, options, backdrop); + Push(0, 1, options, record_bounds, backdrop); } else { - bounds // - ? Push(0, 1, options, *bounds) - : Push(0, 1, options); + Push(0, 1, options, record_bounds); } if (options.renders_with_attributes()) { @@ -558,24 +664,31 @@ void DisplayListBuilder::saveLayer(const SkRect* bounds, // 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_.resetCullRect(bounds); - } else if (bounds) { + tracker_.resetCullRect(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); + tracker_.clipRect(bounds, ClipOp::kIntersect, false); } } void DisplayListBuilder::SaveLayer(const SkRect* bounds, const DlPaint* paint, const DlImageFilter* backdrop) { + SaveLayerOptions options; + SkRect temp_bounds; + if (bounds) { + options = options.with_bounds_from_caller(); + temp_bounds = *bounds; + } else { + temp_bounds.setEmpty(); + } if (paint != nullptr) { + options = options.with_renders_with_attributes(); SetAttributesFromPaint(*paint, DisplayListOpFlags::kSaveLayerWithPaintFlags); - saveLayer(bounds, SaveLayerOptions::kWithAttributes, backdrop); - } else { - saveLayer(bounds, SaveLayerOptions::kNoAttributes, backdrop); } + saveLayer(temp_bounds, options, backdrop); } void DisplayListBuilder::Translate(SkScalar tx, SkScalar ty) { @@ -584,6 +697,9 @@ void DisplayListBuilder::Translate(SkScalar tx, SkScalar ty) { checkForDeferredSave(); Push(0, 1, tx, ty); tracker_.translate(tx, ty); + if (layer_tracker_) { + layer_tracker_->translate(tx, ty); + } } } void DisplayListBuilder::Scale(SkScalar sx, SkScalar sy) { @@ -592,6 +708,9 @@ void DisplayListBuilder::Scale(SkScalar sx, SkScalar sy) { checkForDeferredSave(); Push(0, 1, sx, sy); tracker_.scale(sx, sy); + if (layer_tracker_) { + layer_tracker_->scale(sx, sy); + } } } void DisplayListBuilder::Rotate(SkScalar degrees) { @@ -599,6 +718,9 @@ void DisplayListBuilder::Rotate(SkScalar degrees) { checkForDeferredSave(); Push(0, 1, degrees); tracker_.rotate(degrees); + if (layer_tracker_) { + layer_tracker_->rotate(degrees); + } } } void DisplayListBuilder::Skew(SkScalar sx, SkScalar sy) { @@ -607,6 +729,9 @@ void DisplayListBuilder::Skew(SkScalar sx, SkScalar sy) { checkForDeferredSave(); Push(0, 1, sx, sy); tracker_.skew(sx, sy); + if (layer_tracker_) { + layer_tracker_->skew(sx, sy); + } } } @@ -629,6 +754,10 @@ void DisplayListBuilder::Transform2DAffine( myx, myy, myt); tracker_.transform2DAffine(mxx, mxy, mxt, myx, myy, myt); + if (layer_tracker_) { + layer_tracker_->transform2DAffine(mxx, mxy, mxt, + myx, myy, myt); + } } } } @@ -658,12 +787,39 @@ void DisplayListBuilder::TransformFullPerspective( 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); + } } } // clang-format on void DisplayListBuilder::TransformReset() { checkForDeferredSave(); Push(0, 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)); + } + } tracker_.setIdentity(); } void DisplayListBuilder::Transform(const SkMatrix* matrix) { @@ -1360,16 +1516,6 @@ void DisplayListBuilder::DrawShadow(const SkPath& path, } } -bool DisplayListBuilder::ComputeFilteredBounds(SkRect& bounds, - const DlImageFilter* filter) { - if (filter) { - if (!filter->map_local_bounds(bounds, bounds)) { - return false; - } - } - return true; -} - bool DisplayListBuilder::AdjustBoundsForPaint(SkRect& bounds, DisplayListAttributeFlags flags) { if (flags.ignores_paint()) { @@ -1420,8 +1566,16 @@ bool DisplayListBuilder::AdjustBoundsForPaint(SkRect& bounds, } } + // Color filter does not modify bounds even if it affects transparent + // black because it is clipped by the "mask" of the primitive. That + // property only comes into play when it is applied to something like + // a layer. + if (flags.applies_image_filter()) { - return ComputeFilteredBounds(bounds, current_.getImageFilter().get()); + auto filter = current_.getImageFilterPtr(); + if (filter && !filter->map_local_bounds(bounds, bounds)) { + return false; + } } return true; @@ -1433,6 +1587,11 @@ bool DisplayListBuilder::AccumulateUnbounded() { 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()); + } return true; } @@ -1446,9 +1605,16 @@ bool DisplayListBuilder::AccumulateOpBounds(SkRect& bounds, } bool DisplayListBuilder::AccumulateBounds(SkRect& bounds) { if (!bounds.isEmpty()) { - tracker_.mapRect(&bounds); - if (bounds.intersect(tracker_.device_cull_rect())) { - accumulator()->accumulate(bounds, op_index_); + 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; } } diff --git a/display_list/dl_builder.h b/display_list/dl_builder.h index 69e48cb96d2d6..a1742f3a4305a 100644 --- a/display_list/dl_builder.h +++ b/display_list/dl_builder.h @@ -356,7 +356,7 @@ class DisplayListBuilder final : public virtual DlCanvas, // other flags will be ignored and calculated anew as the DisplayList is // built. Alternatively, use the |saveLayer(SkRect, bool)| method. // |DlOpReceiver| - void saveLayer(const SkRect* bounds, + void saveLayer(const SkRect& bounds, const SaveLayerOptions options, const DlImageFilter* backdrop) override; // |DlOpReceiver| @@ -508,25 +508,19 @@ class DisplayListBuilder final : public virtual DlCanvas, return SkScalarIsFinite(sigma) && sigma > 0.0; } - class LayerInfo { + class SaveInfo { public: - explicit LayerInfo( - size_t save_offset = 0, - bool has_layer = false, - const std::shared_ptr& filter = nullptr) - : save_offset_(save_offset), - has_layer_(has_layer), - filter_(filter) {} - - // The offset into the memory buffer where the saveLayer DLOp record - // for this saveLayer() call is placed. This may be needed if the + explicit SaveInfo(size_t save_offset = 0) : save_offset_(save_offset) {} + + // 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 has_layer() const { return has_layer_; } + 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 { @@ -593,7 +587,7 @@ class DisplayListBuilder final : public virtual DlCanvas, private: size_t save_offset_; - bool has_layer_; + bool is_save_layer_ = false; bool cannot_inherit_opacity_ = false; bool has_compatible_op_ = false; std::shared_ptr filter_; @@ -601,13 +595,15 @@ class DisplayListBuilder final : public virtual DlCanvas, bool has_deferred_save_op_ = false; bool is_nop_ = false; bool affects_transparent_layer_ = false; + std::shared_ptr layer_accumulator_; friend class DisplayListBuilder; }; - std::vector layer_stack_; - LayerInfo* current_layer_; + std::vector layer_stack_; + SaveInfo* current_layer_; DisplayListMatrixClipTracker tracker_; + std::unique_ptr layer_tracker_; std::unique_ptr accumulator_; BoundsAccumulator* accumulator() { return accumulator_.get(); } @@ -744,12 +740,6 @@ class DisplayListBuilder final : public virtual DlCanvas, static DlColor GetEffectiveColor(const DlPaint& paint, DisplayListAttributeFlags flags); - // Computes the bounds of an operation adjusted for a given ImageFilter - // and returns whether the computation was possible. If the method - // returns false then the caller should assume the worst about the bounds. - static bool ComputeFilteredBounds(SkRect& bounds, - const DlImageFilter* filter); - // Adjusts the indicated bounds for the given flags and returns true if // the calculation was possible, or false if it could not be estimated. bool AdjustBoundsForPaint(SkRect& bounds, DisplayListAttributeFlags flags); diff --git a/display_list/dl_op_receiver.h b/display_list/dl_op_receiver.h index dff97c3c15b7a..2eec9358273a7 100644 --- a/display_list/dl_op_receiver.h +++ b/display_list/dl_op_receiver.h @@ -122,6 +122,11 @@ class DlOpReceiver { // attributes will be applied to the save layer surface while rendering // it back to the current surface. If the flag is false then this method // is equivalent to |SkCanvas::saveLayer| with a null paint object. + // + // The |options| parameter can also specify whether the bounds came from + // the caller who recorded the operation, or whether they were calculated + // by the DisplayListBuilder. + // // The |options| parameter may contain other options that indicate some // specific optimizations may be made by the underlying implementation // to avoid creating a temporary layer, these optimization options will @@ -129,11 +134,37 @@ class DlOpReceiver { // specified in calling a |DisplayListBuilder| as they will be ignored. // The |backdrop| filter, if not null, is used to initialize the new // layer before further rendering happens. - virtual void saveLayer(const SkRect* bounds, + virtual void saveLayer(const SkRect& bounds, const SaveLayerOptions options, const DlImageFilter* backdrop = nullptr) = 0; virtual void restore() = 0; + // --------------------------------------------------------------------- + // Legacy helper method for older callers that use the null-ness of + // the bounds to indicate if they should be recorded or computed. + // This method will not be called on a |DlOpReceiver| that is passed + // to the |DisplayList::Dispatch()| method, so client receivers should + // ignore it for their implementation purposes. + // + // DlOpReceiver methods are generally meant to ONLY be output from a + // previously recorded DisplayList so this method is really only used + // from testing methods that bypass the public builder APIs for legacy + // convenience or for internal white-box testing of the DisplayList + // internals. Such methods should eventually be converted to using the + // public DisplayListBuilder/DlCanvas public interfaces where possible, + // as tracked in: + // https://github.com/flutter/flutter/issues/144070 + virtual void saveLayer(const SkRect* bounds, + const SaveLayerOptions options, + const DlImageFilter* backdrop = nullptr) final { + if (bounds) { + saveLayer(*bounds, options.with_bounds_from_caller(), backdrop); + } else { + saveLayer(SkRect(), options.without_bounds_from_caller(), backdrop); + } + } + // --------------------------------------------------------------------- + virtual void translate(SkScalar tx, SkScalar ty) = 0; virtual void scale(SkScalar sx, SkScalar sy) = 0; virtual void rotate(SkScalar degrees) = 0; diff --git a/display_list/dl_op_records.h b/display_list/dl_op_records.h index d0f12caea0318..bcd895a1fcad7 100644 --- a/display_list/dl_op_records.h +++ b/display_list/dl_op_records.h @@ -312,8 +312,8 @@ struct SetSharedImageFilterOp : DLOp { } }; -// The base object for all save() and saveLayer() ops -// 4 byte header + 8 byte payload packs neatly into 16 bytes (4 bytes unused) +// The base struct for all save() and saveLayer() ops +// 4 byte header + 8 byte payload packs into 16 bytes (4 bytes unused) struct SaveOpBase : DLOp { SaveOpBase() : options(), restore_index(0) {} @@ -333,7 +333,7 @@ struct SaveOpBase : DLOp { return needed; } }; -// 24 byte SaveOpBase with no additional data (options is unsed here) +// 16 byte SaveOpBase with no additional data (options is unsed here) struct SaveOp final : SaveOpBase { static const auto kType = DisplayListOpType::kSave; @@ -345,74 +345,45 @@ struct SaveOp final : SaveOpBase { } } }; -// 16 byte SaveOpBase with no additional data -struct SaveLayerOp final : SaveOpBase { - static const auto kType = DisplayListOpType::kSaveLayer; - - explicit SaveLayerOp(const SaveLayerOptions& options) : SaveOpBase(options) {} +// The base struct for all saveLayer() ops +// 16 byte SaveOpBase + 16 byte payload packs into 32 bytes (4 bytes unused) +struct SaveLayerOpBase : SaveOpBase { + SaveLayerOpBase(const SaveLayerOptions& options, const SkRect& rect) + : SaveOpBase(options), rect(rect) {} - void dispatch(DispatchContext& ctx) const { - if (save_needed(ctx)) { - ctx.receiver.saveLayer(nullptr, options); - } - } + SkRect rect; }; -// 24 byte SaveOpBase + 16 byte payload packs evenly into 40 bytes -struct SaveLayerBoundsOp final : SaveOpBase { - static const auto kType = DisplayListOpType::kSaveLayerBounds; - - SaveLayerBoundsOp(const SaveLayerOptions& options, const SkRect& rect) - : SaveOpBase(options), rect(rect) {} +// 32 byte SaveLayerOpBase with no additional data +struct SaveLayerOp final : SaveLayerOpBase { + static const auto kType = DisplayListOpType::kSaveLayer; - const SkRect rect; + SaveLayerOp(const SaveLayerOptions& options, const SkRect& rect) + : SaveLayerOpBase(options, rect) {} void dispatch(DispatchContext& ctx) const { if (save_needed(ctx)) { - ctx.receiver.saveLayer(&rect, options); + ctx.receiver.saveLayer(rect, options); } } }; -// 24 byte SaveOpBase + 16 byte payload packs into minimum 40 bytes -struct SaveLayerBackdropOp final : SaveOpBase { +// 32 byte SaveLayerOpBase + 16 byte payload packs into minimum 48 bytes +struct SaveLayerBackdropOp final : SaveLayerOpBase { static const auto kType = DisplayListOpType::kSaveLayerBackdrop; - explicit SaveLayerBackdropOp(const SaveLayerOptions& options, - const DlImageFilter* backdrop) - : SaveOpBase(options), backdrop(backdrop->shared()) {} + SaveLayerBackdropOp(const SaveLayerOptions& options, + const SkRect& rect, + const DlImageFilter* backdrop) + : SaveLayerOpBase(options, rect), backdrop(backdrop->shared()) {} const std::shared_ptr backdrop; void dispatch(DispatchContext& ctx) const { if (save_needed(ctx)) { - ctx.receiver.saveLayer(nullptr, options, backdrop.get()); + ctx.receiver.saveLayer(rect, options, backdrop.get()); } } DisplayListCompare equals(const SaveLayerBackdropOp* other) const { - return options == other->options && Equals(backdrop, other->backdrop) - ? DisplayListCompare::kEqual - : DisplayListCompare::kNotEqual; - } -}; -// 24 byte SaveOpBase + 32 byte payload packs into minimum 56 bytes -struct SaveLayerBackdropBoundsOp final : SaveOpBase { - static const auto kType = DisplayListOpType::kSaveLayerBackdropBounds; - - SaveLayerBackdropBoundsOp(const SaveLayerOptions& options, - const SkRect& rect, - const DlImageFilter* backdrop) - : SaveOpBase(options), rect(rect), backdrop(backdrop->shared()) {} - - const SkRect rect; - const std::shared_ptr backdrop; - - void dispatch(DispatchContext& ctx) const { - if (save_needed(ctx)) { - ctx.receiver.saveLayer(&rect, options, backdrop.get()); - } - } - - DisplayListCompare equals(const SaveLayerBackdropBoundsOp* other) const { return (options == other->options && rect == other->rect && Equals(backdrop, other->backdrop)) ? DisplayListCompare::kEqual diff --git a/display_list/skia/dl_sk_dispatcher.cc b/display_list/skia/dl_sk_dispatcher.cc index 13449c38ee99d..e75ccd96da4b9 100644 --- a/display_list/skia/dl_sk_dispatcher.cc +++ b/display_list/skia/dl_sk_dispatcher.cc @@ -43,10 +43,10 @@ void DlSkCanvasDispatcher::restore() { canvas_->restore(); restore_opacity(); } -void DlSkCanvasDispatcher::saveLayer(const SkRect* bounds, +void DlSkCanvasDispatcher::saveLayer(const SkRect& bounds, const SaveLayerOptions options, const DlImageFilter* backdrop) { - if (bounds == nullptr && options.can_distribute_opacity() && + if (!options.content_is_clipped() && options.can_distribute_opacity() && backdrop == nullptr) { // We know that: // - no bounds is needed for clipping here @@ -67,8 +67,9 @@ void DlSkCanvasDispatcher::saveLayer(const SkRect* bounds, TRACE_EVENT0("flutter", "Canvas::saveLayer"); const SkPaint* paint = safe_paint(options.renders_with_attributes()); const sk_sp sk_backdrop = ToSk(backdrop); + const SkRect* sl_bounds = options.bounds_from_caller() ? &bounds : nullptr; canvas_->saveLayer( - SkCanvas::SaveLayerRec(bounds, paint, sk_backdrop.get(), 0)); + SkCanvas::SaveLayerRec(sl_bounds, paint, sk_backdrop.get(), 0)); // saveLayer will apply the current opacity on behalf of the children // so they will inherit an opaque opacity. save_opacity(SK_Scalar1); diff --git a/display_list/skia/dl_sk_dispatcher.h b/display_list/skia/dl_sk_dispatcher.h index a9177925aaabd..0db4dce710d0f 100644 --- a/display_list/skia/dl_sk_dispatcher.h +++ b/display_list/skia/dl_sk_dispatcher.h @@ -29,7 +29,7 @@ class DlSkCanvasDispatcher : public virtual DlOpReceiver, void save() override; void restore() override; - void saveLayer(const SkRect* bounds, + void saveLayer(const SkRect& bounds, const SaveLayerOptions options, const DlImageFilter* backdrop) override; diff --git a/display_list/testing/dl_test_snippets.cc b/display_list/testing/dl_test_snippets.cc index c26d95ab91e99..4757833f99ef6 100644 --- a/display_list/testing/dl_test_snippets.cc +++ b/display_list/testing/dl_test_snippets.cc @@ -252,7 +252,7 @@ std::vector CreateAllSaveRestoreOps() { return { {"Save(Layer)+Restore", { - {5, 112, 5, 112, + {5, 128, 5, 128, [](DlOpReceiver& r) { r.saveLayer(nullptr, SaveLayerOptions::kNoAttributes, &kTestCFImageFilter1); @@ -276,7 +276,7 @@ std::vector CreateAllSaveRestoreOps() { r.drawRect({10, 10, 20, 20}); r.restore(); }}, - {5, 96, 5, 96, + {5, 112, 5, 112, [](DlOpReceiver& r) { r.saveLayer(nullptr, SaveLayerOptions::kNoAttributes); r.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true); @@ -284,7 +284,7 @@ std::vector CreateAllSaveRestoreOps() { r.drawRect({10, 10, 20, 20}); r.restore(); }}, - {5, 96, 5, 96, + {5, 112, 5, 112, [](DlOpReceiver& r) { r.saveLayer(nullptr, SaveLayerOptions::kWithAttributes); r.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true); @@ -317,7 +317,7 @@ std::vector CreateAllSaveRestoreOps() { // r.drawRect({10, 10, 20, 20}); // r.restore(); // }}, - {5, 112, 5, 112, + {5, 128, 5, 128, [](DlOpReceiver& r) { r.saveLayer(nullptr, SaveLayerOptions::kWithAttributes, &kTestCFImageFilter1); diff --git a/display_list/utils/dl_matrix_clip_tracker.cc b/display_list/utils/dl_matrix_clip_tracker.cc index 2dd0e8fa31a39..c4023aa840089 100644 --- a/display_list/utils/dl_matrix_clip_tracker.cc +++ b/display_list/utils/dl_matrix_clip_tracker.cc @@ -224,6 +224,24 @@ void DisplayListMatrixClipTracker::setTransform(const SkM44& m44) { current_->setTransform(m44); } +bool DisplayListMatrixClipTracker::inverseTransform( + const DisplayListMatrixClipTracker& tracker_) { + if (tracker_.using_4x4_matrix()) { + SkM44 inverse; + if (tracker_.matrix_4x4().invert(&inverse)) { + transform(inverse); + return true; + } + } else { + SkMatrix inverse; + if (tracker_.matrix_3x3().invert(&inverse)) { + transform(inverse); + return true; + } + } + return false; +} + void DisplayListMatrixClipTracker::clipRRect(const SkRRect& rrect, ClipOp op, bool is_aa) { diff --git a/display_list/utils/dl_matrix_clip_tracker.h b/display_list/utils/dl_matrix_clip_tracker.h index 81976a9e858d9..ca71e78b3df34 100644 --- a/display_list/utils/dl_matrix_clip_tracker.h +++ b/display_list/utils/dl_matrix_clip_tracker.h @@ -84,7 +84,15 @@ class DisplayListMatrixClipTracker { void setTransform(const SkMatrix& matrix) { current_->setTransform(matrix); } void setTransform(const SkM44& m44); void setIdentity() { current_->setIdentity(); } + // If the matrix in |other_tracker| is invertible then transform this + // tracker by the inverse of its matrix and return true. Otherwise, + // return false and leave this tracker unmodified. + bool inverseTransform(const DisplayListMatrixClipTracker& other_tracker); + bool mapRect(SkRect* rect) const { return current_->mapRect(*rect, rect); } + bool mapRect(const SkRect& src, SkRect* mapped) { + return current_->mapRect(src, mapped); + } void clipRect(const SkRect& rect, ClipOp op, bool is_aa) { current_->clipBounds(rect, op, is_aa); diff --git a/display_list/utils/dl_receiver_utils.h b/display_list/utils/dl_receiver_utils.h index 4450cefdbe5ba..a3b5bb156de86 100644 --- a/display_list/utils/dl_receiver_utils.h +++ b/display_list/utils/dl_receiver_utils.h @@ -78,7 +78,7 @@ class IgnoreTransformDispatchHelper : public virtual DlOpReceiver { class IgnoreDrawDispatchHelper : public virtual DlOpReceiver { public: void save() override {} - void saveLayer(const SkRect* bounds, + void saveLayer(const SkRect& bounds, const SaveLayerOptions options, const DlImageFilter* backdrop) override {} void restore() override {} diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc index 57f84496625b3..5ac6fbc37cac6 100644 --- a/impeller/aiks/canvas.cc +++ b/impeller/aiks/canvas.cc @@ -783,7 +783,8 @@ void Canvas::AddEntityToCurrentPass(Entity entity) { void Canvas::SaveLayer(const Paint& paint, std::optional bounds, - const std::shared_ptr& backdrop_filter) { + const std::shared_ptr& backdrop_filter, + SaveLayerBoundsPromise bounds_promise) { TRACE_EVENT0("flutter", "Canvas::saveLayer"); Save(true, paint.blend_mode, backdrop_filter); @@ -796,7 +797,26 @@ void Canvas::SaveLayer(const Paint& paint, } auto& new_layer_pass = GetCurrentPass(); - new_layer_pass.SetBoundsLimit(bounds); + if (bounds) { + new_layer_pass.SetBoundsLimit(bounds); + bool snug; + bool might_clip; + switch (bounds_promise) { + case SaveLayerBoundsPromise::kUnknown: + snug = false; + might_clip = true; + break; + case SaveLayerBoundsPromise::kContainsContents: + snug = true; + might_clip = false; + break; + case SaveLayerBoundsPromise::kClipsContents: + snug = true; + might_clip = true; + } + new_layer_pass.SetBoundsAreSnug(snug); + new_layer_pass.SetBoundsMightClipContent(might_clip); + } if (paint.image_filter) { MipCountVisitor mip_count_visitor; diff --git a/impeller/aiks/canvas.h b/impeller/aiks/canvas.h index 867c5b8535069..c144061e0b4af 100644 --- a/impeller/aiks/canvas.h +++ b/impeller/aiks/canvas.h @@ -55,6 +55,22 @@ enum class SourceRectConstraint { kStrict, }; +/// Controls how much to trust the bounds rectangle given to SaveLayer. +enum class SaveLayerBoundsPromise { + /// @brief The caller makes no claims related to the size of the bounds. + kUnknown, + + /// @brief The caller claims the bounds are a reasonably tight estimate + /// of the coverage of the contents and should contain all of the + /// contents. + kContainsContents, + + /// @brief The caller claims the bounds are a subset of an estimate of + /// the reasonably tight bounds but likely clips off some of the + /// contents. + kClipsContents, +}; + class Canvas { public: struct DebugOptions { @@ -75,9 +91,11 @@ class Canvas { void Save(); - void SaveLayer(const Paint& paint, - std::optional bounds = std::nullopt, - const std::shared_ptr& backdrop_filter = nullptr); + void SaveLayer( + const Paint& paint, + std::optional bounds = std::nullopt, + const std::shared_ptr& backdrop_filter = nullptr, + SaveLayerBoundsPromise bounds_promise = SaveLayerBoundsPromise::kUnknown); bool Restore(); diff --git a/impeller/aiks/canvas_recorder.h b/impeller/aiks/canvas_recorder.h index e0089111720b8..16bd739ea75bd 100644 --- a/impeller/aiks/canvas_recorder.h +++ b/impeller/aiks/canvas_recorder.h @@ -113,12 +113,13 @@ class CanvasRecorder { return ExecuteAndSerialize(CanvasRecorderOp::kSave, &Canvas::Save); } - void SaveLayer( - const Paint& paint, - std::optional bounds = std::nullopt, - const std::shared_ptr& backdrop_filter = nullptr) { + void SaveLayer(const Paint& paint, + std::optional bounds = std::nullopt, + const std::shared_ptr& backdrop_filter = nullptr, + SaveLayerBoundsPromise bounds_promise = + SaveLayerBoundsPromise::kUnknown) { return ExecuteAndSerialize(FLT_CANVAS_RECORDER_OP_ARG(SaveLayer), paint, - bounds, backdrop_filter); + bounds, backdrop_filter, bounds_promise); } bool Restore() { diff --git a/impeller/aiks/canvas_recorder_unittests.cc b/impeller/aiks/canvas_recorder_unittests.cc index 24ef24355febf..6ce9b10e08245 100644 --- a/impeller/aiks/canvas_recorder_unittests.cc +++ b/impeller/aiks/canvas_recorder_unittests.cc @@ -56,6 +56,8 @@ class Serializer { void Write(const SourceRectConstraint& src_rect_constraint) {} + void Write(const SaveLayerBoundsPromise& promise) {} + CanvasRecorderOp last_op_; }; } // namespace diff --git a/impeller/aiks/paint_pass_delegate.cc b/impeller/aiks/paint_pass_delegate.cc index fe42af0b7c2aa..a03fd49be83e3 100644 --- a/impeller/aiks/paint_pass_delegate.cc +++ b/impeller/aiks/paint_pass_delegate.cc @@ -71,8 +71,9 @@ bool OpacityPeepholePassDelegate::CanElide() { // |EntityPassDelgate| bool OpacityPeepholePassDelegate::CanCollapseIntoParentPass( EntityPass* entity_pass) { - // Passes with absorbed clips can not be safely collapsed. - if (entity_pass->GetBoundsLimit().has_value()) { + // Passes with enforced bounds that clip the contents can not be safely + // collapsed. + if (entity_pass->GetBoundsMightClipContent()) { return false; } diff --git a/impeller/aiks/trace_serializer.cc b/impeller/aiks/trace_serializer.cc index 09325cf432df9..c03ca38b6cd6f 100644 --- a/impeller/aiks/trace_serializer.cc +++ b/impeller/aiks/trace_serializer.cc @@ -258,4 +258,8 @@ void TraceSerializer::Write(const SourceRectConstraint& src_rect_constraint) { buffer_ << "[SourceRectConstraint] "; } +void TraceSerializer::Write(const SaveLayerBoundsPromise& promise) { + buffer_ << "[SaveLayerBoundsPromise]"; +} + } // namespace impeller diff --git a/impeller/aiks/trace_serializer.h b/impeller/aiks/trace_serializer.h index 0f777287bd992..d7e919a82d883 100644 --- a/impeller/aiks/trace_serializer.h +++ b/impeller/aiks/trace_serializer.h @@ -60,6 +60,8 @@ class TraceSerializer { void Write(const SourceRectConstraint& src_rect_constraint); + void Write(const SaveLayerBoundsPromise& promise); + private: std::stringstream buffer_; }; diff --git a/impeller/display_list/dl_dispatcher.cc b/impeller/display_list/dl_dispatcher.cc index d999c5d9eaae4..60e38ff83ad4d 100644 --- a/impeller/display_list/dl_dispatcher.cc +++ b/impeller/display_list/dl_dispatcher.cc @@ -623,12 +623,15 @@ void DlDispatcher::save() { } // |flutter::DlOpReceiver| -void DlDispatcher::saveLayer(const SkRect* bounds, +void DlDispatcher::saveLayer(const SkRect& bounds, const flutter::SaveLayerOptions options, const flutter::DlImageFilter* backdrop) { auto paint = options.renders_with_attributes() ? paint_ : Paint{}; + auto promise = options.content_is_clipped() + ? SaveLayerBoundsPromise::kClipsContents + : SaveLayerBoundsPromise::kContainsContents; canvas_.SaveLayer(paint, skia_conversions::ToRect(bounds), - ToImageFilter(backdrop)); + ToImageFilter(backdrop), promise); } // |flutter::DlOpReceiver| diff --git a/impeller/display_list/dl_dispatcher.h b/impeller/display_list/dl_dispatcher.h index 4ef1da1c29aea..4643e43ad1c94 100644 --- a/impeller/display_list/dl_dispatcher.h +++ b/impeller/display_list/dl_dispatcher.h @@ -72,7 +72,7 @@ class DlDispatcher final : public flutter::DlOpReceiver { void save() override; // |flutter::DlOpReceiver| - void saveLayer(const SkRect* bounds, + void saveLayer(const SkRect& bounds, const flutter::SaveLayerOptions options, const flutter::DlImageFilter* backdrop) override; diff --git a/impeller/entity/entity_pass.cc b/impeller/entity/entity_pass.cc index d84e8f935b575..d6dadad5b3c21 100644 --- a/impeller/entity/entity_pass.cc +++ b/impeller/entity/entity_pass.cc @@ -62,12 +62,30 @@ void EntityPass::SetDelegate(std::shared_ptr delegate) { void EntityPass::SetBoundsLimit(std::optional bounds_limit) { bounds_limit_ = bounds_limit; + bounds_might_clip_ = bounds_limit.has_value(); + bounds_are_snug_ = false; } std::optional EntityPass::GetBoundsLimit() const { return bounds_limit_; } +void EntityPass::SetBoundsMightClipContent(bool clips) { + bounds_might_clip_ = clips; +} + +bool EntityPass::GetBoundsMightClipContent() const { + return bounds_might_clip_; +} + +void EntityPass::SetBoundsAreSnug(bool clips) { + bounds_are_snug_ = clips; +} + +bool EntityPass::GetBoundsAreSnug() const { + return bounds_are_snug_; +} + void EntityPass::AddEntity(Entity entity) { if (entity.GetBlendMode() == BlendMode::kSourceOver && entity.GetContents()->IsOpaque()) { @@ -201,6 +219,10 @@ std::optional EntityPass::GetElementsCoverage( std::optional EntityPass::GetSubpassCoverage( const EntityPass& subpass, std::optional coverage_limit) const { + if (subpass.bounds_limit_.has_value() && subpass.GetBoundsAreSnug()) { + return subpass.bounds_limit_->TransformBounds(subpass.transform_); + } + std::shared_ptr image_filter = subpass.delegate_->WithImageFilter(Rect(), subpass.transform_); diff --git a/impeller/entity/entity_pass.h b/impeller/entity/entity_pass.h index 322fbd18fd62f..d18e45e437ad2 100644 --- a/impeller/entity/entity_pass.h +++ b/impeller/entity/entity_pass.h @@ -63,12 +63,48 @@ class EntityPass { /// For consistency with Skia, we effectively treat this like a /// rectangle clip by forcing the subpass texture size to never exceed /// it. + /// + /// The entity pass will assume that these bounds cause a clipping + /// effect on the layer unless this call is followed up with a + /// call to |SetBoundsClipsContent()| specifying otherwise. void SetBoundsLimit(std::optional bounds_limit); /// @brief Get the bounds limit, which is provided by the user when creating /// a SaveLayer. std::optional GetBoundsLimit() const; + /// @brief Explicitly sets whether or not the bounds limit might + /// potentially clip the contents of the pass. + /// It is conservatively assumed to be true whenever a bounds + /// limit is set and may be corrected by calling this method + /// afterwards if the caller has good information that the + /// bounds are large enough not to clip any of the contents. + /// + /// This property does not promise that the bounds are otherwise + /// a reasonably tight approximation of the coverage of the + /// contents as in |GetBoundsAreSnug|. + void SetBoundsMightClipContent(bool clips); + + /// @brief Indicates if the bounds limit set using |SetBoundsLimit()| + /// might clip the contents of the pass. + bool GetBoundsMightClipContent() const; + + /// @brief Explicitly sets whether or not the bounds are a reasonably + /// tight approximation of (snug to) the contents of the pass. + /// It is conservatively assumed to be false whenever a bounds + /// limit is set and may be corrected by calling this method + /// afterwards if the caller has good information that the + /// bounds are reasonably sized to the contents, subject only + /// to the conservative effects of approximation methods. + /// + /// This property does not promise that the contents are not + /// clipped as in |GetBoundsMightClipContents|. + void SetBoundsAreSnug(bool clips); + + /// @brief Indicates if the bounds limit set using |SetBoundsLimit()| + /// is a reasonably tight estimate of the bounds of the contents. + bool GetBoundsAreSnug() const; + size_t GetSubpassesDepth() const; /// @brief Add an entity to the current entity pass. @@ -322,6 +358,8 @@ class EntityPass { bool flood_clip_ = false; bool enable_offscreen_debug_checkerboard_ = false; std::optional bounds_limit_; + bool bounds_might_clip_ = false; + bool bounds_are_snug_ = false; std::unique_ptr clip_replay_ = std::make_unique(); int32_t required_mip_count_ = 1; diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 9b293bd00d06f..5c36c6a973223 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -119,7 +119,7 @@ auto CreatePassWithRectPath(Rect rect, return subpass; } -TEST_P(EntityTest, EntityPassRespectsSubpassBoundsLimit) { +TEST_P(EntityTest, EntityPassRespectsUntrustedSubpassBoundsLimit) { EntityPass pass; auto subpass0 = CreatePassWithRectPath(Rect::MakeLTRB(0, 0, 100, 100), @@ -146,6 +146,49 @@ TEST_P(EntityTest, EntityPassRespectsSubpassBoundsLimit) { ASSERT_RECT_NEAR(coverage.value(), Rect::MakeLTRB(50, 50, 900, 900)); } +TEST_P(EntityTest, EntityPassTrustsSnugSubpassBoundsLimit) { + EntityPass pass; + + auto subpass0 = CreatePassWithRectPath(Rect::MakeLTRB(10, 10, 90, 90), + Rect::MakeLTRB(5, 5, 95, 95)); + auto subpass1 = CreatePassWithRectPath(Rect::MakeLTRB(500, 500, 1000, 1000), + Rect::MakeLTRB(495, 495, 1005, 1005)); + + auto subpass0_coverage = + pass.GetSubpassCoverage(*subpass0.get(), std::nullopt); + EXPECT_TRUE(subpass0_coverage.has_value()); + // First result is natural bounds + EXPECT_RECT_NEAR(subpass0_coverage.value(), Rect::MakeLTRB(10, 10, 90, 90)); + subpass0->SetBoundsAreSnug(true); + subpass0_coverage = pass.GetSubpassCoverage(*subpass0.get(), std::nullopt); + EXPECT_TRUE(subpass0_coverage.has_value()); + // Second result with snug flag is the overridden bounds + // (we lied about them being snug, but the property is respected) + EXPECT_RECT_NEAR(subpass0_coverage.value(), Rect::MakeLTRB(5, 5, 95, 95)); + + auto subpass1_coverage = + pass.GetSubpassCoverage(*subpass1.get(), std::nullopt); + EXPECT_TRUE(subpass1_coverage.has_value()); + // First result is natural bounds + EXPECT_RECT_NEAR(subpass1_coverage.value(), + Rect::MakeLTRB(500, 500, 1000, 1000)); + subpass1->SetBoundsAreSnug(true); + subpass1_coverage = pass.GetSubpassCoverage(*subpass1.get(), std::nullopt); + EXPECT_TRUE(subpass1_coverage.has_value()); + // Second result with snug flag is the overridden bounds + // (we lied about them being snug, but the property is respected) + EXPECT_RECT_NEAR(subpass1_coverage.value(), + Rect::MakeLTRB(495, 495, 1005, 1005)); + + pass.AddSubpass(std::move(subpass0)); + pass.AddSubpass(std::move(subpass1)); + + auto coverage = pass.GetElementsCoverage(std::nullopt); + EXPECT_TRUE(coverage.has_value()); + // This result should be the union of the overridden bounds + EXPECT_RECT_NEAR(coverage.value(), Rect::MakeLTRB(5, 5, 1005, 1005)); +} + TEST_P(EntityTest, EntityPassCanMergeSubpassIntoParent) { // Both a red and a blue box should appear if the pass merging has worked // correctly. diff --git a/shell/common/dl_op_spy.cc b/shell/common/dl_op_spy.cc index bcf213bffa8e9..f3995a45cf2f2 100644 --- a/shell/common/dl_op_spy.cc +++ b/shell/common/dl_op_spy.cc @@ -29,7 +29,7 @@ void DlOpSpy::setColorSource(const DlColorSource* source) { will_draw_ = true; } void DlOpSpy::save() {} -void DlOpSpy::saveLayer(const SkRect* bounds, +void DlOpSpy::saveLayer(const SkRect& bounds, const SaveLayerOptions options, const DlImageFilter* backdrop) {} void DlOpSpy::restore() {} diff --git a/shell/common/dl_op_spy.h b/shell/common/dl_op_spy.h index 8982b98d6914b..438a2757bf631 100644 --- a/shell/common/dl_op_spy.h +++ b/shell/common/dl_op_spy.h @@ -39,7 +39,7 @@ class DlOpSpy final : public virtual DlOpReceiver, void setColor(DlColor color) override; void setColorSource(const DlColorSource* source) override; void save() override; - void saveLayer(const SkRect* bounds, + void saveLayer(const SkRect& bounds, const SaveLayerOptions options, const DlImageFilter* backdrop) override; void restore() override; diff --git a/shell/platform/embedder/fixtures/verifyb143464703_soft_noxform.png b/shell/platform/embedder/fixtures/verifyb143464703_soft_noxform.png index 35f482b381d22f6d007107be0f93cd8928a962c1..659473eedd28ab9cc2f67222e4b55496c97e3dca 100644 GIT binary patch literal 15747 zcmeHti(3DF3M#1s${5|XLA z+lnA$)gmI0w8cslL#iAikTC5o5S2q-iX_S*vt|ebL`Wwg36o4_z9+ixx8MKpHC!@W zUU)h8bDw^P=j404I7|Mp`VRpC0ZX>Oo%DV{z!Tud-+cJg@4$n6_N#F4c%tCV?R%aA zpW>%JDG3O8Az*vbpZ5rB=O%LUt#M~w8%^qo`9Xef?i=ULKe$h94g7mR*UNpuIa`l@ zb1Uf&OY_I_Z!c`8y)a$#_-YEHDQ14s#ix`mQO)L2I%$&3mMt(6AU+MPXMf-oOch&i zbVa3O$QFe_iM=gl+G2^T_ zDo6h0qMmV=Hd0%tv(mwfx|kmq6vQI?vB)#2-ipneMl)It(4HIf^c-P{Q%$LysxfE_ z+gwQR!>q=M+bY$&_f&FLSxS#G}gKjuQ2eQ!`KRex5h}Sq^^i&AtPii@Lm5 zD;gO48Yt&dw2yqnqxZevOm7^#KIhakoB3`Pw%4sPR+kkTyk}MuTFOT_duy68GGkKj zyG<2#HqiMRqctoZ%j8{;>M3$38FI+bH4Gl(l)d-oy0;3@PUJQdD{7(hGgUrY zm}&&BQgjfG!wqd894(roMOrIrV0nqaz?nP-b5s|!@wWk~FDqSx50f{P4MjpZ6tR;K zyKQe%sUSwdT)I`XbqFr5^N>T*bV7^QU!db1whNd^;_Z&j`x+S`mSY8)sUEaA

KG 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 TeR;=!4BNNvN@{xZqrd(iIt!wf literal 14266 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 diff --git a/testing/display_list_testing.cc b/testing/display_list_testing.cc index 908afd9432755..03a37553ac84f 100644 --- a/testing/display_list_testing.cc +++ b/testing/display_list_testing.cc @@ -628,7 +628,7 @@ void DisplayListStreamDispatcher::save() { startl() << "{" << std::endl; indent(); } -void DisplayListStreamDispatcher::saveLayer(const SkRect* bounds, +void DisplayListStreamDispatcher::saveLayer(const SkRect& bounds, const SaveLayerOptions options, const DlImageFilter* backdrop) { startl() << "saveLayer(" << bounds << ", " << options; diff --git a/testing/display_list_testing.h b/testing/display_list_testing.h index 0657a5a63d6f4..74bc2826228a8 100644 --- a/testing/display_list_testing.h +++ b/testing/display_list_testing.h @@ -73,7 +73,7 @@ class DisplayListStreamDispatcher final : public DlOpReceiver { void setImageFilter(const DlImageFilter* filter) override; void save() override; - void saveLayer(const SkRect* bounds, + void saveLayer(const SkRect& bounds, const SaveLayerOptions options, const DlImageFilter* backdrop) override; void restore() override; From c0d3361a4e8b7f5a61ce26d9c807b23bd3063a0b Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Thu, 14 Mar 2024 13:05:21 -0700 Subject: [PATCH 2/2] consolidate EntityPass and Canvas into a common bounds state enum --- impeller/aiks/canvas.cc | 21 +-------- impeller/aiks/canvas.h | 18 +------- impeller/aiks/canvas_recorder.h | 10 ++--- impeller/aiks/canvas_recorder_unittests.cc | 2 +- impeller/aiks/paint_pass_delegate.cc | 2 +- impeller/aiks/trace_serializer.cc | 2 +- impeller/aiks/trace_serializer.h | 2 +- impeller/display_list/dl_dispatcher.cc | 4 +- impeller/entity/entity_pass.cc | 48 +++++++++++++------- impeller/entity/entity_pass.h | 52 ++++++++++------------ impeller/entity/entity_unittests.cc | 40 ++++++++--------- 11 files changed, 87 insertions(+), 114 deletions(-) diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc index 5ac6fbc37cac6..1e3fe3812b138 100644 --- a/impeller/aiks/canvas.cc +++ b/impeller/aiks/canvas.cc @@ -784,7 +784,7 @@ void Canvas::AddEntityToCurrentPass(Entity entity) { void Canvas::SaveLayer(const Paint& paint, std::optional bounds, const std::shared_ptr& backdrop_filter, - SaveLayerBoundsPromise bounds_promise) { + ContentBoundsPromise bounds_promise) { TRACE_EVENT0("flutter", "Canvas::saveLayer"); Save(true, paint.blend_mode, backdrop_filter); @@ -798,24 +798,7 @@ void Canvas::SaveLayer(const Paint& paint, auto& new_layer_pass = GetCurrentPass(); if (bounds) { - new_layer_pass.SetBoundsLimit(bounds); - bool snug; - bool might_clip; - switch (bounds_promise) { - case SaveLayerBoundsPromise::kUnknown: - snug = false; - might_clip = true; - break; - case SaveLayerBoundsPromise::kContainsContents: - snug = true; - might_clip = false; - break; - case SaveLayerBoundsPromise::kClipsContents: - snug = true; - might_clip = true; - } - new_layer_pass.SetBoundsAreSnug(snug); - new_layer_pass.SetBoundsMightClipContent(might_clip); + new_layer_pass.SetBoundsLimit(bounds, bounds_promise); } if (paint.image_filter) { diff --git a/impeller/aiks/canvas.h b/impeller/aiks/canvas.h index c144061e0b4af..36ba4639b264d 100644 --- a/impeller/aiks/canvas.h +++ b/impeller/aiks/canvas.h @@ -55,22 +55,6 @@ enum class SourceRectConstraint { kStrict, }; -/// Controls how much to trust the bounds rectangle given to SaveLayer. -enum class SaveLayerBoundsPromise { - /// @brief The caller makes no claims related to the size of the bounds. - kUnknown, - - /// @brief The caller claims the bounds are a reasonably tight estimate - /// of the coverage of the contents and should contain all of the - /// contents. - kContainsContents, - - /// @brief The caller claims the bounds are a subset of an estimate of - /// the reasonably tight bounds but likely clips off some of the - /// contents. - kClipsContents, -}; - class Canvas { public: struct DebugOptions { @@ -95,7 +79,7 @@ class Canvas { const Paint& paint, std::optional bounds = std::nullopt, const std::shared_ptr& backdrop_filter = nullptr, - SaveLayerBoundsPromise bounds_promise = SaveLayerBoundsPromise::kUnknown); + ContentBoundsPromise bounds_promise = ContentBoundsPromise::kUnknown); bool Restore(); diff --git a/impeller/aiks/canvas_recorder.h b/impeller/aiks/canvas_recorder.h index 16bd739ea75bd..526050547453f 100644 --- a/impeller/aiks/canvas_recorder.h +++ b/impeller/aiks/canvas_recorder.h @@ -113,11 +113,11 @@ class CanvasRecorder { return ExecuteAndSerialize(CanvasRecorderOp::kSave, &Canvas::Save); } - void SaveLayer(const Paint& paint, - std::optional bounds = std::nullopt, - const std::shared_ptr& backdrop_filter = nullptr, - SaveLayerBoundsPromise bounds_promise = - SaveLayerBoundsPromise::kUnknown) { + void SaveLayer( + const Paint& paint, + std::optional bounds = std::nullopt, + const std::shared_ptr& backdrop_filter = nullptr, + ContentBoundsPromise bounds_promise = ContentBoundsPromise::kUnknown) { return ExecuteAndSerialize(FLT_CANVAS_RECORDER_OP_ARG(SaveLayer), paint, bounds, backdrop_filter, bounds_promise); } diff --git a/impeller/aiks/canvas_recorder_unittests.cc b/impeller/aiks/canvas_recorder_unittests.cc index 6ce9b10e08245..a18e092aab9e2 100644 --- a/impeller/aiks/canvas_recorder_unittests.cc +++ b/impeller/aiks/canvas_recorder_unittests.cc @@ -56,7 +56,7 @@ class Serializer { void Write(const SourceRectConstraint& src_rect_constraint) {} - void Write(const SaveLayerBoundsPromise& promise) {} + void Write(const ContentBoundsPromise& promise) {} CanvasRecorderOp last_op_; }; diff --git a/impeller/aiks/paint_pass_delegate.cc b/impeller/aiks/paint_pass_delegate.cc index a03fd49be83e3..34610488f5a24 100644 --- a/impeller/aiks/paint_pass_delegate.cc +++ b/impeller/aiks/paint_pass_delegate.cc @@ -73,7 +73,7 @@ bool OpacityPeepholePassDelegate::CanCollapseIntoParentPass( EntityPass* entity_pass) { // Passes with enforced bounds that clip the contents can not be safely // collapsed. - if (entity_pass->GetBoundsMightClipContent()) { + if (entity_pass->GetBoundsLimitMightClipContent()) { return false; } diff --git a/impeller/aiks/trace_serializer.cc b/impeller/aiks/trace_serializer.cc index c03ca38b6cd6f..7600760406b1b 100644 --- a/impeller/aiks/trace_serializer.cc +++ b/impeller/aiks/trace_serializer.cc @@ -258,7 +258,7 @@ void TraceSerializer::Write(const SourceRectConstraint& src_rect_constraint) { buffer_ << "[SourceRectConstraint] "; } -void TraceSerializer::Write(const SaveLayerBoundsPromise& promise) { +void TraceSerializer::Write(const ContentBoundsPromise& promise) { buffer_ << "[SaveLayerBoundsPromise]"; } diff --git a/impeller/aiks/trace_serializer.h b/impeller/aiks/trace_serializer.h index d7e919a82d883..32abcdcddcdb4 100644 --- a/impeller/aiks/trace_serializer.h +++ b/impeller/aiks/trace_serializer.h @@ -60,7 +60,7 @@ class TraceSerializer { void Write(const SourceRectConstraint& src_rect_constraint); - void Write(const SaveLayerBoundsPromise& promise); + void Write(const ContentBoundsPromise& promise); private: std::stringstream buffer_; diff --git a/impeller/display_list/dl_dispatcher.cc b/impeller/display_list/dl_dispatcher.cc index 60e38ff83ad4d..8be3b9daf39bf 100644 --- a/impeller/display_list/dl_dispatcher.cc +++ b/impeller/display_list/dl_dispatcher.cc @@ -628,8 +628,8 @@ void DlDispatcher::saveLayer(const SkRect& bounds, const flutter::DlImageFilter* backdrop) { auto paint = options.renders_with_attributes() ? paint_ : Paint{}; auto promise = options.content_is_clipped() - ? SaveLayerBoundsPromise::kClipsContents - : SaveLayerBoundsPromise::kContainsContents; + ? ContentBoundsPromise::kMayClipContents + : ContentBoundsPromise::kContainsContents; canvas_.SaveLayer(paint, skia_conversions::ToRect(bounds), ToImageFilter(backdrop), promise); } diff --git a/impeller/entity/entity_pass.cc b/impeller/entity/entity_pass.cc index d6dadad5b3c21..ebce3faf8b2a7 100644 --- a/impeller/entity/entity_pass.cc +++ b/impeller/entity/entity_pass.cc @@ -60,30 +60,46 @@ void EntityPass::SetDelegate(std::shared_ptr delegate) { delegate_ = std::move(delegate); } -void EntityPass::SetBoundsLimit(std::optional bounds_limit) { +void EntityPass::SetBoundsLimit(std::optional bounds_limit, + ContentBoundsPromise bounds_promise) { bounds_limit_ = bounds_limit; - bounds_might_clip_ = bounds_limit.has_value(); - bounds_are_snug_ = false; + bounds_promise_ = bounds_limit.has_value() ? bounds_promise + : ContentBoundsPromise::kUnknown; } std::optional EntityPass::GetBoundsLimit() const { return bounds_limit_; } -void EntityPass::SetBoundsMightClipContent(bool clips) { - bounds_might_clip_ = clips; -} - -bool EntityPass::GetBoundsMightClipContent() const { - return bounds_might_clip_; -} - -void EntityPass::SetBoundsAreSnug(bool clips) { - bounds_are_snug_ = clips; +bool EntityPass::GetBoundsLimitMightClipContent() const { + switch (bounds_promise_) { + case ContentBoundsPromise::kUnknown: + // If the promise is unknown due to not having a bounds limit, + // then no clipping will occur. But if we have a bounds limit + // and it is unkown, then we can make no promises about whether + // it causes clipping of the entity pass contents and we + // conservatively return true. + return bounds_limit_.has_value(); + case ContentBoundsPromise::kContainsContents: + FML_DCHECK(bounds_limit_.has_value()); + return false; + case ContentBoundsPromise::kMayClipContents: + FML_DCHECK(bounds_limit_.has_value()); + return true; + } + FML_UNREACHABLE(); } -bool EntityPass::GetBoundsAreSnug() const { - return bounds_are_snug_; +bool EntityPass::GetBoundsLimitIsSnug() const { + switch (bounds_promise_) { + case ContentBoundsPromise::kUnknown: + return false; + case ContentBoundsPromise::kContainsContents: + case ContentBoundsPromise::kMayClipContents: + FML_DCHECK(bounds_limit_.has_value()); + return true; + } + FML_UNREACHABLE(); } void EntityPass::AddEntity(Entity entity) { @@ -219,7 +235,7 @@ std::optional EntityPass::GetElementsCoverage( std::optional EntityPass::GetSubpassCoverage( const EntityPass& subpass, std::optional coverage_limit) const { - if (subpass.bounds_limit_.has_value() && subpass.GetBoundsAreSnug()) { + if (subpass.bounds_limit_.has_value() && subpass.GetBoundsLimitIsSnug()) { return subpass.bounds_limit_->TransformBounds(subpass.transform_); } diff --git a/impeller/entity/entity_pass.h b/impeller/entity/entity_pass.h index d18e45e437ad2..4198bccd0fbba 100644 --- a/impeller/entity/entity_pass.h +++ b/impeller/entity/entity_pass.h @@ -23,6 +23,23 @@ namespace impeller { class ContentContext; class EntityPassClipRecorder; +/// Specifies how much to trust the bounds rectangle provided for a list +/// of contents. Used by both |EntityPass| and |Canvas::SaveLayer|. +enum class ContentBoundsPromise { + /// @brief The caller makes no claims related to the size of the bounds. + kUnknown, + + /// @brief The caller claims the bounds are a reasonably tight estimate + /// of the coverage of the contents and should contain all of the + /// contents. + kContainsContents, + + /// @brief The caller claims the bounds are a subset of an estimate of + /// the reasonably tight bounds but likely clips off some of the + /// contents. + kMayClipContents, +}; + class EntityPass { public: /// Elements are renderable items in the `EntityPass`. Each can either be an @@ -67,43 +84,21 @@ class EntityPass { /// The entity pass will assume that these bounds cause a clipping /// effect on the layer unless this call is followed up with a /// call to |SetBoundsClipsContent()| specifying otherwise. - void SetBoundsLimit(std::optional bounds_limit); + void SetBoundsLimit( + std::optional bounds_limit, + ContentBoundsPromise bounds_promise = ContentBoundsPromise::kUnknown); /// @brief Get the bounds limit, which is provided by the user when creating /// a SaveLayer. std::optional GetBoundsLimit() const; - /// @brief Explicitly sets whether or not the bounds limit might - /// potentially clip the contents of the pass. - /// It is conservatively assumed to be true whenever a bounds - /// limit is set and may be corrected by calling this method - /// afterwards if the caller has good information that the - /// bounds are large enough not to clip any of the contents. - /// - /// This property does not promise that the bounds are otherwise - /// a reasonably tight approximation of the coverage of the - /// contents as in |GetBoundsAreSnug|. - void SetBoundsMightClipContent(bool clips); - /// @brief Indicates if the bounds limit set using |SetBoundsLimit()| /// might clip the contents of the pass. - bool GetBoundsMightClipContent() const; - - /// @brief Explicitly sets whether or not the bounds are a reasonably - /// tight approximation of (snug to) the contents of the pass. - /// It is conservatively assumed to be false whenever a bounds - /// limit is set and may be corrected by calling this method - /// afterwards if the caller has good information that the - /// bounds are reasonably sized to the contents, subject only - /// to the conservative effects of approximation methods. - /// - /// This property does not promise that the contents are not - /// clipped as in |GetBoundsMightClipContents|. - void SetBoundsAreSnug(bool clips); + bool GetBoundsLimitMightClipContent() const; /// @brief Indicates if the bounds limit set using |SetBoundsLimit()| /// is a reasonably tight estimate of the bounds of the contents. - bool GetBoundsAreSnug() const; + bool GetBoundsLimitIsSnug() const; size_t GetSubpassesDepth() const; @@ -358,8 +353,7 @@ class EntityPass { bool flood_clip_ = false; bool enable_offscreen_debug_checkerboard_ = false; std::optional bounds_limit_; - bool bounds_might_clip_ = false; - bool bounds_are_snug_ = false; + ContentBoundsPromise bounds_promise_ = ContentBoundsPromise::kUnknown; std::unique_ptr clip_replay_ = std::make_unique(); int32_t required_mip_count_ = 1; diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 5c36c6a973223..1592bcb1198f1 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -106,16 +106,18 @@ class TestPassDelegate final : public EntityPassDelegate { const bool collapse_; }; -auto CreatePassWithRectPath(Rect rect, - std::optional bounds_hint, - bool collapse = false) { +auto CreatePassWithRectPath( + Rect rect, + std::optional bounds_hint, + ContentBoundsPromise bounds_promise = ContentBoundsPromise::kUnknown, + bool collapse = false) { auto subpass = std::make_unique(); Entity entity; entity.SetContents(SolidColorContents::Make( PathBuilder{}.AddRect(rect).TakePath(), Color::Red())); subpass->AddEntity(std::move(entity)); subpass->SetDelegate(std::make_unique(collapse)); - subpass->SetBoundsLimit(bounds_hint); + subpass->SetBoundsLimit(bounds_hint, bounds_promise); return subpass; } @@ -149,33 +151,26 @@ TEST_P(EntityTest, EntityPassRespectsUntrustedSubpassBoundsLimit) { TEST_P(EntityTest, EntityPassTrustsSnugSubpassBoundsLimit) { EntityPass pass; - auto subpass0 = CreatePassWithRectPath(Rect::MakeLTRB(10, 10, 90, 90), - Rect::MakeLTRB(5, 5, 95, 95)); - auto subpass1 = CreatePassWithRectPath(Rect::MakeLTRB(500, 500, 1000, 1000), - Rect::MakeLTRB(495, 495, 1005, 1005)); + auto subpass0 = // + CreatePassWithRectPath(Rect::MakeLTRB(10, 10, 90, 90), + Rect::MakeLTRB(5, 5, 95, 95), + ContentBoundsPromise::kContainsContents); + auto subpass1 = // + CreatePassWithRectPath(Rect::MakeLTRB(500, 500, 1000, 1000), + Rect::MakeLTRB(495, 495, 1005, 1005), + ContentBoundsPromise::kContainsContents); auto subpass0_coverage = pass.GetSubpassCoverage(*subpass0.get(), std::nullopt); EXPECT_TRUE(subpass0_coverage.has_value()); - // First result is natural bounds - EXPECT_RECT_NEAR(subpass0_coverage.value(), Rect::MakeLTRB(10, 10, 90, 90)); - subpass0->SetBoundsAreSnug(true); - subpass0_coverage = pass.GetSubpassCoverage(*subpass0.get(), std::nullopt); - EXPECT_TRUE(subpass0_coverage.has_value()); - // Second result with snug flag is the overridden bounds + // Result should be the overridden bounds // (we lied about them being snug, but the property is respected) EXPECT_RECT_NEAR(subpass0_coverage.value(), Rect::MakeLTRB(5, 5, 95, 95)); auto subpass1_coverage = pass.GetSubpassCoverage(*subpass1.get(), std::nullopt); EXPECT_TRUE(subpass1_coverage.has_value()); - // First result is natural bounds - EXPECT_RECT_NEAR(subpass1_coverage.value(), - Rect::MakeLTRB(500, 500, 1000, 1000)); - subpass1->SetBoundsAreSnug(true); - subpass1_coverage = pass.GetSubpassCoverage(*subpass1.get(), std::nullopt); - EXPECT_TRUE(subpass1_coverage.has_value()); - // Second result with snug flag is the overridden bounds + // Result should be the overridden bounds // (we lied about them being snug, but the property is respected) EXPECT_RECT_NEAR(subpass1_coverage.value(), Rect::MakeLTRB(495, 495, 1005, 1005)); @@ -195,7 +190,8 @@ TEST_P(EntityTest, EntityPassCanMergeSubpassIntoParent) { EntityPass pass; auto subpass = CreatePassWithRectPath(Rect::MakeLTRB(0, 0, 100, 100), - Rect::MakeLTRB(50, 50, 150, 150), true); + Rect::MakeLTRB(50, 50, 150, 150), + ContentBoundsPromise::kUnknown, true); pass.AddSubpass(std::move(subpass)); Entity entity;