diff --git a/display_list/dl_builder.cc b/display_list/dl_builder.cc index d5689ed4fce9e..e1b89d97894a4 100644 --- a/display_list/dl_builder.cc +++ b/display_list/dl_builder.cc @@ -1042,6 +1042,9 @@ bool DisplayListBuilder::QuickReject(const SkRect& bounds) const { void DisplayListBuilder::drawPaint() { OpResult result = PaintResult(current_, kDrawPaintFlags); if (result != OpResult::kNoEffect && AccumulateUnbounded()) { + if (current_layer().update_clear_color(current_, false)) { + return; + } Push(0); CheckLayerOpacityCompatibility(); UpdateLayerResult(result); @@ -1054,6 +1057,9 @@ void DisplayListBuilder::DrawPaint(const DlPaint& paint) { void DisplayListBuilder::DrawColor(DlColor color, DlBlendMode mode) { OpResult result = PaintResult(DlPaint(color).setBlendMode(mode)); if (result != OpResult::kNoEffect && AccumulateUnbounded()) { + if (current_layer().update_clear_color(color, mode)) { + return; + } Push(0, color, mode); CheckLayerOpacityCompatibility(mode); UpdateLayerResult(result, mode); @@ -1082,6 +1088,11 @@ void DisplayListBuilder::drawRect(const SkRect& rect) { OpResult result = PaintResult(current_, flags); if (result != OpResult::kNoEffect && AccumulateOpBounds(rect.makeSorted(), flags)) { + if (current_layer().still_clearing && + current_info().global_state.rect_covers_cull(rect) && + current_layer().update_clear_color(current_, true)) { + return; + } Push(0, rect); CheckLayerOpacityCompatibility(); UpdateLayerResult(result); @@ -1096,6 +1107,11 @@ void DisplayListBuilder::drawOval(const SkRect& bounds) { OpResult result = PaintResult(current_, flags); if (result != OpResult::kNoEffect && AccumulateOpBounds(bounds.makeSorted(), flags)) { + if (current_layer().still_clearing && + current_info().global_state.oval_covers_cull(bounds) && + current_layer().update_clear_color(current_, true)) { + return; + } Push(0, bounds); CheckLayerOpacityCompatibility(); UpdateLayerResult(result); @@ -1112,6 +1128,11 @@ void DisplayListBuilder::drawCircle(const SkPoint& center, SkScalar radius) { SkRect bounds = SkRect::MakeLTRB(center.fX - radius, center.fY - radius, center.fX + radius, center.fY + radius); if (AccumulateOpBounds(bounds, flags)) { + if (current_layer().still_clearing && + current_info().global_state.oval_covers_cull(bounds) && + current_layer().update_clear_color(current_, true)) { + return; + } Push(0, center, radius); CheckLayerOpacityCompatibility(); UpdateLayerResult(result); @@ -1134,6 +1155,11 @@ void DisplayListBuilder::drawRRect(const SkRRect& rrect) { OpResult result = PaintResult(current_, flags); if (result != OpResult::kNoEffect && AccumulateOpBounds(rrect.getBounds(), flags)) { + if (current_layer().still_clearing && + current_info().global_state.rrect_covers_cull(rrect) && + current_layer().update_clear_color(current_, true)) { + return; + } Push(0, rrect); CheckLayerOpacityCompatibility(); UpdateLayerResult(result); @@ -1786,6 +1812,63 @@ bool DisplayListBuilder::AccumulateBounds(const SkRect& bounds, return true; } +bool DisplayListBuilder::LayerInfo::update_clear_color(DlColor color, + DlBlendMode mode) { + if (still_clearing) { + switch (mode) { + case DlBlendMode::kSrc: + if (color.isTransparent()) { + clear_color = DlColor::kTransparent(); + max_blend_mode = DlBlendMode::kClear; + } else { + clear_color = color; + max_blend_mode = DlBlendMode::kSrc; + } + return true; + case DlBlendMode::kSrcOver: + if (color.isTransparent()) { + // No changes to clear_color or max_blend_mode... + } else { + if (color.isOpaque() || clear_color.isTransparent()) { + clear_color = color; + max_blend_mode = DlBlendMode::kSrc; + } else { + DlScalar fSrc = color.getAlphaF(); + DlScalar fDst = clear_color.getAlphaF() * (1.0f - fSrc); + DlScalar fRed = + color.getRedF() * fSrc + clear_color.getRedF() * fDst; + DlScalar fGreen = + color.getGreenF() * fSrc + clear_color.getGreenF() * fDst; + DlScalar fBlue = + color.getBlueF() * fSrc + clear_color.getBlueF() * fDst; + clear_color = DlColor::MakeARGB(fSrc + fDst, fRed, fGreen, fBlue); + max_blend_mode = DlBlendMode::kSrc; + } + } + return true; + default: + break; + } + } + return false; +} + +bool DisplayListBuilder::LayerInfo::update_clear_color(const DlPaint& paint, + bool has_geometry) { + if (paint.getColorSourcePtr() != nullptr || + paint.getColorFilterPtr() != nullptr || + paint.getImageFilterPtr() != nullptr) { + return false; + } + if (has_geometry) { + if (paint.getDrawStyle() != DlDrawStyle::kFill || + paint.getMaskFilterPtr() != nullptr) { + return false; + } + } + return update_clear_color(paint.getColor(), paint.getBlendMode()); +} + bool DisplayListBuilder::SaveInfo::AccumulateBoundsLocal(const SkRect& bounds) { if (bounds.isEmpty()) { return false; diff --git a/display_list/dl_builder.h b/display_list/dl_builder.h index f492680003a5e..4ea62b6c26f3d 100644 --- a/display_list/dl_builder.h +++ b/display_list/dl_builder.h @@ -539,6 +539,9 @@ class DisplayListBuilder final : public virtual DlCanvas, DlBlendMode max_blend_mode = DlBlendMode::kClear; + bool still_clearing; + DlColor clear_color = DlColor::kTransparent(); + bool opacity_incompatible_op_detected = false; bool affects_transparent_layer = false; bool contains_backdrop_filter = false; @@ -553,6 +556,16 @@ class DisplayListBuilder final : public virtual DlCanvas, max_blend_mode = mode; } } + + // Update the layer's clear color according to the indicated fill + // color and blend mode and return true if the operation was + // successfully merged into the layer clear information. + bool update_clear_color(DlColor color, DlBlendMode mode); + + // Update the layer's clear color according to the indicated fill + // color and blend mode and return true if the operation was + // successfully merged into the layer clear information. + bool update_clear_color(const DlPaint& paint, bool has_geometry); }; // The SaveInfo class stores internal data common to both Save and diff --git a/display_list/dl_color.h b/display_list/dl_color.h index 8746b96e3120d..7d776ffc0e3b7 100644 --- a/display_list/dl_color.h +++ b/display_list/dl_color.h @@ -5,6 +5,8 @@ #ifndef FLUTTER_DISPLAY_LIST_DL_COLOR_H_ #define FLUTTER_DISPLAY_LIST_DL_COLOR_H_ +#include + #include "third_party/skia/include/core/SkScalar.h" namespace flutter { @@ -13,6 +15,15 @@ struct DlColor { public: constexpr DlColor() : argb_(0xFF000000) {} constexpr explicit DlColor(uint32_t argb) : argb_(argb) {} + constexpr static DlColor MakeARGB(SkScalar fAlpha, + SkScalar fRed, + SkScalar fGreen, + SkScalar fBlue) { + return DlColor((toC(fAlpha) << 24) + // + (toC(fRed) << 16) + // + (toC(fGreen) << 8) + // + (toC(fBlue))); + } static constexpr uint8_t toAlpha(SkScalar opacity) { return toC(opacity); } static constexpr SkScalar toOpacity(uint8_t alpha) { return toF(alpha); } @@ -87,7 +98,18 @@ struct DlColor { uint32_t argb_; static float toF(uint8_t comp) { return comp * (1.0f / 255); } - static uint8_t toC(float fComp) { return round(fComp * 255); } + static uint8_t toC(float fComp) { + return round(std::clamp(fComp, 0.0f, 1.0f) * 255); + } + static uint32_t fromFloats(SkScalar fAlpha, + SkScalar fRed, + SkScalar fGreen, + SkScalar fBlue) { + return ((toC(std::clamp(fAlpha, 0.0f, 1.0f)) << 24) + + (toC(std::clamp(fRed, 0.0f, 1.0f)) << 16) + + (toC(std::clamp(fGreen, 0.0f, 1.0f)) << 8) + + (toC(std::clamp(fBlue, 0.0f, 1.0f)))); + } }; } // namespace flutter diff --git a/display_list/dl_color_unittests.cc b/display_list/dl_color_unittests.cc index 7a6c6a15ed82d..dc5df6c42bfbe 100644 --- a/display_list/dl_color_unittests.cc +++ b/display_list/dl_color_unittests.cc @@ -45,5 +45,35 @@ TEST(DisplayListColor, DlColorDirectlyComparesToSkColor) { EXPECT_EQ(DlColor::kBlue(), SK_ColorBLUE); } +TEST(DisplayListColor, ScalarFactory) { + // Test 9 standard colors with their Scalar equivalents + EXPECT_EQ(DlColor::MakeARGB(0.0f, 0.0f, 0.0f, 0.0f), DlColor::kTransparent()); + EXPECT_EQ(DlColor::MakeARGB(1.0f, 0.0f, 0.0f, 0.0f), DlColor::kBlack()); + EXPECT_EQ(DlColor::MakeARGB(1.0f, 1.0f, 1.0f, 1.0f), DlColor::kWhite()); + + EXPECT_EQ(DlColor::MakeARGB(1.0f, 1.0f, 0.0f, 0.0f), DlColor::kRed()); + EXPECT_EQ(DlColor::MakeARGB(1.0f, 0.0f, 1.0f, 0.0f), DlColor::kGreen()); + EXPECT_EQ(DlColor::MakeARGB(1.0f, 0.0f, 0.0f, 1.0f), DlColor::kBlue()); + + EXPECT_EQ(DlColor::MakeARGB(1.0f, 0.0f, 1.0f, 1.0f), DlColor::kCyan()); + EXPECT_EQ(DlColor::MakeARGB(1.0f, 1.0f, 0.0f, 1.0f), DlColor::kMagenta()); + EXPECT_EQ(DlColor::MakeARGB(1.0f, 1.0f, 1.0f, 0.0f), DlColor::kYellow()); + + // Test each component reduced to half intensity + EXPECT_EQ(DlColor::MakeARGB(0.5f, 1.0f, 1.0f, 1.0f), + DlColor::kWhite().withAlpha(0x80)); + EXPECT_EQ(DlColor::MakeARGB(1.0f, 0.5f, 1.0f, 1.0f), + DlColor::kWhite().withRed(0x80)); + EXPECT_EQ(DlColor::MakeARGB(1.0f, 1.0f, 0.5f, 1.0f), + DlColor::kWhite().withGreen(0x80)); + EXPECT_EQ(DlColor::MakeARGB(1.0f, 1.0f, 1.0f, 0.5f), + DlColor::kWhite().withBlue(0x80)); + + // Test clamping to [0.0, 1.0] + EXPECT_EQ(DlColor::MakeARGB(-1.0f, -1.0f, -1.0f, -1.0f), + DlColor::kTransparent()); + EXPECT_EQ(DlColor::MakeARGB(2.0f, 2.0f, 2.0f, 2.0f), DlColor::kWhite()); +} + } // namespace testing } // namespace flutter diff --git a/display_list/utils/dl_matrix_clip_tracker.cc b/display_list/utils/dl_matrix_clip_tracker.cc index 1196ca7f38405..d0d5a4d88968a 100644 --- a/display_list/utils/dl_matrix_clip_tracker.cc +++ b/display_list/utils/dl_matrix_clip_tracker.cc @@ -270,4 +270,105 @@ SkRect DisplayListMatrixClipState::local_cull_rect() const { return ToSkRect(cull_rect_.TransformBounds(inverse)); } +bool DisplayListMatrixClipState::rect_covers_cull(const DlRect& content) const { + if (content.IsEmpty()) { + return false; + } + if (cull_rect_.IsEmpty()) { + return true; + } + DlPoint corners[4]; + if (!getLocalCullCorners(corners)) { + return false; + } + for (auto corner : corners) { + if (!content.ContainsInclusive(corner)) { + return false; + } + } + return true; +} + +bool DisplayListMatrixClipState::oval_covers_cull(const DlRect& bounds) const { + if (bounds.IsEmpty()) { + return false; + } + if (cull_rect_.IsEmpty()) { + return true; + } + DlPoint corners[4]; + if (!getLocalCullCorners(corners)) { + return false; + } + DlPoint center = bounds.GetCenter(); + DlSize scale = 2.0 / bounds.GetSize(); + for (auto corner : corners) { + if (!bounds.Contains(corner)) { + return false; + } + if (((corner - center) * scale).GetLengthSquared() >= 1.0) { + return false; + } + } + return true; +} + +bool DisplayListMatrixClipState::rrect_covers_cull( + const SkRRect& content) const { + if (content.isEmpty()) { + return false; + } + if (cull_rect_.IsEmpty()) { + return true; + } + if (content.isRect()) { + return rect_covers_cull(content.getBounds()); + } + if (content.isOval()) { + return oval_covers_cull(content.getBounds()); + } + if (!content.isSimple()) { + return false; + } + DlPoint corners[4]; + if (!getLocalCullCorners(corners)) { + return false; + } + auto outer = content.getBounds(); + DlScalar x_center = outer.centerX(); + DlScalar y_center = outer.centerY(); + auto radii = content.getSimpleRadii(); + DlScalar inner_x = outer.width() * 0.5f - radii.fX; + DlScalar inner_y = outer.height() * 0.5f - radii.fY; + DlScalar scale_x = 1.0 / radii.fX; + DlScalar scale_y = 1.0 / radii.fY; + for (auto corner : corners) { + if (!outer.contains(corner.x, corner.y)) { + return false; + } + DlScalar x_rel = std::abs(corner.x - x_center) - inner_x; + DlScalar y_rel = std::abs(corner.y - y_center) - inner_y; + if (x_rel > 0.0f && y_rel > 0.0f) { + x_rel *= scale_x; + y_rel *= scale_y; + if (x_rel * x_rel + y_rel * y_rel >= 1.0f) { + return false; + } + } + } + return true; +} + +bool DisplayListMatrixClipState::getLocalCullCorners(DlPoint corners[4]) const { + if (!is_matrix_invertable()) { + return false; + } + DlMatrix inverse = matrix_.Invert(); + corners[0] = inverse * cull_rect_.GetLeftTop(); + corners[1] = inverse * cull_rect_.GetRightTop(); + corners[2] = inverse * cull_rect_.GetRightBottom(); + corners[3] = inverse * cull_rect_.GetLeftBottom(); + return true; +} + } // namespace flutter diff --git a/display_list/utils/dl_matrix_clip_tracker.h b/display_list/utils/dl_matrix_clip_tracker.h index 53edb60e5ad8e..4d072acc3c2ee 100644 --- a/display_list/utils/dl_matrix_clip_tracker.h +++ b/display_list/utils/dl_matrix_clip_tracker.h @@ -61,6 +61,16 @@ class DisplayListMatrixClipState { SkRect local_cull_rect() const; SkRect device_cull_rect() const { return ToSkRect(cull_rect_); } + bool rect_covers_cull(const DlRect& content) const; + bool rect_covers_cull(const SkRect& content) const { + return rect_covers_cull(ToDlRect(content)); + } + bool oval_covers_cull(const DlRect& content_bounds) const; + bool oval_covers_cull(const SkRect& content_bounds) const { + return oval_covers_cull(ToDlRect(content_bounds)); + } + bool rrect_covers_cull(const SkRRect& content) const; + bool content_culled(const DlRect& content_bounds) const; bool content_culled(const SkRect& content_bounds) const { return content_culled(ToDlRect(content_bounds)); @@ -144,6 +154,7 @@ class DisplayListMatrixClipState { DlRect cull_rect_; DlMatrix matrix_; + bool getLocalCullCorners(DlPoint corners[4]) const; void adjustCullRect(const DlRect& clip, ClipOp op, bool is_aa); friend class DisplayListMatrixClipTracker; diff --git a/display_list/utils/dl_matrix_clip_tracker_unittests.cc b/display_list/utils/dl_matrix_clip_tracker_unittests.cc index 1904cc0ba0aea..c84d2c231833a 100644 --- a/display_list/utils/dl_matrix_clip_tracker_unittests.cc +++ b/display_list/utils/dl_matrix_clip_tracker_unittests.cc @@ -1343,5 +1343,179 @@ TEST(DisplayListMatrixClipState, MapAndClipRectScale) { } } +TEST(DisplayListMatrixClipState, RectCoverage) { + DlRect rect = DlRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + DisplayListMatrixClipState state(rect); + + EXPECT_TRUE(state.rect_covers_cull(rect)); + EXPECT_TRUE(state.rect_covers_cull(rect.Expand(0.1f, 0.0f, 0.0f, 0.0f))); + EXPECT_TRUE(state.rect_covers_cull(rect.Expand(0.0f, 0.1f, 0.0f, 0.0f))); + EXPECT_TRUE(state.rect_covers_cull(rect.Expand(0.0f, 0.0f, 0.1f, 0.0f))); + EXPECT_TRUE(state.rect_covers_cull(rect.Expand(0.0f, 0.0f, 0.0f, 0.1f))); + EXPECT_FALSE(state.rect_covers_cull(rect.Expand(-0.1f, 0.0f, 0.0f, 0.0f))); + EXPECT_FALSE(state.rect_covers_cull(rect.Expand(0.0f, -0.1f, 0.0f, 0.0f))); + EXPECT_FALSE(state.rect_covers_cull(rect.Expand(0.0f, 0.0f, -0.1f, 0.0f))); + EXPECT_FALSE(state.rect_covers_cull(rect.Expand(0.0f, 0.0f, 0.0f, -0.1f))); +} + +TEST(DisplayListMatrixClipState, RectCoverageUnderScale) { + DlRect rect = DlRect::MakeLTRB(100.0f, 100.0f, 200.0f, 200.0f); + DisplayListMatrixClipState state(rect); + state.scale(2.0f, 2.0f); + + EXPECT_FALSE(state.rect_covers_cull(DlRect::MakeLTRB(100, 100, 200, 200))); + EXPECT_TRUE(state.rect_covers_cull(DlRect::MakeLTRB(50, 50, 100, 100))); + EXPECT_TRUE(state.rect_covers_cull(DlRect::MakeLTRB(49, 50, 100, 100))); + EXPECT_TRUE(state.rect_covers_cull(DlRect::MakeLTRB(50, 49, 100, 100))); + EXPECT_TRUE(state.rect_covers_cull(DlRect::MakeLTRB(50, 50, 101, 100))); + EXPECT_TRUE(state.rect_covers_cull(DlRect::MakeLTRB(50, 50, 100, 101))); + EXPECT_FALSE(state.rect_covers_cull(DlRect::MakeLTRB(51, 50, 100, 100))); + EXPECT_FALSE(state.rect_covers_cull(DlRect::MakeLTRB(50, 51, 100, 100))); + EXPECT_FALSE(state.rect_covers_cull(DlRect::MakeLTRB(50, 50, 99, 100))); + EXPECT_FALSE(state.rect_covers_cull(DlRect::MakeLTRB(50, 50, 100, 99))); +} + +TEST(DisplayListMatrixClipState, RectCoverageUnderRotation) { + DlRect rect = DlRect::MakeLTRB(-1.0f, -1.0f, 1.0f, 1.0f); + DlRect cull = rect.Scale(impeller::kSqrt2 * 25); + DlRect test = rect.Scale(50.0f); + DlRect test_true = test.Expand(0.001f); + DlRect test_false = test.Expand(-0.001f); + + for (int i = 0; i <= 360; i++) { + DisplayListMatrixClipState state(cull); + state.rotate(i); + EXPECT_TRUE(state.rect_covers_cull(test_true)) + << " testing " << test_true << std::endl + << " contains " << state.local_cull_rect() << std::endl + << " at " << i << " degrees"; + if ((i % 90) == 45) { + // The cull rect is largest when viewed at multiples of 45 + // degrees so we will fail to contain it at those angles + EXPECT_FALSE(state.rect_covers_cull(test_false)) + << " testing " << test_false << std::endl + << " contains " << state.local_cull_rect() << std::endl + << " at " << i << " degrees"; + } else { + // At other angles, the cull rect is not quite so big as to encroach + // upon the expanded test rectangle. + EXPECT_TRUE(state.rect_covers_cull(test_false)) + << " testing " << test_false << std::endl + << " contains " << state.local_cull_rect() << std::endl + << " at " << i << " degrees"; + } + } +} + +TEST(DisplayListMatrixClipState, OvalCoverage) { + DlRect cull = DlRect::MakeLTRB(-50.0f, -50.0f, 50.0f, 50.0f); + DisplayListMatrixClipState state(cull); + // The cull rect corners will be at (50, 50) so the oval needs to have + // a radius large enough to cover that - sqrt(2*50*50) == sqrt(2) * 50 + // We pad by an ever so slight 0.02f to account for round off error and + // then use larger expansion/contractions of 0.1f to cover/not-cover it. + DlRect test = cull.Scale(impeller::kSqrt2).Expand(0.02f); + + EXPECT_TRUE(state.oval_covers_cull(test)); + EXPECT_TRUE(state.oval_covers_cull(test.Expand(0.1f, 0.0f, 0.0f, 0.0f))); + EXPECT_TRUE(state.oval_covers_cull(test.Expand(0.0f, 0.1f, 0.0f, 0.0f))); + EXPECT_TRUE(state.oval_covers_cull(test.Expand(0.0f, 0.0f, 0.1f, 0.0f))); + EXPECT_TRUE(state.oval_covers_cull(test.Expand(0.0f, 0.0f, 0.0f, 0.1f))); + EXPECT_FALSE(state.oval_covers_cull(test.Expand(-0.1f, 0.0f, 0.0f, 0.0f))); + EXPECT_FALSE(state.oval_covers_cull(test.Expand(0.0f, -0.1f, 0.0f, 0.0f))); + EXPECT_FALSE(state.oval_covers_cull(test.Expand(0.0f, 0.0f, -0.1f, 0.0f))); + EXPECT_FALSE(state.oval_covers_cull(test.Expand(0.0f, 0.0f, 0.0f, -0.1f))); +} + +TEST(DisplayListMatrixClipState, OvalCoverageUnderScale) { + DlRect cull = DlRect::MakeLTRB(-50.0f, -50.0f, 50.0f, 50.0f); + DisplayListMatrixClipState state(cull); + state.scale(2.0f, 2.0f); + // The cull rect corners will be at (50, 50) so the oval needs to have + // a radius large enough to cover that - sqrt(2*50*50) == sqrt(2) * 50 + // We pad by an ever so slight 0.02f to account for round off error and + // then use larger expansion/contractions of 0.1f to cover/not-cover it. + // We combine that with an additional scale 0.5f since we are viewing + // the cull rect under a 2.0 scale. + DlRect test = cull.Scale(0.5f * impeller::kSqrt2).Expand(0.02f); + + EXPECT_TRUE(state.oval_covers_cull(test)); + EXPECT_TRUE(state.oval_covers_cull(test.Expand(0.1f, 0.0f, 0.0f, 0.0f))); + EXPECT_TRUE(state.oval_covers_cull(test.Expand(0.0f, 0.1f, 0.0f, 0.0f))); + EXPECT_TRUE(state.oval_covers_cull(test.Expand(0.0f, 0.0f, 0.1f, 0.0f))); + EXPECT_TRUE(state.oval_covers_cull(test.Expand(0.0f, 0.0f, 0.0f, 0.1f))); + EXPECT_FALSE(state.oval_covers_cull(test.Expand(-0.1f, 0.0f, 0.0f, 0.0f))); + EXPECT_FALSE(state.oval_covers_cull(test.Expand(0.0f, -0.1f, 0.0f, 0.0f))); + EXPECT_FALSE(state.oval_covers_cull(test.Expand(0.0f, 0.0f, -0.1f, 0.0f))); + EXPECT_FALSE(state.oval_covers_cull(test.Expand(0.0f, 0.0f, 0.0f, -0.1f))); +} + +TEST(DisplayListMatrixClipState, OvalCoverageUnderRotation) { + DlRect unit = DlRect::MakeLTRB(-1.0f, -1.0f, 1.0f, 1.0f); + DlRect cull = unit.Scale(50.0f); + // See above, test bounds need to be sqrt(2) larger for the inscribed + // oval to contain the cull rect. These tests are simpler than the scaled + // rectangle coverage tests because this expanded test oval will + // precisely cover the cull rect at all angles. + DlRect test = cull.Scale(impeller::kSqrt2); + DlRect test_true = test.Expand(0.001f); + DlRect test_false = test.Expand(-0.001f); + + for (int i = 0; i <= 360; i++) { + DisplayListMatrixClipState state(cull); + state.rotate(i); + EXPECT_TRUE(state.oval_covers_cull(test_true)) + << " testing " << test_true << std::endl + << " contains " << state.local_cull_rect() << std::endl + << " at " << i << " degrees"; + EXPECT_FALSE(state.oval_covers_cull(test_false)) + << " testing " << test_false << std::endl + << " contains " << state.local_cull_rect() << std::endl + << " at " << i << " degrees"; + } +} + +TEST(DisplayListMatrixClipState, RRectCoverage) { + SkRect cull = SkRect::MakeLTRB(-50.0f, -50.0f, 50.0f, 50.0f); + DisplayListMatrixClipState state(cull); + // test_bounds need to contain + SkRect test = cull.makeOutset(2.0f, 2.0f); + + // RRect of cull with no corners covers + EXPECT_TRUE(state.rrect_covers_cull(SkRRect::MakeRectXY(cull, 0.0f, 0.0f))); + // RRect of cull with even the tiniest corners does not cover + EXPECT_FALSE( + state.rrect_covers_cull(SkRRect::MakeRectXY(cull, 0.01f, 0.01f))); + + // Expanded by 2.0 and then with a corner of 2.0 obviously still covers + EXPECT_TRUE(state.rrect_covers_cull(SkRRect::MakeRectXY(test, 2.0f, 2.0f))); + // The corner point of the cull rect is at (c-2, c-2) relative to the + // corner of the rrect bounds so we compute its disance to the center + // of the circular part and compare it to the radius of the corner (c) + // to find the corner radius where it will start to leave the rounded + // rectangle: + // + // +----------- + + // | __---^^ | + // | +/------- + | + // | / \ | c + // | /| \ c-2 | + // |/ | \ | | + // || | * + + + // + // sqrt(2*(c-2)*(c-2)) > c + // 2*(c-2)*(c-2) > c*c + // 2*(cc - 4c + 4) > cc + // 2cc - 8c + 8 > cc + // cc - 8c + 8 > 0 + // c > 8 +/- sqrt(64 - 32) / 2 + // c > ~6.828 + // corners set to 6.82 should still cover the cull rect + EXPECT_TRUE(state.rrect_covers_cull(SkRRect::MakeRectXY(test, 6.82f, 6.82f))); + // but corners set to 6.83 should not cover the cull rect + EXPECT_FALSE( + state.rrect_covers_cull(SkRRect::MakeRectXY(test, 6.83f, 6.83f))); +} + } // namespace testing } // namespace flutter diff --git a/impeller/geometry/rect.h b/impeller/geometry/rect.h index 4f7bbba55f7ff..328a911013e56 100644 --- a/impeller/geometry/rect.h +++ b/impeller/geometry/rect.h @@ -226,6 +226,25 @@ struct TRect { p.y < bottom_; } + /// @brief Returns true iff the provided point |p| is inside the + /// closed-range interior of this rectangle. + /// + /// Unlike the regular |Contains(TPoint)| method, this method + /// considers all points along the boundary of the rectangle + /// to be contained within the rectangle - useful for testing + /// if vertices that define a filled shape would carry the + /// interior of that shape outside the bounds of the rectangle. + /// Since both geometries are defining half-open spaces, their + /// defining geometry needs to consider their boundaries to + /// be equivalent with respect to interior and exterior. + [[nodiscard]] constexpr bool ContainsInclusive(const TPoint& p) const { + return !this->IsEmpty() && // + p.x >= left_ && // + p.y >= top_ && // + p.x <= right_ && // + p.y <= bottom_; + } + /// @brief Returns true iff this rectangle is not empty and it also /// contains every point considered inside the provided /// rectangle |o| (as determined by |Contains(TPoint)|).