diff --git a/BUILD.gn b/BUILD.gn index 33053946e3f53..46a1e76c8fcea 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -116,6 +116,7 @@ group("flutter") { public_deps += [ "//flutter/display_list:display_list_benchmarks", "//flutter/display_list:display_list_builder_benchmarks", + "//flutter/display_list:display_list_region_benchmarks", "//flutter/fml:fml_benchmarks", "//flutter/impeller/geometry:geometry_benchmarks", "//flutter/lib/ui:ui_benchmarks", diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index 5d8ae172abdeb..78947e7716127 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -39,6 +39,7 @@ ../../../flutter/display_list/effects/dl_image_filter_unittests.cc ../../../flutter/display_list/effects/dl_mask_filter_unittests.cc ../../../flutter/display_list/effects/dl_path_effect_unittests.cc +../../../flutter/display_list/geometry/dl_region_unittests.cc ../../../flutter/display_list/geometry/dl_rtree_unittests.cc ../../../flutter/display_list/skia/dl_sk_conversions_unittests.cc ../../../flutter/display_list/skia/dl_sk_paint_dispatcher_unittests.cc diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 69d87bce22845..a98cab24c4da3 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -714,6 +714,7 @@ ORIGIN: ../../../flutter/display_list/benchmarking/dl_complexity_gl.h + ../../.. ORIGIN: ../../../flutter/display_list/benchmarking/dl_complexity_helper.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/benchmarking/dl_complexity_metal.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/benchmarking/dl_complexity_metal.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/benchmarking/dl_region_benchmarks.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/display_list.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/display_list.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/dl_attributes.h + ../../../flutter/LICENSE @@ -748,6 +749,8 @@ ORIGIN: ../../../flutter/display_list/effects/dl_path_effect.cc + ../../../flutt ORIGIN: ../../../flutter/display_list/effects/dl_path_effect.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/effects/dl_runtime_effect.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/effects/dl_runtime_effect.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/geometry/dl_region.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/display_list/geometry/dl_region.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/geometry/dl_rtree.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/geometry/dl_rtree.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/display_list/image/dl_image.cc + ../../../flutter/LICENSE @@ -3380,6 +3383,7 @@ FILE: ../../../flutter/display_list/benchmarking/dl_complexity_gl.h FILE: ../../../flutter/display_list/benchmarking/dl_complexity_helper.h FILE: ../../../flutter/display_list/benchmarking/dl_complexity_metal.cc FILE: ../../../flutter/display_list/benchmarking/dl_complexity_metal.h +FILE: ../../../flutter/display_list/benchmarking/dl_region_benchmarks.cc FILE: ../../../flutter/display_list/display_list.cc FILE: ../../../flutter/display_list/display_list.h FILE: ../../../flutter/display_list/dl_attributes.h @@ -3414,6 +3418,8 @@ FILE: ../../../flutter/display_list/effects/dl_path_effect.cc FILE: ../../../flutter/display_list/effects/dl_path_effect.h FILE: ../../../flutter/display_list/effects/dl_runtime_effect.cc FILE: ../../../flutter/display_list/effects/dl_runtime_effect.h +FILE: ../../../flutter/display_list/geometry/dl_region.cc +FILE: ../../../flutter/display_list/geometry/dl_region.h FILE: ../../../flutter/display_list/geometry/dl_rtree.cc FILE: ../../../flutter/display_list/geometry/dl_rtree.h FILE: ../../../flutter/display_list/image/dl_image.cc diff --git a/display_list/BUILD.gn b/display_list/BUILD.gn index 0830d3c5864fa..97c9683511b61 100644 --- a/display_list/BUILD.gn +++ b/display_list/BUILD.gn @@ -57,6 +57,8 @@ source_set("display_list") { "effects/dl_path_effect.h", "effects/dl_runtime_effect.cc", "effects/dl_runtime_effect.h", + "geometry/dl_region.cc", + "geometry/dl_region.h", "geometry/dl_rtree.cc", "geometry/dl_rtree.h", "image/dl_image.cc", @@ -112,6 +114,7 @@ if (enable_unittests) { "effects/dl_image_filter_unittests.cc", "effects/dl_mask_filter_unittests.cc", "effects/dl_path_effect_unittests.cc", + "geometry/dl_region_unittests.cc", "geometry/dl_rtree_unittests.cc", "skia/dl_sk_conversions_unittests.cc", "skia/dl_sk_paint_dispatcher_unittests.cc", @@ -182,6 +185,18 @@ if (enable_unittests) { "//flutter/testing:testing_lib", ] } + + executable("display_list_region_benchmarks") { + testonly = true + + sources = [ "benchmarking/dl_region_benchmarks.cc" ] + + deps = [ + ":display_list_fixtures", + "//flutter/benchmarking", + "//flutter/testing:testing_lib", + ] + } } fixtures_location("display_list_benchmarks_fixtures") { diff --git a/display_list/benchmarking/dl_region_benchmarks.cc b/display_list/benchmarking/dl_region_benchmarks.cc new file mode 100644 index 0000000000000..a07faeba88e8e --- /dev/null +++ b/display_list/benchmarking/dl_region_benchmarks.cc @@ -0,0 +1,92 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/benchmarking/benchmarking.h" + +#include "flutter/display_list/geometry/dl_region.h" +#include "third_party/skia/include/core/SkRegion.h" + +#include + +class SkRegionAdapter { + public: + void addRect(const SkIRect& rect) { region_.op(rect, SkRegion::kUnion_Op); } + + std::vector getRects() { + std::vector rects; + SkRegion::Iterator it(region_); + while (!it.done()) { + rects.push_back(it.rect()); + it.next(); + } + return rects; + } + + private: + SkRegion region_; +}; + +class DlRegionAdapter { + public: + void addRect(const SkIRect& rect) { rects_.push_back(rect); } + + std::vector getRects() { + flutter::DlRegion region(std::move(rects_)); + return region.getRects(false); + } + + private: + std::vector rects_; +}; + +template +void RunRegionBenchmark(benchmark::State& state, int maxSize) { + while (state.KeepRunning()) { + 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); + + Region region; + + for (int i = 0; i < 2000; ++i) { + SkIRect rect = + SkIRect::MakeXYWH(pos(rng), pos(rng), size(rng), size(rng)); + region.addRect(rect); + } + + auto vec2 = region.getRects(); + } +} + +namespace flutter { + +static void BM_RegionBenchmarkSkRegion(benchmark::State& state, int maxSize) { + RunRegionBenchmark(state, maxSize); +} + +static void BM_RegionBenchmarkDlRegion(benchmark::State& state, int maxSize) { + RunRegionBenchmark(state, maxSize); +} + +BENCHMARK_CAPTURE(BM_RegionBenchmarkDlRegion, Tiny, 30) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_RegionBenchmarkSkRegion, Tiny, 30) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_RegionBenchmarkDlRegion, Small, 100) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_RegionBenchmarkSkRegion, Small, 100) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_RegionBenchmarkDlRegion, Medium, 400) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_RegionBenchmarkSkRegion, Medium, 400) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_RegionBenchmarkDlRegion, Large, 1500) + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_RegionBenchmarkSkRegion, Large, 1500) + ->Unit(benchmark::kMicrosecond); + +} // namespace flutter \ No newline at end of file diff --git a/display_list/dl_builder.cc b/display_list/dl_builder.cc index 6aa6c94ab868c..f224c444c42bf 100644 --- a/display_list/dl_builder.cc +++ b/display_list/dl_builder.cc @@ -1106,7 +1106,8 @@ void DisplayListBuilder::DrawDisplayList(const sk_sp display_list, case BoundsAccumulatorType::kRTree: auto rtree = display_list->rtree(); if (rtree) { - std::list rects = rtree->searchAndConsolidateRects(bounds); + std::list rects = + rtree->searchAndConsolidateRects(bounds, false); for (const SkRect& rect : rects) { // TODO (https://github.com/flutter/flutter/issues/114919): Attributes // are not necessarily `kDrawDisplayListFlags`. diff --git a/display_list/geometry/dl_region.cc b/display_list/geometry/dl_region.cc new file mode 100644 index 0000000000000..281fd142b393f --- /dev/null +++ b/display_list/geometry/dl_region.cc @@ -0,0 +1,248 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/display_list/geometry/dl_region.h" + +#include "flutter/fml/logging.h" + +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)); + + for (auto& spanvec : spanvec_pool_) { + delete spanvec; + } + spanvec_pool_.clear(); +} + +DlRegion::~DlRegion() { + for (auto& line : lines_) { + delete line.spans; + } +} + +std::vector DlRegion::getRects(bool deband) const { + std::vector rects; + 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}; + if (deband) { + auto iter = rects.begin() + previous_span_end; + // If there is rectangle previously in rects on which this one is a + // vertical continuation, remove the previous rectangle and expand + // this one vertically to cover the area. + while (iter != rects.begin()) { + --iter; + if (iter->bottom() < rect.top()) { + // Went all the way to previous span line. + break; + } else if (iter->left() == rect.left() && + iter->right() == rect.right()) { + FML_DCHECK(iter->bottom() == rect.top()); + rect.fTop = iter->fTop; + rects.erase(iter); + --previous_span_end; + break; + } + } + } + rects.push_back(rect); + } + previous_span_end = rects.size(); + } + 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::SpanLine::spansEqual(const SpanLine& l2) const { + SpanVec& spans = *this->spans; + SpanVec& otherSpans = *l2.spans; + FML_DCHECK(this != &l2); + + if (spans.size() != otherSpans.size()) { + 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); +} + +DlRegion::LineVec::iterator DlRegion::removeLine( + DlRegion::LineVec::iterator line) { + spanvec_pool_.push_back(line->spans); + return lines_.erase(line); +} + +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(); + } else { + span_vec = new SpanVec(); + } + span_vec->push_back({spanLeft, spanRight}); + return {top, bottom, span_vec}; +} + +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(); + } + *span_vec = spans; + return {top, bottom, span_vec}; +} + +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(); + }); + + size_t start_index = 0; + + size_t dirty_start = std::numeric_limits::max(); + size_t dirty_end = 0; + + // 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); + }; + + for (const SkIRect& rect : rects) { + if (rect.isEmpty()) { + continue; + } + + int32_t y1 = rect.fTop; + int32_t y2 = rect.fBottom; + + for (size_t i = start_index; i < lines_.size() && y1 < y2; ++i) { + SpanLine& line = lines_[i]; + + if (rect.fTop >= line.bottom) { + start_index = i; + continue; + } + + 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 (y1 < y2) { + lines_.push_back(makeLine(y1, y2, rect.fLeft, rect.fRight)); + mark_dirty(lines_.size() - 1); + } + + // 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; + } + 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; + } + } + } + dirty_start = std::numeric_limits::max(); + dirty_end = 0; + } +} + +} // namespace flutter diff --git a/display_list/geometry/dl_region.h b/display_list/geometry/dl_region.h new file mode 100644 index 0000000000000..159b32c37d7dd --- /dev/null +++ b/display_list/geometry/dl_region.h @@ -0,0 +1,65 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_DISPLAY_LIST_GEOMETRY_REGION_H_ +#define FLUTTER_DISPLAY_LIST_GEOMETRY_REGION_H_ + +#include "third_party/skia/include/core/SkRect.h" + +#include + +namespace flutter { + +/// Represents a region as a collection of non-overlapping rectangles. +/// Implements a subset of SkRegion functionality optimized for quickly +/// 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(); + + /// 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. + std::vector getRects(bool deband = true) const; + + private: + void addRects(std::vector&& rects); + + struct Span { + int32_t left; + int32_t right; + }; + typedef std::vector SpanVec; + struct SpanLine { + int32_t top; + int32_t bottom; + SpanVec* spans; + + void insertSpan(int32_t left, int32_t right); + bool spansEqual(const SpanLine& l2) const; + }; + + typedef std::vector LineVec; + + std::vector lines_; + std::vector spanvec_pool_; + + void insertLine(size_t position, SpanLine line); + LineVec::iterator removeLine(LineVec::iterator position); + + 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); +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_GEOMETRY_REGION_H_ diff --git a/display_list/geometry/dl_region_unittests.cc b/display_list/geometry/dl_region_unittests.cc new file mode 100644 index 0000000000000..f9610ff3c5561 --- /dev/null +++ b/display_list/geometry/dl_region_unittests.cc @@ -0,0 +1,207 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/display_list/geometry/dl_region.h" +#include "gtest/gtest.h" + +#include "third_party/skia/include/core/SkRegion.h" + +#include + +namespace flutter { +namespace testing { + +TEST(DisplayListRegion, EmptyRegion) { + DlRegion region({}); + EXPECT_TRUE(region.getRects().empty()); +} + +TEST(DisplayListRegion, SingleRectangle) { + DlRegion region({SkIRect::MakeLTRB(10, 10, 50, 50)}); + auto rects = region.getRects(); + ASSERT_EQ(rects.size(), 1u); + EXPECT_EQ(rects.front(), SkIRect::MakeLTRB(10, 10, 50, 50)); +} + +TEST(DisplayListRegion, NonOverlappingRectangles1) { + std::vector rects_in; + for (int i = 0; i < 10; ++i) { + SkIRect rect = SkIRect::MakeXYWH(50 * i, 50 * i, 50, 50); + rects_in.push_back(rect); + } + DlRegion region(std::move(rects_in)); + auto rects = region.getRects(); + std::vector expected{ + {0, 0, 50, 50}, {50, 50, 100, 100}, {100, 100, 150, 150}, + {150, 150, 200, 200}, {200, 200, 250, 250}, {250, 250, 300, 300}, + {300, 300, 350, 350}, {350, 350, 400, 400}, {400, 400, 450, 450}, + {450, 450, 500, 500}, + }; + EXPECT_EQ(rects, expected); +} + +TEST(DisplayListRegion, NonOverlappingRectangles2) { + DlRegion region({ + SkIRect::MakeXYWH(5, 5, 10, 10), + SkIRect::MakeXYWH(25, 5, 10, 10), + SkIRect::MakeXYWH(5, 25, 10, 10), + SkIRect::MakeXYWH(25, 25, 10, 10), + }); + auto rects = region.getRects(); + std::vector expected{ + SkIRect::MakeXYWH(5, 5, 10, 10), + SkIRect::MakeXYWH(25, 5, 10, 10), + SkIRect::MakeXYWH(5, 25, 10, 10), + SkIRect::MakeXYWH(25, 25, 10, 10), + }; + EXPECT_EQ(rects, expected); +} + +TEST(DisplayListRegion, NonOverlappingRectangles3) { + DlRegion region({ + SkIRect::MakeXYWH(0, 0, 10, 10), + SkIRect::MakeXYWH(-11, -11, 10, 10), + SkIRect::MakeXYWH(11, 11, 10, 10), + SkIRect::MakeXYWH(-11, 0, 10, 10), + SkIRect::MakeXYWH(0, 11, 10, 10), + SkIRect::MakeXYWH(0, -11, 10, 10), + SkIRect::MakeXYWH(11, 0, 10, 10), + SkIRect::MakeXYWH(11, -11, 10, 10), + SkIRect::MakeXYWH(-11, 11, 10, 10), + }); + auto rects = region.getRects(); + std::vector expected{ + SkIRect::MakeXYWH(-11, -11, 10, 10), // + SkIRect::MakeXYWH(0, -11, 10, 10), // + SkIRect::MakeXYWH(11, -11, 10, 10), // + SkIRect::MakeXYWH(-11, 0, 10, 10), // + SkIRect::MakeXYWH(0, 0, 10, 10), // + SkIRect::MakeXYWH(11, 0, 10, 10), // + SkIRect::MakeXYWH(-11, 11, 10, 10), // + SkIRect::MakeXYWH(0, 11, 10, 10), // + SkIRect::MakeXYWH(11, 11, 10, 10), + }; + EXPECT_EQ(rects, expected); +} + +TEST(DisplayListRegion, MergeTouchingRectangles) { + DlRegion region({ + SkIRect::MakeXYWH(0, 0, 10, 10), + SkIRect::MakeXYWH(-10, -10, 10, 10), + SkIRect::MakeXYWH(10, 10, 10, 10), + SkIRect::MakeXYWH(-10, 0, 10, 10), + SkIRect::MakeXYWH(0, 10, 10, 10), + SkIRect::MakeXYWH(0, -10, 10, 10), + SkIRect::MakeXYWH(10, 0, 10, 10), + SkIRect::MakeXYWH(10, -10, 10, 10), + SkIRect::MakeXYWH(-10, 10, 10, 10), + }); + + auto rects = region.getRects(); + std::vector expected{ + SkIRect::MakeXYWH(-10, -10, 30, 30), + }; + EXPECT_EQ(rects, expected); +} + +TEST(DisplayListRegion, OverlappingRectangles) { + std::vector rects_in; + for (int i = 0; i < 10; ++i) { + SkIRect rect = SkIRect::MakeXYWH(10 * i, 10 * i, 50, 50); + rects_in.push_back(rect); + } + DlRegion region(std::move(rects_in)); + auto rects = region.getRects(); + std::vector expected{ + {0, 0, 50, 10}, {0, 10, 60, 20}, {0, 20, 70, 30}, + {0, 30, 80, 40}, {0, 40, 90, 50}, {10, 50, 100, 60}, + {20, 60, 110, 70}, {30, 70, 120, 80}, {40, 80, 130, 90}, + {50, 90, 140, 100}, {60, 100, 140, 110}, {70, 110, 140, 120}, + {80, 120, 140, 130}, {90, 130, 140, 140}, + }; + + EXPECT_EQ(rects, expected); +} + +TEST(DisplayListRegion, Deband) { + DlRegion region({ + SkIRect::MakeXYWH(0, 0, 50, 50), + SkIRect::MakeXYWH(60, 0, 20, 20), + SkIRect::MakeXYWH(90, 0, 50, 50), + }); + + auto rects_with_deband = region.getRects(true); + std::vector expected{ + SkIRect::MakeXYWH(60, 0, 20, 20), + SkIRect::MakeXYWH(0, 0, 50, 50), + SkIRect::MakeXYWH(90, 0, 50, 50), + }; + EXPECT_EQ(rects_with_deband, expected); + + auto rects_without_deband = region.getRects(false); + std::vector expected_without_deband{ + SkIRect::MakeXYWH(0, 0, 50, 20), // + SkIRect::MakeXYWH(60, 0, 20, 20), // + SkIRect::MakeXYWH(90, 0, 50, 20), // + SkIRect::MakeXYWH(0, 20, 50, 30), // + SkIRect::MakeXYWH(90, 20, 50, 30), + }; + EXPECT_EQ(rects_without_deband, expected_without_deband); +} + +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}, + }; + + for (const auto& settings : all_settings) { + std::random_device d; + std::seed_seq seed{::testing::UnitTest::GetInstance()->random_seed()}; + std::mt19937 rng(seed); + + SkRegion sk_region; + + std::uniform_int_distribution pos(0, 4000); + std::uniform_int_distribution size(1, settings.max_size); + + std::vector rects_in; + + 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); + } + + DlRegion region(std::move(rects_in)); + + // Do not deband the rectangles - identical to SkRegion::Iterator + auto rects = 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); + } +} + +} // namespace testing +} // namespace flutter diff --git a/display_list/geometry/dl_rtree.cc b/display_list/geometry/dl_rtree.cc index 509607e50d7ce..548ff505071a4 100644 --- a/display_list/geometry/dl_rtree.cc +++ b/display_list/geometry/dl_rtree.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "flutter/display_list/geometry/dl_rtree.h" +#include "flutter/display_list/geometry/dl_region.h" #include "flutter/fml/logging.h" @@ -159,44 +160,25 @@ void DlRTree::search(const SkRect& query, std::vector* results) const { } } -std::list DlRTree::searchAndConsolidateRects( - const SkRect& query) const { +std::list DlRTree::searchAndConsolidateRects(const SkRect& query, + bool deband) const { // Get the indexes for the operations that intersect with the query rect. std::vector intermediary_results; search(query, &intermediary_results); - std::list final_results; + std::vector rects; + rects.reserve(intermediary_results.size()); for (int index : intermediary_results) { - auto current_record_rect = bounds(index); - auto replaced_existing_rect = false; - // // If the current record rect intersects with any of the rects in the - // // result list, then join them, and update the rect in final_results. - std::list::iterator curr_rect_itr = final_results.begin(); - std::list::iterator first_intersecting_rect_itr; - while (!replaced_existing_rect && curr_rect_itr != final_results.end()) { - if (SkRect::Intersects(*curr_rect_itr, current_record_rect)) { - replaced_existing_rect = true; - first_intersecting_rect_itr = curr_rect_itr; - curr_rect_itr->join(current_record_rect); - } - curr_rect_itr++; - } - // It's possible that the result contains duplicated rects at this point. - // For example, consider a result list that contains rects A, B. If a - // new rect C is a superset of A and B, then A and B are the same set after - // the merge. As a result, find such cases and remove them from the result - // list. - while (replaced_existing_rect && curr_rect_itr != final_results.end()) { - if (SkRect::Intersects(*curr_rect_itr, *first_intersecting_rect_itr)) { - first_intersecting_rect_itr->join(*curr_rect_itr); - curr_rect_itr = final_results.erase(curr_rect_itr); - } else { - curr_rect_itr++; - } - } - if (!replaced_existing_rect) { - final_results.push_back(current_record_rect); - } + SkIRect current_record_rect; + bounds(index).roundOut(¤t_record_rect); + rects.push_back(current_record_rect); + } + DlRegion region(std::move(rects)); + + auto non_overlapping_rects = region.getRects(deband); + std::list final_results; + for (const auto& rect : non_overlapping_rects) { + final_results.push_back(SkRect::Make(rect)); } return final_results; } diff --git a/display_list/geometry/dl_rtree.h b/display_list/geometry/dl_rtree.h index 9a3ab407d62c3..0c2f25abc766a 100644 --- a/display_list/geometry/dl_rtree.h +++ b/display_list/geometry/dl_rtree.h @@ -109,11 +109,15 @@ class DlRTree : public SkRefCnt { /// Finds the rects in the tree that intersect with the query rect. /// - /// When two matching query results intersect with each other, they are - /// joined into a single rect which also intersects with the query rect. + /// The returned list of rectangles will be non-overlapping. /// In other words, the bounds of each rect in the result list are mutually /// exclusive. - std::list searchAndConsolidateRects(const SkRect& query) const; + /// + /// If |deband| is true, then matching rectangles from adjacent DlRegion + /// spanlines will be joined together. This reduces the number of + /// rectangles returned, but requires some extra computation. + std::list searchAndConsolidateRects(const SkRect& query, + bool deband = true) const; private: static constexpr SkRect empty_ = SkRect::MakeEmpty(); diff --git a/flow/rtree.cc b/flow/rtree.cc index 70266346ee223..5e2630f867960 100644 --- a/flow/rtree.cc +++ b/flow/rtree.cc @@ -6,6 +6,7 @@ #include +#include "flutter/display_list/geometry/dl_region.h" #include "flutter/fml/logging.h" #include "third_party/skia/include/core/SkBBHFactory.h" #include "third_party/skia/include/core/SkRect.h" @@ -41,43 +42,23 @@ std::list RTree::searchNonOverlappingDrawnRects( std::vector intermediary_results; search(query, &intermediary_results); - std::list final_results; + std::vector rects; for (int index : intermediary_results) { auto draw_op = draw_op_.find(index); // Ignore records that don't draw anything. if (draw_op == draw_op_.end()) { continue; } - auto current_record_rect = draw_op->second; - auto replaced_existing_rect = false; - // // If the current record rect intersects with any of the rects in the - // // result list, then join them, and update the rect in final_results. - std::list::iterator curr_rect_itr = final_results.begin(); - std::list::iterator first_intersecting_rect_itr; - while (!replaced_existing_rect && curr_rect_itr != final_results.end()) { - if (SkRect::Intersects(*curr_rect_itr, current_record_rect)) { - replaced_existing_rect = true; - first_intersecting_rect_itr = curr_rect_itr; - curr_rect_itr->join(current_record_rect); - } - curr_rect_itr++; - } - // It's possible that the result contains duplicated rects at this point. - // For example, consider a result list that contains rects A, B. If a - // new rect C is a superset of A and B, then A and B are the same set after - // the merge. As a result, find such cases and remove them from the result - // list. - while (replaced_existing_rect && curr_rect_itr != final_results.end()) { - if (SkRect::Intersects(*curr_rect_itr, *first_intersecting_rect_itr)) { - first_intersecting_rect_itr->join(*curr_rect_itr); - curr_rect_itr = final_results.erase(curr_rect_itr); - } else { - curr_rect_itr++; - } - } - if (!replaced_existing_rect) { - final_results.push_back(current_record_rect); - } + SkIRect current_record_rect; + draw_op->second.roundOut(¤t_record_rect); + rects.push_back(current_record_rect); + } + + DlRegion region(std::move(rects)); + auto non_overlapping_rects = region.getRects(true); + std::list final_results; + for (const auto& rect : non_overlapping_rects) { + final_results.push_back(SkRect::Make(rect)); } return final_results; } diff --git a/flow/rtree_unittests.cc b/flow/rtree_unittests.cc index 1a1498efc8af3..873b5e398a49c 100644 --- a/flow/rtree_unittests.cc +++ b/flow/rtree_unittests.cc @@ -123,15 +123,15 @@ TEST(RTree, searchNonOverlappingDrawnRectsJoinRectsWhenIntersectedCase1) { rect_paint.setStyle(SkPaint::Style::kFill_Style); // Given the A, and B rects, which intersect with the query rect, - // the result list contains the rect resulting from the union of A and B. + // the result list contains the three rectangles covering same area. // - // +-----+ - // | A | - // | +-----+ - // | | C | - // | +-----+ - // | | - // +-----+ + // +-----+ +-----+ + // | A | | A | + // | +-----+ +---------+ + // | | | | B | + // +---| B | +---+-----+ + // | | | C | + // +-----+ +-----+ // A recording_canvas->drawRect(SkRect::MakeLTRB(100, 100, 150, 150), rect_paint); @@ -142,8 +142,10 @@ TEST(RTree, searchNonOverlappingDrawnRectsJoinRectsWhenIntersectedCase1) { auto hits = rtree_factory.getInstance()->searchNonOverlappingDrawnRects( SkRect::MakeXYWH(120, 120, 126, 126)); - ASSERT_EQ(1UL, hits.size()); - ASSERT_EQ(*hits.begin(), SkRect::MakeLTRB(100, 100, 175, 175)); + ASSERT_EQ(3UL, hits.size()); + ASSERT_EQ(*hits.begin(), SkRect::MakeLTRB(100, 100, 150, 125)); + ASSERT_EQ(*std::next(hits.begin(), 1), SkRect::MakeLTRB(100, 125, 175, 150)); + ASSERT_EQ(*std::next(hits.begin(), 2), SkRect::MakeLTRB(125, 150, 175, 175)); } TEST(RTree, searchNonOverlappingDrawnRectsJoinRectsWhenIntersectedCase2) { @@ -198,7 +200,7 @@ TEST(RTree, searchNonOverlappingDrawnRectsJoinRectsWhenIntersectedCase3) { rect_paint.setStyle(SkPaint::Style::kFill_Style); // Given the A, B, C and D rects that intersect with the query rect, - // the result list contains a single rect, which is the union of + // the result list contains two rects - D and remainder of C // these four rects. // // +------------------------------+ @@ -227,8 +229,9 @@ TEST(RTree, searchNonOverlappingDrawnRectsJoinRectsWhenIntersectedCase3) { auto hits = rtree_factory.getInstance()->searchNonOverlappingDrawnRects( SkRect::MakeLTRB(30, 30, 550, 270)); - ASSERT_EQ(1UL, hits.size()); - ASSERT_EQ(*hits.begin(), SkRect::MakeLTRB(50, 50, 620, 300)); + ASSERT_EQ(2UL, hits.size()); + ASSERT_EQ(*hits.begin(), SkRect::MakeLTRB(50, 50, 620, 250)); + ASSERT_EQ(*std::next(hits.begin(), 1), SkRect::MakeLTRB(500, 250, 600, 300)); } } // namespace testing diff --git a/testing/scenario_app/ios/Scenarios/ScenariosUITests/UnobstructedPlatformViewTests.m b/testing/scenario_app/ios/Scenarios/ScenariosUITests/UnobstructedPlatformViewTests.m index 57d627e2b2145..3397e4b1effa2 100644 --- a/testing/scenario_app/ios/Scenarios/ScenariosUITests/UnobstructedPlatformViewTests.m +++ b/testing/scenario_app/ios/Scenarios/ScenariosUITests/UnobstructedPlatformViewTests.m @@ -158,17 +158,14 @@ - (void)testOneOverlayAndTwoIntersectingOverlays { XCUIElement* overlay1 = app.otherElements[@"platform_view[0].overlay[0]"]; XCTAssertTrue(overlay1.exists); - XCTAssertEqual(overlay1.frame.origin.x, 150); + XCTAssertEqual(overlay1.frame.origin.x, 75); XCTAssertEqual(overlay1.frame.origin.y, 150); - XCTAssertEqual(overlay1.frame.size.width, 75); - XCTAssertEqual(overlay1.frame.size.height, 75); + XCTAssertEqual(overlay1.frame.size.width, 150); + XCTAssertEqual(overlay1.frame.size.height, 100); - XCUIElement* overlay2 = app.otherElements[@"platform_view[0].overlay[1]"]; - XCTAssertTrue(overlay2.exists); - XCTAssertEqual(overlay2.frame.origin.x, 75); - XCTAssertEqual(overlay2.frame.origin.y, 225); - XCTAssertEqual(overlay2.frame.size.width, 50); - XCTAssertEqual(overlay2.frame.size.height, 25); + // There are three non overlapping rects above platform view, which + // FlutterPlatformViewsController merges into one. + XCTAssertFalse(app.otherElements[@"platform_view[0].overlay[1]"].exists); XCUIElement* overlayView0 = app.otherElements[@"platform_view[0].overlay_view[0]"]; XCTAssertTrue(overlayView0.exists); @@ -178,15 +175,6 @@ - (void)testOneOverlayAndTwoIntersectingOverlays { XCTAssertEqualWithAccuracy(overlayView0.frame.size.width, app.frame.size.width, kCompareAccuracy); XCTAssertEqualWithAccuracy(overlayView0.frame.size.height, app.frame.size.height, kCompareAccuracy); - - XCUIElement* overlayView1 = app.otherElements[@"platform_view[0].overlay_view[1]"]; - XCTAssertTrue(overlayView1.exists); - // Overlay should always be the same frame as the app. - XCTAssertEqualWithAccuracy(overlayView1.frame.origin.x, app.frame.origin.x, kCompareAccuracy); - XCTAssertEqualWithAccuracy(overlayView1.frame.origin.y, app.frame.origin.x, kCompareAccuracy); - XCTAssertEqualWithAccuracy(overlayView1.frame.size.width, app.frame.size.width, kCompareAccuracy); - XCTAssertEqualWithAccuracy(overlayView1.frame.size.height, app.frame.size.height, - kCompareAccuracy); } // A is the layer, which z index is higher than the platform view.