diff --git a/display_list/benchmarking/dl_region_benchmarks.cc b/display_list/benchmarking/dl_region_benchmarks.cc index a07faeba88e8e..bf169baf7db1d 100644 --- a/display_list/benchmarking/dl_region_benchmarks.cc +++ b/display_list/benchmarking/dl_region_benchmarks.cc @@ -5,13 +5,76 @@ #include "flutter/benchmarking/benchmarking.h" #include "flutter/display_list/geometry/dl_region.h" +#include "flutter/fml/logging.h" #include "third_party/skia/include/core/SkRegion.h" #include +namespace { + +template +std::vector GenerateRects(RNG& rng, + const SkIRect& bounds, + int numRects, + int maxSize) { + auto max_size_x = std::min(maxSize, bounds.width()); + auto max_size_y = std::min(maxSize, bounds.height()); + + std::uniform_int_distribution pos_x(bounds.fLeft, bounds.fRight - max_size_x); + std::uniform_int_distribution pos_y(bounds.fTop, bounds.fBottom - max_size_y); + std::uniform_int_distribution size_x(1, max_size_x); + std::uniform_int_distribution size_y(1, max_size_y); + + std::vector rects; + for (int i = 0; i < numRects; ++i) { + SkIRect rect = + SkIRect::MakeXYWH(pos_x(rng), pos_y(rng), size_x(rng), size_y(rng)); + rects.push_back(rect); + } + return rects; +} + +template +SkIRect RandomSubRect(RNG& rng, const SkIRect& rect, double size_factor) { + FML_DCHECK(size_factor <= 1); + + int32_t width = rect.width() * size_factor; + int32_t height = rect.height() * size_factor; + + std::uniform_int_distribution pos_x(0, rect.width() - width); + std::uniform_int_distribution pos_y(0, rect.height() - height); + + return SkIRect::MakeXYWH(rect.fLeft + pos_x(rng), rect.fTop + pos_y(rng), + width, height); +} + class SkRegionAdapter { public: - void addRect(const SkIRect& rect) { region_.op(rect, SkRegion::kUnion_Op); } + explicit SkRegionAdapter(const std::vector& rects) { + region_.setRects(rects.data(), rects.size()); + } + + SkIRect getBounds() { return region_.getBounds(); } + + static SkRegionAdapter unionRegions(const SkRegionAdapter& a1, + const SkRegionAdapter& a2) { + SkRegionAdapter result(a1); + result.region_.op(a2.region_, SkRegion::kUnion_Op); + return result; + } + + static SkRegionAdapter intersectRegions(const SkRegionAdapter& a1, + const SkRegionAdapter& a2) { + SkRegionAdapter result(a1); + result.region_.op(a2.region_, SkRegion::kIntersect_Op); + return result; + } + + bool intersects(const SkRegionAdapter& region) { + return region_.intersects(region.region_); + } + + bool intersects(const SkIRect& rect) { return region_.intersects(rect); } std::vector getRects() { std::vector rects; @@ -29,64 +92,621 @@ class SkRegionAdapter { class DlRegionAdapter { public: - void addRect(const SkIRect& rect) { rects_.push_back(rect); } + explicit DlRegionAdapter(const std::vector& rects) + : region_(rects) {} - std::vector getRects() { - flutter::DlRegion region(std::move(rects_)); - return region.getRects(false); + static DlRegionAdapter unionRegions(const DlRegionAdapter& a1, + const DlRegionAdapter& a2) { + return DlRegionAdapter( + flutter::DlRegion::MakeUnion(a1.region_, a2.region_)); } + static DlRegionAdapter intersectRegions(const DlRegionAdapter& a1, + const DlRegionAdapter& a2) { + return DlRegionAdapter( + flutter::DlRegion::MakeIntersection(a1.region_, a2.region_)); + } + + SkIRect getBounds() { return region_.bounds(); } + + bool intersects(const DlRegionAdapter& region) { + return region_.intersects(region.region_); + } + + bool intersects(const SkIRect& rect) { return region_.intersects(rect); } + + std::vector getRects() { return region_.getRects(false); } + private: - std::vector rects_; + explicit DlRegionAdapter(flutter::DlRegion&& region) + : region_(std::move(region)) {} + + flutter::DlRegion region_; }; template -void RunRegionBenchmark(benchmark::State& state, int maxSize) { +void RunFromRectsBenchmark(benchmark::State& state, int maxSize) { + std::random_device d; + std::seed_seq seed{2, 1, 3}; + std::mt19937 rng(seed); + + std::uniform_int_distribution pos(0, 4000); + std::uniform_int_distribution size(1, maxSize); + + std::vector rects; + for (int i = 0; i < 2000; ++i) { + SkIRect rect = SkIRect::MakeXYWH(pos(rng), pos(rng), size(rng), size(rng)); + rects.push_back(rect); + } + while (state.KeepRunning()) { - std::random_device d; - std::seed_seq seed{2, 1, 3}; - std::mt19937 rng(seed); + Region region(rects); + } +} - std::uniform_int_distribution pos(0, 4000); - std::uniform_int_distribution size(1, maxSize); +template +void RunGetRectsBenchmark(benchmark::State& state, int maxSize) { + std::random_device d; + std::seed_seq seed{2, 1, 3}; + std::mt19937 rng(seed); - Region region; + std::uniform_int_distribution pos(0, 4000); + std::uniform_int_distribution size(1, maxSize); - for (int i = 0; i < 2000; ++i) { - SkIRect rect = - SkIRect::MakeXYWH(pos(rng), pos(rng), size(rng), size(rng)); - region.addRect(rect); - } + std::vector rects; + for (int i = 0; i < 2000; ++i) { + SkIRect rect = SkIRect::MakeXYWH(pos(rng), pos(rng), size(rng), size(rng)); + rects.push_back(rect); + } + + Region region(rects); + while (state.KeepRunning()) { auto vec2 = region.getRects(); } } +enum RegionOp { kUnion, kIntersection }; + +template +void RunRegionOpBenchmark(benchmark::State& state, + RegionOp op, + bool withSingleRect, + int maxSize, + double sizeFactor) { + std::random_device d; + std::seed_seq seed{2, 1, 3}; + std::mt19937 rng(seed); + + SkIRect bounds1 = SkIRect::MakeWH(4000, 4000); + SkIRect bounds2 = RandomSubRect(rng, bounds1, sizeFactor); + + auto rects = GenerateRects(rng, bounds1, 500, maxSize); + Region region1(rects); + + rects = GenerateRects(rng, bounds2, withSingleRect ? 1 : 500 * sizeFactor, + maxSize); + Region region2(rects); + + switch (op) { + case kUnion: + while (state.KeepRunning()) { + Region::unionRegions(region1, region2); + } + break; + case kIntersection: + while (state.KeepRunning()) { + Region::intersectRegions(region1, region2); + } + break; + } +} + +template +void RunIntersectsRegionBenchmark(benchmark::State& state, + int maxSize, + double sizeFactor) { + std::random_device d; + std::seed_seq seed{2, 1, 3}; + std::mt19937 rng(seed); + + SkIRect bounds1 = SkIRect::MakeWH(4000, 4000); + SkIRect bounds2 = RandomSubRect(rng, bounds1, sizeFactor); + + auto rects = GenerateRects(rng, bounds1, 500, maxSize); + Region region1(rects); + + rects = GenerateRects(rng, bounds2, 500 * sizeFactor, maxSize); + Region region2(rects); + + while (state.KeepRunning()) { + region1.intersects(region2); + } +} + +template +void RunIntersectsSingleRectBenchmark(benchmark::State& state, int maxSize) { + std::random_device d; + std::seed_seq seed{2, 1, 3}; + std::mt19937 rng(seed); + + std::uniform_int_distribution pos(0, 4000); + std::uniform_int_distribution size(1, maxSize); + + std::vector rects; + for (int i = 0; i < 500; ++i) { + SkIRect rect = SkIRect::MakeXYWH(pos(rng), pos(rng), size(rng), size(rng)); + rects.push_back(rect); + } + Region region1(rects); + + rects.clear(); + for (int i = 0; i < 100; ++i) { + SkIRect rect = SkIRect::MakeXYWH(pos(rng), pos(rng), size(rng), size(rng)); + rects.push_back(rect); + } + + while (state.KeepRunning()) { + for (auto& rect : rects) { + region1.intersects(rect); + } + } +} + +} // namespace + namespace flutter { -static void BM_RegionBenchmarkSkRegion(benchmark::State& state, int maxSize) { - RunRegionBenchmark(state, maxSize); +static void BM_DlRegion_FromRects(benchmark::State& state, int maxSize) { + RunFromRectsBenchmark(state, maxSize); +} + +static void BM_SkRegion_FromRects(benchmark::State& state, int maxSize) { + RunFromRectsBenchmark(state, maxSize); } -static void BM_RegionBenchmarkDlRegion(benchmark::State& state, int maxSize) { - RunRegionBenchmark(state, maxSize); +static void BM_DlRegion_GetRects(benchmark::State& state, int maxSize) { + RunGetRectsBenchmark(state, maxSize); } -BENCHMARK_CAPTURE(BM_RegionBenchmarkDlRegion, Tiny, 30) +static void BM_SkRegion_GetRects(benchmark::State& state, int maxSize) { + RunGetRectsBenchmark(state, maxSize); +} + +static void BM_DlRegion_Operation(benchmark::State& state, + RegionOp op, + bool withSingleRect, + int maxSize, + double sizeFactor) { + RunRegionOpBenchmark(state, op, withSingleRect, maxSize, + sizeFactor); +} + +static void BM_SkRegion_Operation(benchmark::State& state, + RegionOp op, + bool withSingleRect, + int maxSize, + double sizeFactor) { + RunRegionOpBenchmark(state, op, withSingleRect, maxSize, + sizeFactor); +} + +static void BM_DlRegion_IntersectsRegion(benchmark::State& state, + int maxSize, + double sizeFactor) { + RunIntersectsRegionBenchmark(state, maxSize, sizeFactor); +} + +static void BM_SkRegion_IntersectsRegion(benchmark::State& state, + int maxSize, + double sizeFactor) { + RunIntersectsRegionBenchmark(state, maxSize, sizeFactor); +} + +static void BM_DlRegion_IntersectsSingleRect(benchmark::State& state, + int maxSize) { + RunIntersectsSingleRectBenchmark(state, maxSize); +} + +static void BM_SkRegion_IntersectsSingleRect(benchmark::State& state, + int maxSize) { + RunIntersectsSingleRectBenchmark(state, maxSize); +} + +const double kSizeFactorSmall = 0.3; + +BENCHMARK_CAPTURE(BM_DlRegion_IntersectsSingleRect, Tiny, 30) + ->Unit(benchmark::kNanosecond); +BENCHMARK_CAPTURE(BM_SkRegion_IntersectsSingleRect, Tiny, 30) + ->Unit(benchmark::kNanosecond); +BENCHMARK_CAPTURE(BM_DlRegion_IntersectsSingleRect, Small, 100) + ->Unit(benchmark::kNanosecond); +BENCHMARK_CAPTURE(BM_SkRegion_IntersectsSingleRect, Small, 100) + ->Unit(benchmark::kNanosecond); +BENCHMARK_CAPTURE(BM_DlRegion_IntersectsSingleRect, Medium, 400) + ->Unit(benchmark::kNanosecond); +BENCHMARK_CAPTURE(BM_SkRegion_IntersectsSingleRect, Medium, 400) + ->Unit(benchmark::kNanosecond); +BENCHMARK_CAPTURE(BM_DlRegion_IntersectsSingleRect, Large, 1500) + ->Unit(benchmark::kNanosecond); +BENCHMARK_CAPTURE(BM_SkRegion_IntersectsSingleRect, Large, 1500) + ->Unit(benchmark::kNanosecond); + +BENCHMARK_CAPTURE(BM_DlRegion_IntersectsRegion, Tiny, 30, 1.0) + ->Unit(benchmark::kNanosecond); +BENCHMARK_CAPTURE(BM_SkRegion_IntersectsRegion, Tiny, 30, 1.0) + ->Unit(benchmark::kNanosecond); +BENCHMARK_CAPTURE(BM_DlRegion_IntersectsRegion, Small, 100, 1.0) + ->Unit(benchmark::kNanosecond); +BENCHMARK_CAPTURE(BM_SkRegion_IntersectsRegion, Small, 100, 1.0) + ->Unit(benchmark::kNanosecond); +BENCHMARK_CAPTURE(BM_DlRegion_IntersectsRegion, Medium, 400, 1.0) + ->Unit(benchmark::kNanosecond); +BENCHMARK_CAPTURE(BM_SkRegion_IntersectsRegion, Medium, 400, 1.0) + ->Unit(benchmark::kNanosecond); +BENCHMARK_CAPTURE(BM_DlRegion_IntersectsRegion, Large, 1500, 1.0) + ->Unit(benchmark::kNanosecond); +BENCHMARK_CAPTURE(BM_SkRegion_IntersectsRegion, Large, 1500, 1.0) + ->Unit(benchmark::kNanosecond); + +BENCHMARK_CAPTURE(BM_DlRegion_IntersectsRegion, + TinyAsymmetric, + 30, + kSizeFactorSmall) + ->Unit(benchmark::kNanosecond); +BENCHMARK_CAPTURE(BM_SkRegion_IntersectsRegion, + TinyAsymmetric, + 30, + kSizeFactorSmall) + ->Unit(benchmark::kNanosecond); +BENCHMARK_CAPTURE(BM_DlRegion_IntersectsRegion, + SmallAsymmetric, + 100, + kSizeFactorSmall) + ->Unit(benchmark::kNanosecond); +BENCHMARK_CAPTURE(BM_SkRegion_IntersectsRegion, + SmallAsymmetric, + 100, + kSizeFactorSmall) + ->Unit(benchmark::kNanosecond); +BENCHMARK_CAPTURE(BM_DlRegion_IntersectsRegion, + MediumAsymmetric, + 400, + kSizeFactorSmall) + ->Unit(benchmark::kNanosecond); +BENCHMARK_CAPTURE(BM_SkRegion_IntersectsRegion, + MediumAsymmetric, + 400, + kSizeFactorSmall) + ->Unit(benchmark::kNanosecond); +BENCHMARK_CAPTURE(BM_DlRegion_IntersectsRegion, + LargeAsymmetric, + 1500, + kSizeFactorSmall) + ->Unit(benchmark::kNanosecond); +BENCHMARK_CAPTURE(BM_SkRegion_IntersectsRegion, + LargeAsymmetric, + 1500, + kSizeFactorSmall) + ->Unit(benchmark::kNanosecond); + +BENCHMARK_CAPTURE(BM_DlRegion_Operation, + Union_Tiny, + RegionOp::kUnion, + false, + 30, + 1.0) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_Operation, + Union_Tiny, + RegionOp::kUnion, + false, + 30, + 1.0) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DlRegion_Operation, + Union_Small, + RegionOp::kUnion, + false, + 100, + 1.0) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_Operation, + Union_Small, + RegionOp::kUnion, + false, + 100, + 1.0) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DlRegion_Operation, + Union_Medium, + RegionOp::kUnion, + false, + 400, + 1.0) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_Operation, + Union_Medium, + RegionOp::kUnion, + false, + 400, + 1.0) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DlRegion_Operation, + Union_Large, + RegionOp::kUnion, + false, + 1500, + 1.0) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_Operation, + Union_Large, + RegionOp::kUnion, + false, + 1500, + 1.0) + ->Unit(benchmark::kMicrosecond); + +BENCHMARK_CAPTURE(BM_DlRegion_Operation, + Union_TinyAsymmetric, + RegionOp::kUnion, + false, + 30, + kSizeFactorSmall) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_Operation, + Union_TinyAsymmetric, + RegionOp::kUnion, + false, + 30, + kSizeFactorSmall) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DlRegion_Operation, + Union_SmallAsymmetric, + RegionOp::kUnion, + false, + 100, + kSizeFactorSmall) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_Operation, + Union_SmallAsymmetric, + RegionOp::kUnion, + false, + 100, + kSizeFactorSmall) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DlRegion_Operation, + Union_MediumAsymmetric, + RegionOp::kUnion, + false, + 400, + kSizeFactorSmall) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_Operation, + Union_MediumAsymmetric, + RegionOp::kUnion, + false, + 400, + kSizeFactorSmall) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DlRegion_Operation, + Union_LargeAsymmetric, + RegionOp::kUnion, + false, + 1500, + kSizeFactorSmall) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_Operation, + Union_LargeAsymmetric, + RegionOp::kUnion, + false, + 1500, + kSizeFactorSmall) + ->Unit(benchmark::kMicrosecond); + +BENCHMARK_CAPTURE(BM_DlRegion_Operation, + Intersection_Tiny, + RegionOp::kIntersection, + false, + 30, + 1.0) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_Operation, + Intersection_Tiny, + RegionOp::kIntersection, + false, + 30, + 1.0) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DlRegion_Operation, + Intersection_Small, + RegionOp::kIntersection, + false, + 100, + 1.0) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_Operation, + Intersection_Small, + RegionOp::kIntersection, + false, + 100, + 1.0) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DlRegion_Operation, + Intersection_Medium, + RegionOp::kIntersection, + false, + 400, + 1.0) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_Operation, + Intersection_Medium, + RegionOp::kIntersection, + false, + 400, + 1.0) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DlRegion_Operation, + Intersection_Large, + RegionOp::kIntersection, + false, + 1500, + 1.0) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_Operation, + Intersection_Large, + RegionOp::kIntersection, + false, + 1500, + 1.0) + ->Unit(benchmark::kMicrosecond); + +BENCHMARK_CAPTURE(BM_DlRegion_Operation, + Intersection_TinyAsymmetric, + RegionOp::kIntersection, + false, + 30, + kSizeFactorSmall) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_Operation, + Intersection_TinyAsymmetric, + RegionOp::kIntersection, + false, + 30, + kSizeFactorSmall) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DlRegion_Operation, + Intersection_SmallAsymmetric, + RegionOp::kIntersection, + false, + 100, + kSizeFactorSmall) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_Operation, + Intersection_SmallAsymmetric, + RegionOp::kIntersection, + false, + 100, + kSizeFactorSmall) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DlRegion_Operation, + Intersection_MediumAsymmetric, + RegionOp::kIntersection, + false, + 400, + kSizeFactorSmall) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_Operation, + Intersection_MediumAsymmetric, + RegionOp::kIntersection, + false, + 400, + kSizeFactorSmall) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DlRegion_Operation, + Intersection_LargeAsymmetric, + RegionOp::kIntersection, + false, + 1500, + kSizeFactorSmall) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_Operation, + Intersection_LargeAsymmetric, + RegionOp::kIntersection, + false, + 1500, + kSizeFactorSmall) + ->Unit(benchmark::kMicrosecond); + +BENCHMARK_CAPTURE(BM_DlRegion_Operation, + Intersection_SingleRect_Tiny, + RegionOp::kIntersection, + true, + 30, + 1.0) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_Operation, + Intersection_SingleRect_Tiny, + RegionOp::kIntersection, + true, + 30, + 1.0) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DlRegion_Operation, + Intersection_SingleRect_Small, + RegionOp::kIntersection, + true, + 100, + 1.0) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_Operation, + Intersection_SingleRect_Small, + RegionOp::kIntersection, + true, + 100, + 1.0) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DlRegion_Operation, + Intersection_SingleRect_Medium, + RegionOp::kIntersection, + true, + 400, + 1.0) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_Operation, + Intersection_SingleRect_Medium, + RegionOp::kIntersection, + true, + 400, + 1.0) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DlRegion_Operation, + Intersection_SingleRect_Large, + RegionOp::kIntersection, + true, + 1500, + 1.0) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_Operation, + Intersection_SingleRect_Large, + RegionOp::kIntersection, + true, + 1500, + 1.0) + ->Unit(benchmark::kMicrosecond); + +BENCHMARK_CAPTURE(BM_DlRegion_FromRects, Tiny, 30) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_FromRects, Tiny, 30) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DlRegion_FromRects, Small, 100) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_FromRects, Small, 100) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DlRegion_FromRects, Medium, 400) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_FromRects, Medium, 400) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DlRegion_FromRects, Large, 1500) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_SkRegion_FromRects, Large, 1500) + ->Unit(benchmark::kMicrosecond); + +BENCHMARK_CAPTURE(BM_DlRegion_GetRects, Tiny, 30) ->Unit(benchmark::kMicrosecond); -BENCHMARK_CAPTURE(BM_RegionBenchmarkSkRegion, Tiny, 30) +BENCHMARK_CAPTURE(BM_SkRegion_GetRects, Tiny, 30) ->Unit(benchmark::kMicrosecond); -BENCHMARK_CAPTURE(BM_RegionBenchmarkDlRegion, Small, 100) +BENCHMARK_CAPTURE(BM_DlRegion_GetRects, Small, 100) ->Unit(benchmark::kMicrosecond); -BENCHMARK_CAPTURE(BM_RegionBenchmarkSkRegion, Small, 100) +BENCHMARK_CAPTURE(BM_SkRegion_GetRects, Small, 100) ->Unit(benchmark::kMicrosecond); -BENCHMARK_CAPTURE(BM_RegionBenchmarkDlRegion, Medium, 400) +BENCHMARK_CAPTURE(BM_DlRegion_GetRects, Medium, 400) ->Unit(benchmark::kMicrosecond); -BENCHMARK_CAPTURE(BM_RegionBenchmarkSkRegion, Medium, 400) +BENCHMARK_CAPTURE(BM_SkRegion_GetRects, Medium, 400) ->Unit(benchmark::kMicrosecond); -BENCHMARK_CAPTURE(BM_RegionBenchmarkDlRegion, Large, 1500) +BENCHMARK_CAPTURE(BM_DlRegion_GetRects, Large, 1500) ->Unit(benchmark::kMicrosecond); -BENCHMARK_CAPTURE(BM_RegionBenchmarkSkRegion, Large, 1500) +BENCHMARK_CAPTURE(BM_SkRegion_GetRects, Large, 1500) ->Unit(benchmark::kMicrosecond); } // namespace flutter \ No newline at end of file diff --git a/display_list/geometry/dl_region.cc b/display_list/geometry/dl_region.cc index 281fd142b393f..3e900972fd71e 100644 --- a/display_list/geometry/dl_region.cc +++ b/display_list/geometry/dl_region.cc @@ -8,31 +8,579 @@ namespace flutter { -DlRegion::DlRegion(std::vector&& rects) { - // If SpanLines can not be memmoved `addRect` would be signifantly slower - // due to cost of inserting and removing elements from the `lines_` vector. - static_assert(std::is_trivially_constructible::value, - "SpanLine must be trivially constructible."); - addRects(std::move(rects)); +// Threshold for switching from linear search through span lines to binary +// search. +const int kBinarySearchThreshold = 10; - for (auto& spanvec : spanvec_pool_) { - delete spanvec; +DlRegion::SpanBuffer::SpanBuffer(DlRegion::SpanBuffer&& m) + : capacity_(m.capacity_), size_(m.size_), spans_(m.spans_) { + m.size_ = 0; + m.capacity_ = 0; + m.spans_ = nullptr; +}; + +DlRegion::SpanBuffer::SpanBuffer(const DlRegion::SpanBuffer& m) + : capacity_(m.capacity_), size_(m.size_) { + if (m.spans_ == nullptr) { + spans_ = nullptr; + } else { + spans_ = static_cast(std::malloc(capacity_ * sizeof(Span))); + memcpy(spans_, m.spans_, size_ * sizeof(Span)); + } +}; + +DlRegion::SpanBuffer& DlRegion::SpanBuffer::operator=( + const DlRegion::SpanBuffer& buffer) { + SpanBuffer copy(buffer); + std::swap(*this, copy); + return *this; +} + +DlRegion::SpanBuffer& DlRegion::SpanBuffer::operator=( + DlRegion::SpanBuffer&& buffer) { + std::swap(capacity_, buffer.capacity_); + std::swap(size_, buffer.size_); + std::swap(spans_, buffer.spans_); + return *this; +} + +DlRegion::SpanBuffer::~SpanBuffer() { + free(spans_); +} + +void DlRegion::SpanBuffer::reserve(size_t capacity) { + if (capacity_ < capacity) { + spans_ = static_cast(std::realloc(spans_, capacity * sizeof(Span))); + capacity_ = capacity; + } +} + +DlRegion::SpanChunkHandle DlRegion::SpanBuffer::storeChunk(const Span* begin, + const Span* end) { + size_t chunk_size = end - begin; + size_t min_capacity = size_ + chunk_size + 1; + if (capacity_ < min_capacity) { + size_t new_capacity = std::max(min_capacity, capacity_ * 2); + new_capacity = std::max(new_capacity, size_t(512)); + reserve(new_capacity); + } + SpanChunkHandle res = size_; + size_ += chunk_size + 1; + setChunkSize(res, chunk_size); + + auto* dst = spans_ + res + 1; + memmove(dst, begin, chunk_size * sizeof(Span)); + + return res; +} + +size_t DlRegion::SpanBuffer::getChunkSize(SpanChunkHandle handle) const { + FML_DCHECK(handle < size_); + return spans_[handle].left; +} + +void DlRegion::SpanBuffer::setChunkSize(SpanChunkHandle handle, size_t size) { + FML_DCHECK(handle < size_); + FML_DCHECK(spans_ != nullptr); + // NOLINTNEXTLINE(clang-analyzer-core.NullDereference) + spans_[handle].left = size; +} + +void DlRegion::SpanBuffer::getSpans(SpanChunkHandle handle, + const DlRegion::Span*& begin, + const DlRegion::Span*& end) const { + FML_DCHECK(handle < size_); + begin = spans_ + handle + 1; + end = begin + getChunkSize(handle); +} + +DlRegion::DlRegion(const std::vector& rects) { + setRects(rects); +} + +DlRegion::DlRegion(const SkIRect& rect) : bounds_(rect) { + Span span{rect.left(), rect.right()}; + lines_.push_back(makeLine(rect.top(), rect.bottom(), &span, &span + 1)); +} + +bool DlRegion::spansEqual(SpanLine& line, + const Span* begin, + const Span* end) const { + const Span *our_begin, *our_end; + span_buffer_.getSpans(line.chunk_handle, our_begin, our_end); + size_t our_size = our_end - our_begin; + size_t their_size = end - begin; + if (our_size != their_size) { + return false; + } + + return memcmp(our_begin, begin, our_size * sizeof(Span)) == 0; +} + +DlRegion::SpanLine DlRegion::makeLine(int32_t top, + int32_t bottom, + const SpanVec& v) { + return makeLine(top, bottom, v.data(), v.data() + v.size()); +} + +DlRegion::SpanLine DlRegion::makeLine(int32_t top, + int32_t bottom, + const Span* begin, + const Span* end) { + auto handle = span_buffer_.storeChunk(begin, end); + return {top, bottom, handle}; +} + +// Returns number of valid spans in res. For performance reasons res is never +// downsized. +size_t DlRegion::unionLineSpans(std::vector& res, + const SpanBuffer& a_buffer, + SpanChunkHandle a_handle, + const SpanBuffer& b_buffer, + SpanChunkHandle b_handle) { + class OrderedSpanAccumulator { + public: + explicit OrderedSpanAccumulator(std::vector& res) : res(res) {} + + void accumulate(const Span& span) { + if (span.left > last_ || len == 0) { + res[len++] = span; + last_ = span.right; + } else if (span.right > last_) { + FML_DCHECK(len > 0); + res[len - 1].right = span.right; + last_ = span.right; + } + } + + size_t len = 0; + std::vector& res; + + private: + int32_t last_ = std::numeric_limits::min(); + }; + + const Span *begin1, *end1; + a_buffer.getSpans(a_handle, begin1, end1); + + const Span *begin2, *end2; + b_buffer.getSpans(b_handle, begin2, end2); + + size_t min_size = (end1 - begin1) + (end2 - begin2); + if (res.size() < min_size) { + res.resize(min_size); + } + + OrderedSpanAccumulator accumulator(res); + + while (true) { + if (begin1->left < begin2->left) { + accumulator.accumulate(*begin1++); + if (begin1 == end1) { + break; + } + } else { + // Either 2 is first, or they are equal, in which case add 2 now + // and we might combine 1 with it next time around + accumulator.accumulate(*begin2++); + if (begin2 == end2) { + break; + } + } + } + + FML_DCHECK(begin1 == end1 || begin2 == end2); + + while (begin1 < end1) { + accumulator.accumulate(*begin1++); } - spanvec_pool_.clear(); + while (begin2 < end2) { + accumulator.accumulate(*begin2++); + } + + FML_DCHECK(begin1 == end1 && begin2 == end2); + + return accumulator.len; } -DlRegion::~DlRegion() { - for (auto& line : lines_) { - delete line.spans; +size_t DlRegion::intersectLineSpans(std::vector& res, + const SpanBuffer& a_buffer, + SpanChunkHandle a_handle, + const SpanBuffer& b_buffer, + SpanChunkHandle b_handle) { + const Span *begin1, *end1; + a_buffer.getSpans(a_handle, begin1, end1); + + const Span *begin2, *end2; + b_buffer.getSpans(b_handle, begin2, end2); + + // Worst case scenario, interleaved overlapping spans + // AAAA BBBB CCCC + // XXX YYYY XXXX + size_t min_size = (end1 - begin1) + (end2 - begin2) - 1; + if (res.size() < min_size) { + res.resize(min_size); } + + // Pointer to the next span to be written. + Span* new_span = res.data(); + + while (begin1 != end1 && begin2 != end2) { + if (begin1->right <= begin2->left) { + ++begin1; + } else if (begin2->right <= begin1->left) { + ++begin2; + } else { + int32_t left = std::max(begin1->left, begin2->left); + int32_t right = std::min(begin1->right, begin2->right); + FML_DCHECK(left < right); + FML_DCHECK(new_span < res.data() + res.size()); + *new_span++ = {left, right}; + if (begin1->right == right) { + ++begin1; + } + if (begin2->right == right) { + ++begin2; + } + } + } + + return new_span - res.data(); +} + +void DlRegion::setRects(const std::vector& unsorted_rects) { + // setRects can only be called on empty regions. + FML_DCHECK(lines_.empty()); + + size_t count = unsorted_rects.size(); + std::vector rects(count); + for (size_t i = 0; i < count; i++) { + rects[i] = &unsorted_rects[i]; + bounds_.join(unsorted_rects[i]); + } + std::sort(rects.begin(), rects.end(), [](const SkIRect* a, const SkIRect* b) { + if (a->top() < b->top()) { + return true; + } + if (a->top() > b->top()) { + return false; + } + return a->left() < b->left(); + }); + + size_t active_end = 0; + size_t next_rect = 0; + int32_t cur_y = std::numeric_limits::min(); + SpanVec working_spans; + +#ifdef DlRegion_DO_STATS + size_t active_rect_count = 0; + size_t span_count = 0; + int pass_count = 0; + int line_count = 0; +#endif + + while (next_rect < count || active_end > 0) { + // First prune passed rects out of the active list + size_t preserve_end = 0; + for (size_t i = 0; i < active_end; i++) { + const SkIRect* r = rects[i]; + if (r->bottom() > cur_y) { + rects[preserve_end++] = r; + } + } + active_end = preserve_end; + + // If we have no active rects any more, jump to the top of the + // next available input rect. + if (active_end == 0) { + if (next_rect >= count) { + // No active rects and no more rects to bring in. We are done. + break; + } + cur_y = rects[next_rect]->top(); + } + + // Next, insert any new rects we've reached into the active list + while (next_rect < count) { + const SkIRect* r = rects[next_rect]; + if (r->isEmpty()) { + continue; + } + if (r->top() > cur_y) { + break; + } + // We now know that we will be inserting this rect into active list + next_rect++; + size_t insert_at = active_end++; + while (insert_at > 0) { + const SkIRect* ir = rects[insert_at - 1]; + if (ir->left() <= r->left()) { + break; + } + rects[insert_at--] = ir; + } + rects[insert_at] = r; + } + + // We either preserved some rects in the active list or added more from + // the remaining input rects, or we would have exited the loop above. + FML_DCHECK(active_end != 0); + working_spans.clear(); + FML_DCHECK(working_spans.empty()); + +#ifdef DlRegion_DO_STATS + active_rect_count += active_end; + pass_count++; +#endif + + // [start_x, end_x) always represents a valid span to be inserted + // [cur_y, end_y) is the intersecting range over which all spans are valid + int32_t start_x = rects[0]->left(); + int32_t end_x = rects[0]->right(); + int32_t end_y = rects[0]->bottom(); + for (size_t i = 1; i < active_end; i++) { + const SkIRect* r = rects[i]; + if (r->left() > end_x) { + working_spans.emplace_back(start_x, end_x); + start_x = r->left(); + end_x = r->right(); + } else if (end_x < r->right()) { + end_x = r->right(); + } + if (end_y > r->bottom()) { + end_y = r->bottom(); + } + } + working_spans.emplace_back(start_x, end_x); + + // end_y must not pass by the top of the next input rect + if (next_rect < count && end_y > rects[next_rect]->top()) { + end_y = rects[next_rect]->top(); + } + + // If all of the rules above work out, we should never collapse the + // current range of Y coordinates to empty + FML_DCHECK(end_y > cur_y); + + if (!lines_.empty() && lines_.back().bottom == cur_y && + spansEqual(lines_.back(), working_spans.data(), + working_spans.data() + working_spans.size())) { + lines_.back().bottom = end_y; + } else { +#ifdef DlRegion_DO_STATS + span_count += working_spans.size(); + line_count++; +#endif + lines_.push_back(makeLine(cur_y, end_y, working_spans)); + } + cur_y = end_y; + } + +#ifdef DlRegion_DO_STATS + double span_avg = ((double)span_count) / line_count; + double active_avg = ((double)active_rect_count) / pass_count; + FML_LOG(ERROR) << lines_.size() << " lines for " << count + << " input rects, avg " << span_avg + << " spans per line and avg " << active_avg + << " active rects per loop"; +#endif +} + +void DlRegion::appendLine(int32_t top, + int32_t bottom, + const Span* begin, + const Span* end) { + if (lines_.empty()) { + lines_.push_back(makeLine(top, bottom, begin, end)); + } else { + if (lines_.back().bottom == top && spansEqual(lines_.back(), begin, end)) { + lines_.back().bottom = bottom; + } else { + lines_.push_back(makeLine(top, bottom, begin, end)); + } + } +} + +DlRegion DlRegion::MakeUnion(const DlRegion& a, const DlRegion& b) { + if (a.isEmpty()) { + return b; + } else if (b.isEmpty()) { + return a; + } else if (a.isSimple() && a.bounds_.contains(b.bounds_)) { + return a; + } else if (b.isSimple() && b.bounds_.contains(a.bounds_)) { + return b; + } + + DlRegion res; + res.bounds_ = a.bounds_; + res.bounds_.join(b.bounds_); + res.span_buffer_.reserve(a.span_buffer_.capacity() + + b.span_buffer_.capacity()); + + auto& lines = res.lines_; + lines.reserve(a.lines_.size() + b.lines_.size()); + + auto a_it = a.lines_.begin(); + auto b_it = b.lines_.begin(); + auto a_end = a.lines_.end(); + auto b_end = b.lines_.end(); + + FML_DCHECK(a_it != a_end && b_it != b_end); + + auto& a_buffer = a.span_buffer_; + auto& b_buffer = b.span_buffer_; + + std::vector tmp; + + int32_t cur_top = std::numeric_limits::min(); + + while (a_it != a_end && b_it != b_end) { + auto a_top = std::max(cur_top, a_it->top); + auto b_top = std::max(cur_top, b_it->top); + if (a_it->bottom <= b_top) { + res.appendLine(a_top, a_it->bottom, a_buffer, a_it->chunk_handle); + ++a_it; + } else if (b_it->bottom <= a_top) { + res.appendLine(b_top, b_it->bottom, b_buffer, b_it->chunk_handle); + ++b_it; + } else { + if (a_top < b_top) { + res.appendLine(a_top, b_top, a_buffer, a_it->chunk_handle); + cur_top = b_top; + if (cur_top == a_it->bottom) { + ++a_it; + } + } else if (b_top < a_top) { + res.appendLine(b_top, a_top, b_buffer, b_it->chunk_handle); + cur_top = a_top; + if (cur_top == b_it->bottom) { + ++b_it; + } + } else { + auto new_bottom = std::min(a_it->bottom, b_it->bottom); + FML_DCHECK(a_top == b_top); + FML_DCHECK(new_bottom > a_top); + FML_DCHECK(new_bottom > b_top); + auto size = unionLineSpans(tmp, a_buffer, a_it->chunk_handle, b_buffer, + b_it->chunk_handle); + res.appendLine(a_top, new_bottom, tmp.data(), tmp.data() + size); + cur_top = new_bottom; + if (cur_top == a_it->bottom) { + ++a_it; + } + if (cur_top == b_it->bottom) { + ++b_it; + } + } + } + } + + FML_DCHECK(a_it == a_end || b_it == b_end); + + while (a_it != a_end) { + auto a_top = std::max(cur_top, a_it->top); + res.appendLine(a_top, a_it->bottom, a_buffer, a_it->chunk_handle); + ++a_it; + } + + while (b_it != b_end) { + auto b_top = std::max(cur_top, b_it->top); + res.appendLine(b_top, b_it->bottom, b_buffer, b_it->chunk_handle); + ++b_it; + } + + return res; +} + +DlRegion DlRegion::MakeIntersection(const DlRegion& a, const DlRegion& b) { + if (!SkIRect::Intersects(a.bounds_, b.bounds_)) { + return DlRegion(); + } else if (a.isSimple() && b.isSimple()) { + SkIRect r(a.bounds_); + auto res = r.intersect(b.bounds_); + (void)res; // Suppress unused variable warning in release builds. + FML_DCHECK(res); + return DlRegion(r); + } else if (a.isSimple() && a.bounds_.contains(b.bounds_)) { + return b; + } else if (b.isSimple() && b.bounds_.contains(a.bounds_)) { + return a; + } + + DlRegion res; + res.span_buffer_.reserve( + std::max(a.span_buffer_.capacity(), b.span_buffer_.capacity())); + + auto& lines = res.lines_; + lines.reserve(std::min(a.lines_.size(), b.lines_.size())); + + std::vector::const_iterator a_it, b_it; + getIntersectionIterators(a.lines_, b.lines_, a_it, b_it); + + auto a_end = a.lines_.end(); + auto b_end = b.lines_.end(); + + auto& a_buffer = a.span_buffer_; + auto& b_buffer = b.span_buffer_; + + std::vector tmp; + + int32_t cur_top = std::numeric_limits::min(); + + while (a_it != a_end && b_it != b_end) { + auto a_top = std::max(cur_top, a_it->top); + auto b_top = std::max(cur_top, b_it->top); + if (a_it->bottom <= b_top) { + ++a_it; + } else if (b_it->bottom <= a_top) { + ++b_it; + } else { + auto top = std::max(a_top, b_top); + auto bottom = std::min(a_it->bottom, b_it->bottom); + FML_DCHECK(top < bottom); + auto size = intersectLineSpans(tmp, a_buffer, a_it->chunk_handle, + b_buffer, b_it->chunk_handle); + if (size > 0) { + res.appendLine(top, bottom, tmp.data(), tmp.data() + size); + res.bounds_.join(SkIRect::MakeLTRB( + tmp.data()->left, top, (tmp.data() + size - 1)->right, bottom)); + } + cur_top = bottom; + if (cur_top == a_it->bottom) { + ++a_it; + } + if (cur_top == b_it->bottom) { + ++b_it; + } + } + } + FML_DCHECK(a_it == a_end || b_it == b_end); + return res; } std::vector DlRegion::getRects(bool deband) const { std::vector rects; + if (isEmpty()) { + return rects; + } else if (isSimple()) { + rects.push_back(bounds_); + return rects; + } + + size_t rect_count = 0; size_t previous_span_end = 0; for (const auto& line : lines_) { - for (const Span& span : *line.spans) { - SkIRect rect{span.left, line.top, span.right, line.bottom}; + rect_count += span_buffer_.getChunkSize(line.chunk_handle); + } + rects.reserve(rect_count); + + for (const auto& line : lines_) { + const Span *span_begin, *span_end; + span_buffer_.getSpans(line.chunk_handle, span_begin, span_end); + for (const auto* span = span_begin; span < span_end; ++span) { + SkIRect rect{span->left, line.top, span->right, line.bottom}; if (deband) { auto iter = rects.begin() + previous_span_end; // If there is rectangle previously in rects on which this one is a @@ -60,189 +608,153 @@ std::vector DlRegion::getRects(bool deband) const { return rects; } -void DlRegion::SpanLine::insertSpan(int32_t left, int32_t right) { - auto& spans = *this->spans; - auto size = spans.size(); - for (size_t i = 0; i < size; ++i) { - Span& span = spans[i]; - if (right < span.left) { - spans.insert(spans.begin() + i, {left, right}); - return; - } - if (left > span.right) { - continue; - } - size_t last_index = i; - while (last_index + 1 < size && right >= spans[last_index + 1].left) { - ++last_index; - } - span.left = std::min(span.left, left); - span.right = std::max(spans[last_index].right, right); - if (last_index > i) { - spans.erase(spans.begin() + i + 1, spans.begin() + last_index + 1); - } - return; - } - - spans.push_back({left, right}); +bool DlRegion::isComplex() const { + return lines_.size() > 1 || + (lines_.size() == 1 && + span_buffer_.getChunkSize(lines_.front().chunk_handle) > 1); } -bool DlRegion::SpanLine::spansEqual(const SpanLine& l2) const { - SpanVec& spans = *this->spans; - SpanVec& otherSpans = *l2.spans; - FML_DCHECK(this != &l2); - - if (spans.size() != otherSpans.size()) { +bool DlRegion::intersects(const SkIRect& rect) const { + if (isEmpty()) { return false; } - return memcmp(spans.data(), otherSpans.data(), spans.size() * sizeof(Span)) == - 0; -} -void DlRegion::insertLine(size_t position, SpanLine line) { - lines_.insert(lines_.begin() + position, line); -} + auto bounds_intersect = SkIRect::Intersects(bounds_, rect); -DlRegion::LineVec::iterator DlRegion::removeLine( - DlRegion::LineVec::iterator line) { - spanvec_pool_.push_back(line->spans); - return lines_.erase(line); -} + if (isSimple()) { + return bounds_intersect; + } -DlRegion::SpanLine DlRegion::makeLine(int32_t top, - int32_t bottom, - int32_t spanLeft, - int32_t spanRight) { - SpanVec* span_vec; - if (!spanvec_pool_.empty()) { - span_vec = spanvec_pool_.back(); - spanvec_pool_.pop_back(); - span_vec->clear(); + if (!bounds_intersect) { + return false; + } + + auto it = lines_.begin(); + auto end = lines_.end(); + if (lines_.size() > kBinarySearchThreshold && + it[kBinarySearchThreshold].bottom <= rect.fTop) { + it = std::lower_bound( + lines_.begin() + kBinarySearchThreshold + 1, lines_.end(), rect.fTop, + [](const SpanLine& line, int32_t top) { return line.bottom <= top; }); } else { - span_vec = new SpanVec(); + while (it != end && it->bottom <= rect.fTop) { + ++it; + continue; + } } - span_vec->push_back({spanLeft, spanRight}); - return {top, bottom, span_vec}; + while (it != end && it->top < rect.fBottom) { + FML_DCHECK(rect.fTop < it->bottom && it->top < rect.fBottom); + const Span *begin, *end; + span_buffer_.getSpans(it->chunk_handle, begin, end); + while (begin != end && begin->left < rect.fRight) { + if (begin->right > rect.fLeft) { + return true; + } + ++begin; + } + ++it; + } + + return false; } -DlRegion::SpanLine DlRegion::makeLine(int32_t top, - int32_t bottom, - const SpanVec& spans) { - SpanVec* span_vec; - if (!spanvec_pool_.empty()) { - span_vec = spanvec_pool_.back(); - spanvec_pool_.pop_back(); - } else { - span_vec = new SpanVec(); +bool DlRegion::spansIntersect(const Span* begin1, + const Span* end1, + const Span* begin2, + const Span* end2) { + while (begin1 != end1 && begin2 != end2) { + if (begin1->right <= begin2->left) { + ++begin1; + } else if (begin2->right <= begin1->left) { + ++begin2; + } else { + return true; + } } - *span_vec = spans; - return {top, bottom, span_vec}; + return false; } -void DlRegion::addRects(std::vector&& rects) { - std::sort(rects.begin(), rects.end(), [](const SkIRect& a, const SkIRect& b) { - // Sort the rectangles by Y axis. Because the rectangles have varying - // height, they are added to span lines in non-deterministic order and thus - // it makes no difference if they are also sorted by the X axis. - return a.top() < b.top(); - }); +void DlRegion::getIntersectionIterators( + const std::vector& a_lines, + const std::vector& b_lines, + std::vector::const_iterator& a_it, + std::vector::const_iterator& b_it) { + a_it = a_lines.begin(); + auto a_end = a_lines.end(); + b_it = b_lines.begin(); + auto b_end = b_lines.end(); - size_t start_index = 0; + FML_DCHECK(a_it != a_end && b_it != b_end); - size_t dirty_start = std::numeric_limits::max(); - size_t dirty_end = 0; + auto a_len = a_end - a_it; + auto b_len = b_end - b_it; - // Marks line as dirty. Dirty lines will be checked for equality - // later and merged as needed. - auto mark_dirty = [&](size_t line) { - dirty_start = std::min(dirty_start, line); - dirty_end = std::max(dirty_end, line); - }; + if (a_len > kBinarySearchThreshold && + a_it[kBinarySearchThreshold].bottom <= b_it->top) { + a_it = std::lower_bound( + a_lines.begin() + kBinarySearchThreshold + 1, a_lines.end(), b_it->top, + [](const SpanLine& line, int32_t top) { return line.bottom <= top; }); + } else if (b_len > kBinarySearchThreshold && + b_it[kBinarySearchThreshold].bottom <= a_it->top) { + b_it = std::lower_bound( + b_lines.begin() + kBinarySearchThreshold + 1, b_lines.end(), a_it->top, + [](const SpanLine& line, int32_t top) { return line.bottom <= top; }); + } +} - for (const SkIRect& rect : rects) { - if (rect.isEmpty()) { - continue; - } +bool DlRegion::intersects(const DlRegion& region) const { + if (isEmpty() || region.isEmpty()) { + return false; + } - int32_t y1 = rect.fTop; - int32_t y2 = rect.fBottom; + auto our_complex = isComplex(); + auto their_complex = region.isComplex(); + auto bounds_intersect = SkIRect::Intersects(bounds_, region.bounds_); - for (size_t i = start_index; i < lines_.size() && y1 < y2; ++i) { - SpanLine& line = lines_[i]; + if (!our_complex && !their_complex) { + return bounds_intersect; + } - if (rect.fTop >= line.bottom) { - start_index = i; - continue; - } + if (!bounds_intersect) { + return false; + } - if (y2 <= line.top) { - insertLine(i, makeLine(y1, y2, rect.fLeft, rect.fRight)); - mark_dirty(i); - y1 = y2; - break; - } - if (y1 < line.top) { - auto prevLineStart = line.top; - insertLine(i, makeLine(y1, prevLineStart, rect.fLeft, rect.fRight)); - mark_dirty(i); - y1 = prevLineStart; - continue; - } - if (y1 > line.top) { - // duplicate line - auto prevLineEnd = line.bottom; - line.bottom = y1; - mark_dirty(i); - insertLine(i + 1, makeLine(y1, prevLineEnd, *line.spans)); - continue; - } - FML_DCHECK(y1 == line.top); - if (y2 < line.bottom) { - // duplicate line - auto newLine = makeLine(y2, line.bottom, *line.spans); - line.bottom = y2; - line.insertSpan(rect.fLeft, rect.fRight); - insertLine(i + 1, newLine); - y1 = y2; - mark_dirty(i); - break; - } - FML_DCHECK(y2 >= line.bottom); - line.insertSpan(rect.fLeft, rect.fRight); - mark_dirty(i); - y1 = line.bottom; - } + if (!our_complex) { + return region.intersects(bounds_); + } - if (y1 < y2) { - lines_.push_back(makeLine(y1, y2, rect.fLeft, rect.fRight)); - mark_dirty(lines_.size() - 1); - } + if (!their_complex) { + return intersects(region.bounds_); + } - // Check for duplicate lines and merge them. - if (dirty_start <= dirty_end) { - // Expand the region by one if possible. - if (dirty_start > 0) { - --dirty_start; - } - if (dirty_end + 1 < lines_.size()) { - ++dirty_end; + std::vector::const_iterator ours, theirs; + getIntersectionIterators(lines_, region.lines_, ours, theirs); + auto ours_end = lines_.end(); + auto theirs_end = region.lines_.end(); + + while (ours != ours_end && theirs != theirs_end) { + if (ours->bottom <= theirs->top) { + ++ours; + } else if (theirs->bottom <= ours->top) { + ++theirs; + } else { + FML_DCHECK(ours->top < theirs->bottom && theirs->top < ours->bottom); + const Span *ours_begin, *ours_end; + span_buffer_.getSpans(ours->chunk_handle, ours_begin, ours_end); + const Span *theirs_begin, *theirs_end; + region.span_buffer_.getSpans(theirs->chunk_handle, theirs_begin, + theirs_end); + if (spansIntersect(ours_begin, ours_end, theirs_begin, theirs_end)) { + return true; } - for (auto i = lines_.begin() + dirty_start; - i < lines_.begin() + dirty_end;) { - auto& line = *i; - auto& next = *(i + 1); - if (line.bottom == next.top && line.spansEqual(next)) { - --dirty_end; - next.top = line.top; - i = removeLine(i); - } else { - ++i; - } + if (ours->bottom < theirs->bottom) { + ++ours; + } else { + ++theirs; } } - dirty_start = std::numeric_limits::max(); - dirty_end = 0; } + return false; } } // namespace flutter diff --git a/display_list/geometry/dl_region.h b/display_list/geometry/dl_region.h index 159b32c37d7dd..dd75b35129042 100644 --- a/display_list/geometry/dl_region.h +++ b/display_list/geometry/dl_region.h @@ -7,6 +7,7 @@ #include "third_party/skia/include/core/SkRect.h" +#include #include namespace flutter { @@ -16,48 +17,155 @@ namespace flutter { /// converting set of overlapping rectangles to non-overlapping rectangles. class DlRegion { public: - /// Creates region by bulk adding the rectangles./// Matches - /// SkRegion::op(rect, SkRegion::kUnion_Op) behavior. - explicit DlRegion(std::vector&& rects); - ~DlRegion(); + /// Creates an empty region. + DlRegion() = default; + + /// Creates region by bulk adding the rectangles. + /// Matches SkRegion::op(rect, SkRegion::kUnion_Op) behavior. + explicit DlRegion(const std::vector& rects); + + /// Creates region covering area of a rectangle. + explicit DlRegion(const SkIRect& rect); + + DlRegion(const DlRegion&) = default; + DlRegion(DlRegion&&) = default; + + DlRegion& operator=(const DlRegion&) = default; + DlRegion& operator=(DlRegion&&) = default; + + /// Creates union region of region a and b. + /// Matches SkRegion a; a.op(b, SkRegion::kUnion_Op) behavior. + static DlRegion MakeUnion(const DlRegion& a, const DlRegion& b); + + /// Creates intersection region of region a and b. + /// Matches SkRegion a; a.op(b, SkRegion::kIntersect_Op) behavior. + static DlRegion MakeIntersection(const DlRegion& a, const DlRegion& b); /// Returns list of non-overlapping rectangles that cover current region. /// If |deband| is false, each span line will result in separate rectangles, /// closely matching SkRegion::Iterator behavior. /// If |deband| is true, matching rectangles from adjacent span lines will be - /// merged into single rectange. + /// merged into single rectangle. std::vector getRects(bool deband = true) const; + /// Returns maximum and minimum axis values of rectangles in this region. + /// If region is empty returns SKIRect::MakeEmpty(). + const SkIRect& bounds() const { return bounds_; } + + /// Returns whether this region intersects with a rectangle. + bool intersects(const SkIRect& rect) const; + + /// Returns whether this region intersects with another region. + bool intersects(const DlRegion& region) const; + + /// Returns true if region is empty (contains no rectangles). + bool isEmpty() const { return lines_.empty(); } + + /// Returns true if region is not empty and contains more than one rectangle. + bool isComplex() const; + + /// Returns true if region can be represented by single rectangle or is + /// empty. + bool isSimple() const { return !isComplex(); } + private: - void addRects(std::vector&& rects); + typedef std::uint32_t SpanChunkHandle; struct Span { int32_t left; int32_t right; + + Span() = default; + Span(int32_t left, int32_t right) : left(left), right(right) {} }; - typedef std::vector SpanVec; + + /// Holds spans for the region. Having custom allocated memory that doesn't + /// do zero initialization every time the buffer gets resized improves + /// performance measurably. + class SpanBuffer { + public: + SpanBuffer() = default; + SpanBuffer(const SpanBuffer&); + SpanBuffer(SpanBuffer&& m); + SpanBuffer& operator=(const SpanBuffer&); + SpanBuffer& operator=(SpanBuffer&& m); + + void reserve(size_t capacity); + size_t capacity() const { return capacity_; } + + SpanChunkHandle storeChunk(const Span* begin, const Span* end); + size_t getChunkSize(SpanChunkHandle handle) const; + void getSpans(SpanChunkHandle handle, + const DlRegion::Span*& begin, + const DlRegion::Span*& end) const; + + ~SpanBuffer(); + + private: + void setChunkSize(SpanChunkHandle handle, size_t size); + + size_t capacity_ = 0; + size_t size_ = 0; + + // Spans for the region chunks. First span in each chunk contains the + // chunk size. + Span* spans_ = nullptr; + }; + struct SpanLine { int32_t top; int32_t bottom; - SpanVec* spans; - - void insertSpan(int32_t left, int32_t right); - bool spansEqual(const SpanLine& l2) const; + SpanChunkHandle chunk_handle; }; - typedef std::vector LineVec; + void setRects(const std::vector& rects); - std::vector lines_; - std::vector spanvec_pool_; - - void insertLine(size_t position, SpanLine line); - LineVec::iterator removeLine(LineVec::iterator position); + void appendLine(int32_t top, + int32_t bottom, + const Span* begin, + const Span* end); + void appendLine(int32_t top, + int32_t bottom, + const SpanBuffer& buffer, + SpanChunkHandle handle) { + const Span *begin, *end; + buffer.getSpans(handle, begin, end); + appendLine(top, bottom, begin, end); + } + typedef std::vector SpanVec; + SpanLine makeLine(int32_t top, int32_t bottom, const SpanVec&); SpanLine makeLine(int32_t top, int32_t bottom, - int32_t spanLeft, - int32_t spanRight); - SpanLine makeLine(int32_t top, int32_t bottom, const SpanVec& spans); + const Span* begin, + const Span* end); + static size_t unionLineSpans(std::vector& res, + const SpanBuffer& a_buffer, + SpanChunkHandle a_handle, + const SpanBuffer& b_buffer, + SpanChunkHandle b_handle); + static size_t intersectLineSpans(std::vector& res, + const SpanBuffer& a_buffer, + SpanChunkHandle a_handle, + const SpanBuffer& b_buffer, + SpanChunkHandle b_handle); + + bool spansEqual(SpanLine& line, const Span* begin, const Span* end) const; + + static bool spansIntersect(const Span* begin1, + const Span* end1, + const Span* begin2, + const Span* end2); + + static void getIntersectionIterators( + const std::vector& a_lines, + const std::vector& b_lines, + std::vector::const_iterator& a_it, + std::vector::const_iterator& b_it); + + std::vector lines_; + SkIRect bounds_ = SkIRect::MakeEmpty(); + SpanBuffer span_buffer_; }; } // namespace flutter diff --git a/display_list/geometry/dl_region_unittests.cc b/display_list/geometry/dl_region_unittests.cc index f9610ff3c5561..1f03f89fc6d5a 100644 --- a/display_list/geometry/dl_region_unittests.cc +++ b/display_list/geometry/dl_region_unittests.cc @@ -13,7 +13,8 @@ namespace flutter { namespace testing { TEST(DisplayListRegion, EmptyRegion) { - DlRegion region({}); + DlRegion region; + EXPECT_TRUE(region.isEmpty()); EXPECT_TRUE(region.getRects().empty()); } @@ -30,7 +31,7 @@ TEST(DisplayListRegion, NonOverlappingRectangles1) { SkIRect rect = SkIRect::MakeXYWH(50 * i, 50 * i, 50, 50); rects_in.push_back(rect); } - DlRegion region(std::move(rects_in)); + DlRegion region(rects_in); auto rects = region.getRects(); std::vector expected{ {0, 0, 50, 50}, {50, 50, 100, 100}, {100, 100, 150, 150}, @@ -111,7 +112,7 @@ TEST(DisplayListRegion, OverlappingRectangles) { SkIRect rect = SkIRect::MakeXYWH(10 * i, 10 * i, 50, 50); rects_in.push_back(rect); } - DlRegion region(std::move(rects_in)); + DlRegion region(rects_in); auto rects = region.getRects(); std::vector expected{ {0, 0, 50, 10}, {0, 10, 60, 20}, {0, 20, 70, 30}, @@ -150,56 +151,281 @@ TEST(DisplayListRegion, Deband) { EXPECT_EQ(rects_without_deband, expected_without_deband); } +TEST(DisplayListRegion, Intersects1) { + DlRegion region1({ + SkIRect::MakeXYWH(0, 0, 20, 20), + SkIRect::MakeXYWH(20, 20, 20, 20), + }); + DlRegion region2({ + SkIRect::MakeXYWH(20, 0, 20, 20), + SkIRect::MakeXYWH(0, 20, 20, 20), + }); + EXPECT_FALSE(region1.intersects(region2)); + EXPECT_FALSE(region2.intersects(region1)); + + EXPECT_TRUE(region1.intersects(region2.bounds())); + EXPECT_TRUE(region2.intersects(region1.bounds())); + + EXPECT_TRUE(region1.intersects(SkIRect::MakeXYWH(0, 0, 20, 20))); + EXPECT_FALSE(region1.intersects(SkIRect::MakeXYWH(20, 0, 20, 20))); + + EXPECT_TRUE(region1.intersects( + DlRegion(std::vector{SkIRect::MakeXYWH(0, 0, 20, 20)}))); + EXPECT_FALSE(region1.intersects( + DlRegion(std::vector{SkIRect::MakeXYWH(20, 0, 20, 20)}))); + + EXPECT_FALSE(region1.intersects(SkIRect::MakeXYWH(-1, -1, 1, 1))); + EXPECT_TRUE(region1.intersects(SkIRect::MakeXYWH(0, 0, 1, 1))); + + EXPECT_FALSE(region1.intersects(SkIRect::MakeXYWH(40, 40, 1, 1))); + EXPECT_TRUE(region1.intersects(SkIRect::MakeXYWH(39, 39, 1, 1))); +} + +TEST(DisplayListRegion, Intersects2) { + DlRegion region1({ + SkIRect::MakeXYWH(-10, -10, 20, 20), + SkIRect::MakeXYWH(-30, -30, 20, 20), + }); + DlRegion region2({ + SkIRect::MakeXYWH(20, 20, 5, 5), + SkIRect::MakeXYWH(0, 0, 20, 20), + }); + EXPECT_TRUE(region1.intersects(region2)); + EXPECT_TRUE(region2.intersects(region1)); +} + +TEST(DisplayListRegion, Intersection1) { + DlRegion region1({ + SkIRect::MakeXYWH(0, 0, 20, 20), + SkIRect::MakeXYWH(20, 20, 20, 20), + }); + DlRegion region2({ + SkIRect::MakeXYWH(20, 0, 20, 20), + SkIRect::MakeXYWH(0, 20, 20, 20), + }); + DlRegion i = DlRegion::MakeIntersection(region1, region2); + EXPECT_EQ(i.bounds(), SkIRect::MakeEmpty()); + EXPECT_TRUE(i.isEmpty()); + auto rects = i.getRects(); + EXPECT_TRUE(rects.empty()); +} + +TEST(DisplayListRegion, Intersection2) { + DlRegion region1({ + SkIRect::MakeXYWH(0, 0, 20, 20), + SkIRect::MakeXYWH(20, 20, 20, 20), + }); + DlRegion region2({ + SkIRect::MakeXYWH(0, 0, 20, 20), + SkIRect::MakeXYWH(20, 20, 20, 20), + }); + DlRegion i = DlRegion::MakeIntersection(region1, region2); + EXPECT_EQ(i.bounds(), SkIRect::MakeXYWH(0, 0, 40, 40)); + auto rects = i.getRects(); + std::vector expected{ + SkIRect::MakeXYWH(0, 0, 20, 20), + SkIRect::MakeXYWH(20, 20, 20, 20), + }; + EXPECT_EQ(rects, expected); +} + +TEST(DisplayListRegion, Intersection3) { + DlRegion region1({ + SkIRect::MakeXYWH(0, 0, 20, 20), + }); + DlRegion region2({ + SkIRect::MakeXYWH(-10, -10, 20, 20), + SkIRect::MakeXYWH(10, 10, 20, 20), + }); + DlRegion i = DlRegion::MakeIntersection(region1, region2); + EXPECT_EQ(i.bounds(), SkIRect::MakeXYWH(0, 0, 20, 20)); + auto rects = i.getRects(); + std::vector expected{ + SkIRect::MakeXYWH(0, 0, 10, 10), + SkIRect::MakeXYWH(10, 10, 10, 10), + }; + EXPECT_EQ(rects, expected); +} + +TEST(DisplayListRegion, Union1) { + DlRegion region1({ + SkIRect::MakeXYWH(0, 0, 20, 20), + SkIRect::MakeXYWH(20, 20, 20, 20), + }); + DlRegion region2({ + SkIRect::MakeXYWH(20, 0, 20, 20), + SkIRect::MakeXYWH(0, 20, 20, 20), + }); + DlRegion u = DlRegion::MakeUnion(region1, region2); + EXPECT_EQ(u.bounds(), SkIRect::MakeXYWH(0, 0, 40, 40)); + EXPECT_TRUE(u.isSimple()); + auto rects = u.getRects(); + std::vector expected{ + SkIRect::MakeXYWH(0, 0, 40, 40), // + }; + EXPECT_EQ(rects, expected); +} + +TEST(DisplayListRegion, Union2) { + DlRegion region1({ + SkIRect::MakeXYWH(0, 0, 20, 20), + SkIRect::MakeXYWH(21, 21, 20, 20), + }); + DlRegion region2({ + SkIRect::MakeXYWH(21, 0, 20, 20), + SkIRect::MakeXYWH(0, 21, 20, 20), + }); + DlRegion u = DlRegion::MakeUnion(region1, region2); + EXPECT_EQ(u.bounds(), SkIRect::MakeXYWH(0, 0, 41, 41)); + auto rects = u.getRects(); + std::vector expected{ + SkIRect::MakeXYWH(0, 0, 20, 20), + SkIRect::MakeXYWH(21, 0, 20, 20), + SkIRect::MakeXYWH(0, 21, 20, 20), + SkIRect::MakeXYWH(21, 21, 20, 20), + }; + EXPECT_EQ(rects, expected); +} + +TEST(DisplayListRegion, Union3) { + DlRegion region1({ + SkIRect::MakeXYWH(-10, -10, 20, 20), + }); + DlRegion region2({ + SkIRect::MakeXYWH(0, 0, 20, 20), + }); + DlRegion u = DlRegion::MakeUnion(region1, region2); + EXPECT_EQ(u.bounds(), SkIRect::MakeXYWH(-10, -10, 30, 30)); + auto rects = u.getRects(); + std::vector expected{ + SkIRect::MakeXYWH(-10, -10, 20, 10), + SkIRect::MakeXYWH(-10, 0, 30, 10), + SkIRect::MakeXYWH(0, 10, 20, 10), + }; + EXPECT_EQ(rects, expected); +} + +TEST(DisplayListRegion, UnionEmpty) { + { + DlRegion region1(std::vector{}); + DlRegion region2(std::vector{}); + DlRegion u = DlRegion::MakeUnion(region1, region2); + EXPECT_EQ(u.bounds(), SkIRect::MakeEmpty()); + EXPECT_TRUE(u.isEmpty()); + auto rects = u.getRects(); + EXPECT_TRUE(rects.empty()); + } + { + DlRegion region1(std::vector{}); + DlRegion region2({ + SkIRect::MakeXYWH(0, 0, 20, 20), + }); + DlRegion u = DlRegion::MakeUnion(region1, region2); + EXPECT_EQ(u.bounds(), SkIRect::MakeXYWH(0, 0, 20, 20)); + auto rects = u.getRects(); + std::vector expected{ + SkIRect::MakeXYWH(0, 0, 20, 20), + }; + } + { + DlRegion region1({ + SkIRect::MakeXYWH(0, 0, 20, 20), + }); + DlRegion region2(std::vector{}); + DlRegion u = DlRegion::MakeUnion(region1, region2); + EXPECT_EQ(u.bounds(), SkIRect::MakeXYWH(0, 0, 20, 20)); + auto rects = u.getRects(); + std::vector expected{ + SkIRect::MakeXYWH(0, 0, 20, 20), + }; + } +} + +void CheckEquality(const DlRegion& dl_region, const SkRegion& sk_region) { + EXPECT_EQ(dl_region.bounds(), sk_region.getBounds()); + + // Do not deband the rectangles - identical to SkRegion::Iterator + auto rects = dl_region.getRects(false); + + std::vector skia_rects; + + auto iterator = SkRegion::Iterator(sk_region); + while (!iterator.done()) { + skia_rects.push_back(iterator.rect()); + iterator.next(); + } + + EXPECT_EQ(rects, skia_rects); +} + TEST(DisplayListRegion, TestAgainstSkRegion) { struct Settings { int max_size; - size_t iteration_count; - }; - std::vector all_settings{ - {100, 10}, // - {100, 100}, // - {100, 1000}, // - {400, 10}, // - {400, 100}, // - {400, 1000}, // - {800, 10}, // - {800, 100}, // - {800, 1000}, }; + std::vector all_settings{{100}, {400}, {800}}; + + std::vector iterations{1, 10, 100, 1000}; for (const auto& settings : all_settings) { - std::random_device d; - std::seed_seq seed{::testing::UnitTest::GetInstance()->random_seed()}; - std::mt19937 rng(seed); + for (const auto iterations_1 : iterations) { + for (const auto iterations_2 : iterations) { + std::random_device d; + std::seed_seq seed{::testing::UnitTest::GetInstance()->random_seed()}; + std::mt19937 rng(seed); - SkRegion sk_region; + SkRegion sk_region1; + SkRegion sk_region2; - std::uniform_int_distribution pos(0, 4000); - std::uniform_int_distribution size(1, settings.max_size); + std::uniform_int_distribution pos(0, 4000); + std::uniform_int_distribution size(1, settings.max_size); - std::vector rects_in; + std::vector rects_in1; + std::vector rects_in2; - for (size_t i = 0; i < settings.iteration_count; ++i) { - SkIRect rect = - SkIRect::MakeXYWH(pos(rng), pos(rng), size(rng), size(rng)); - rects_in.push_back(rect); - sk_region.op(rect, SkRegion::kUnion_Op); - } + for (size_t i = 0; i < iterations_1; ++i) { + SkIRect rect = + SkIRect::MakeXYWH(pos(rng), pos(rng), size(rng), size(rng)); + rects_in1.push_back(rect); + } - DlRegion region(std::move(rects_in)); + for (size_t i = 0; i < iterations_2; ++i) { + SkIRect rect = + SkIRect::MakeXYWH(pos(rng), pos(rng), size(rng), size(rng)); + rects_in2.push_back(rect); + } - // Do not deband the rectangles - identical to SkRegion::Iterator - auto rects = region.getRects(false); + DlRegion region1(rects_in1); + sk_region1.setRects(rects_in1.data(), rects_in1.size()); + CheckEquality(region1, sk_region1); - std::vector skia_rects; + DlRegion region2(rects_in2); + sk_region2.setRects(rects_in2.data(), rects_in2.size()); + CheckEquality(region2, sk_region2); - auto iterator = SkRegion::Iterator(sk_region); - while (!iterator.done()) { - skia_rects.push_back(iterator.rect()); - iterator.next(); - } + auto intersects_1 = region1.intersects(region2); + auto intersects_2 = region2.intersects(region1); + auto sk_intesects = sk_region1.intersects(sk_region2); + EXPECT_EQ(intersects_1, intersects_2); + EXPECT_EQ(intersects_1, sk_intesects); + + { + auto rects = region2.getRects(true); + for (const auto& r : rects) { + EXPECT_EQ(region1.intersects(r), sk_region1.intersects(r)); + } + } - EXPECT_EQ(rects, skia_rects); + DlRegion dl_union = DlRegion::MakeUnion(region1, region2); + SkRegion sk_union(sk_region1); + sk_union.op(sk_region2, SkRegion::kUnion_Op); + CheckEquality(dl_union, sk_union); + + DlRegion dl_intersection = DlRegion::MakeIntersection(region1, region2); + SkRegion sk_intersection(sk_region1); + sk_intersection.op(sk_region2, SkRegion::kIntersect_Op); + CheckEquality(dl_intersection, sk_intersection); + } + } } } diff --git a/display_list/geometry/dl_rtree.cc b/display_list/geometry/dl_rtree.cc index d5fb8ad64d1ff..a4a805957bcc3 100644 --- a/display_list/geometry/dl_rtree.cc +++ b/display_list/geometry/dl_rtree.cc @@ -173,7 +173,7 @@ std::list DlRTree::searchAndConsolidateRects(const SkRect& query, bounds(index).roundOut(¤t_record_rect); rects.push_back(current_record_rect); } - DlRegion region(std::move(rects)); + DlRegion region(rects); auto non_overlapping_rects = region.getRects(deband); std::list final_results; @@ -201,6 +201,18 @@ void DlRTree::search(const Node& parent, } } +const DlRegion& DlRTree::region() const { + if (!region_) { + std::vector rects; + rects.resize(leaf_count_); + for (int i = 0; i < leaf_count_; i++) { + nodes_[i].bounds.roundOut(&rects[i]); + } + region_.emplace(rects); + } + return *region_; +} + const SkRect& DlRTree::bounds() const { if (!nodes_.empty()) { return nodes_.back().bounds; diff --git a/display_list/geometry/dl_rtree.h b/display_list/geometry/dl_rtree.h index 0de686abc52d5..f12486edcce76 100644 --- a/display_list/geometry/dl_rtree.h +++ b/display_list/geometry/dl_rtree.h @@ -6,8 +6,10 @@ #define FLUTTER_DISPLAY_LIST_GEOMETRY_DL_RTREE_H_ #include +#include #include +#include "flutter/display_list/geometry/dl_region.h" #include "flutter/fml/logging.h" #include "third_party/skia/include/core/SkRect.h" #include "third_party/skia/include/core/SkRefCnt.h" @@ -50,7 +52,7 @@ class DlRTree : public SkRefCnt { /// If an array of IDs is not specified then all leaf nodes will be /// represented by the |invalid_id| (which defaults to -1). /// - /// Invallid rects that are empty or contain a NaN value will not be + /// Invalid rects that are empty or contain a NaN value will not be /// stored in the R-Tree. And, if a |predicate| function is provided, /// that function will be called to query if the rectangle associated /// with the ID should be included. @@ -73,8 +75,8 @@ class DlRTree : public SkRefCnt { /// The returned indices may not themselves be in numerical order, /// but they will represent the rectangles and IDs in the order in /// which they were passed into the constructor. The actual rectangle - /// and ID associated with each index can be retreived using the - /// |DlRTree::id| and |DlRTree::bouds| methods. + /// and ID associated with each index can be retrieved using the + /// |DlRTree::id| and |DlRTree::bounds| methods. void search(const SkRect& query, std::vector* results) const; /// Return the ID for the indicated result of a query or @@ -123,6 +125,16 @@ class DlRTree : public SkRefCnt { std::list searchAndConsolidateRects(const SkRect& query, bool deband = true) const; + /// Returns DlRegion that represents the union of all rectangles in the + /// R-Tree. + const DlRegion& region() const; + + /// Returns DlRegion that represents the union of all rectangles in the + /// R-Tree intersected with the query rect. + DlRegion region(const SkRect& query) const { + return DlRegion::MakeIntersection(region(), DlRegion(query.roundOut())); + } + private: static constexpr SkRect empty_ = SkRect::MakeEmpty(); @@ -133,6 +145,7 @@ class DlRTree : public SkRefCnt { std::vector nodes_; int leaf_count_; int invalid_id_; + mutable std::optional region_; }; } // namespace flutter diff --git a/display_list/geometry/dl_rtree_unittests.cc b/display_list/geometry/dl_rtree_unittests.cc index 80677931f8c31..ed16a1429bf27 100644 --- a/display_list/geometry/dl_rtree_unittests.cc +++ b/display_list/geometry/dl_rtree_unittests.cc @@ -301,5 +301,23 @@ TEST(DisplayListRTree, OverlappingRects) { EXPECT_EQ(list.front(), SkRect::MakeLTRB(0, 0, 70, 70)); } +TEST(DisplayListRTree, Region) { + SkRect rect[9]; + for (int i = 0; i < 9; i++) { + rect[i] = SkRect::MakeXYWH(i * 10, i * 10, 20, 20); + } + DlRTree rtree(rect, 9); + auto region = rtree.region(); + auto rects = region.getRects(true); + std::vector expected_rects{ + SkIRect::MakeLTRB(0, 0, 20, 10), SkIRect::MakeLTRB(0, 10, 30, 20), + SkIRect::MakeLTRB(10, 20, 40, 30), SkIRect::MakeLTRB(20, 30, 50, 40), + SkIRect::MakeLTRB(30, 40, 60, 50), SkIRect::MakeLTRB(40, 50, 70, 60), + SkIRect::MakeLTRB(50, 60, 80, 70), SkIRect::MakeLTRB(60, 70, 90, 80), + SkIRect::MakeLTRB(70, 80, 100, 90), SkIRect::MakeLTRB(80, 90, 100, 100), + }; + EXPECT_EQ(rects.size(), expected_rects.size()); +} + } // namespace testing } // namespace flutter diff --git a/flow/raster_cache.cc b/flow/raster_cache.cc index 90f49d0371c14..ab1f185fcd264 100644 --- a/flow/raster_cache.cc +++ b/flow/raster_cache.cc @@ -53,16 +53,17 @@ void RasterCacheResult::draw(DlCanvas& canvas, // On some platforms RTree from overlay layers is used for unobstructed // platform views and hit testing. To preserve the RTree raster cache must // paint individual rects instead of the whole image. - auto rects = rtree_->searchAndConsolidateRects(kGiantRect); + auto rects = rtree_->region().getRects(true); canvas.Translate(bounds.fLeft, bounds.fTop); SkRect rtree_bounds = RasterCacheUtil::GetRoundedOutDeviceBounds(rtree_->bounds(), matrix); for (auto rect : rects) { - rect = RasterCacheUtil::GetRoundedOutDeviceBounds(rect, matrix); - rect.offset(-rtree_bounds.fLeft, -rtree_bounds.fTop); - canvas.DrawImageRect(image_, rect, rect, + SkRect device_rect = RasterCacheUtil::GetRoundedOutDeviceBounds( + SkRect::Make(rect), matrix); + device_rect.offset(-rtree_bounds.fLeft, -rtree_bounds.fTop); + canvas.DrawImageRect(image_, device_rect, device_rect, DlImageSampling::kNearestNeighbor, paint); } } diff --git a/flow/rtree.cc b/flow/rtree.cc index 5e2630f867960..ff66d52c1762c 100644 --- a/flow/rtree.cc +++ b/flow/rtree.cc @@ -54,7 +54,7 @@ std::list RTree::searchNonOverlappingDrawnRects( rects.push_back(current_record_rect); } - DlRegion region(std::move(rects)); + DlRegion region(rects); auto non_overlapping_rects = region.getRects(true); std::list final_results; for (const auto& rect : non_overlapping_rects) {