diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index d0182f4580ab0..3d758d0a63861 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -92,6 +92,9 @@ FILE: ../../../flutter/flow/raster_cache.h FILE: ../../../flutter/flow/raster_cache_key.cc FILE: ../../../flutter/flow/raster_cache_key.h FILE: ../../../flutter/flow/raster_cache_unittests.cc +FILE: ../../../flutter/flow/rtree.cc +FILE: ../../../flutter/flow/rtree.h +FILE: ../../../flutter/flow/rtree_unittests.cc FILE: ../../../flutter/flow/scene_update_context.cc FILE: ../../../flutter/flow/scene_update_context.h FILE: ../../../flutter/flow/skia_gpu_object.cc diff --git a/flow/BUILD.gn b/flow/BUILD.gn index c194d35ea628d..14ceec4dadc5c 100644 --- a/flow/BUILD.gn +++ b/flow/BUILD.gn @@ -58,6 +58,8 @@ source_set("flow") { "raster_cache.h", "raster_cache_key.cc", "raster_cache_key.h", + "rtree.cc", + "rtree.h", "skia_gpu_object.cc", "skia_gpu_object.h", "texture.cc", @@ -150,6 +152,7 @@ executable("flow_unittests") { "matrix_decomposition_unittests.cc", "mutators_stack_unittests.cc", "raster_cache_unittests.cc", + "rtree_unittests.cc", "skia_gpu_object_unittests.cc", "testing/mock_layer_unittests.cc", "testing/mock_texture_unittests.cc", diff --git a/flow/rtree.cc b/flow/rtree.cc new file mode 100644 index 0000000000000..a6df0d903c303 --- /dev/null +++ b/flow/rtree.cc @@ -0,0 +1,100 @@ +// 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 "rtree.h" + +#include + +#include "flutter/fml/logging.h" +#include "third_party/skia/include/core/SkBBHFactory.h" + +namespace flutter { + +RTree::RTree() : bbh_{SkRTreeFactory{}()}, all_ops_count_(0) {} + +void RTree::insert(const SkRect boundsArray[], + const SkBBoxHierarchy::Metadata metadata[], + int N) { + FML_DCHECK(0 == all_ops_count_); + bbh_->insert(boundsArray, metadata, N); + for (int i = 0; i < N; i++) { + if (metadata != nullptr && metadata[i].isDraw) { + draw_op_[i] = boundsArray[i]; + } + } + all_ops_count_ = N; +} + +void RTree::insert(const SkRect boundsArray[], int N) { + insert(boundsArray, nullptr, N); +} + +void RTree::search(const SkRect& query, std::vector* results) const { + bbh_->search(query, results); +} + +std::list RTree::searchNonOverlappingDrawnRects( + const SkRect& query) 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; + 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); + } + } + return final_results; +} + +size_t RTree::bytesUsed() const { + return bbh_->bytesUsed(); +} + +RTreeFactory::RTreeFactory() { + r_tree_ = sk_make_sp(); +} + +sk_sp RTreeFactory::getInstance() { + return r_tree_; +} + +sk_sp RTreeFactory::operator()() const { + return r_tree_; +} + +} // namespace flutter diff --git a/flow/rtree.h b/flow/rtree.h new file mode 100644 index 0000000000000..35c74137243c8 --- /dev/null +++ b/flow/rtree.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_FLOW_RTREE_H_ +#define FLUTTER_FLOW_RTREE_H_ + +#include +#include + +#include "third_party/skia/include/core/SkBBHFactory.h" +#include "third_party/skia/include/core/SkTypes.h" + +namespace flutter { +/** + * An R-Tree implementation that forwards calls to an SkRTree. + * + * This implementation provides a searchNonOverlappingDrawnRects method, + * which can be used to query the rects for the operations recorded in the tree. + */ +class RTree : public SkBBoxHierarchy { + public: + RTree(); + + void insert(const SkRect[], + const SkBBoxHierarchy::Metadata[], + int N) override; + void insert(const SkRect[], int N) override; + void search(const SkRect& query, std::vector* results) const override; + size_t bytesUsed() const override; + + // Finds the rects in the tree that represent drawing operations and intersect + // with the query rect. + // + // When two rects intersect with each other, they are joined into a single + // rect which also intersects with the query rect. In other words, the bounds + // of each rect in the result list are mutually exclusive. + std::list searchNonOverlappingDrawnRects(const SkRect& query) const; + + // Insertion count (not overall node count, which may be greater). + int getCount() const { return all_ops_count_; } + + private: + // A map containing the draw operation rects keyed off the operation index + // in the insert call. + std::map draw_op_; + sk_sp bbh_; + int all_ops_count_; +}; + +class RTreeFactory : public SkBBHFactory { + public: + RTreeFactory(); + + // Gets the instance to the R-tree. + sk_sp getInstance(); + sk_sp operator()() const override; + + private: + sk_sp r_tree_; +}; + +} // namespace flutter + +#endif // FLUTTER_FLOW_RTREE_H_ diff --git a/flow/rtree_unittests.cc b/flow/rtree_unittests.cc new file mode 100644 index 0000000000000..f3e82e00ebc4f --- /dev/null +++ b/flow/rtree_unittests.cc @@ -0,0 +1,235 @@ +// 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 "rtree.h" + +#include "flutter/testing/testing.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkPictureRecorder.h" + +namespace flutter { +namespace testing { + +TEST(RTree, searchNonOverlappingDrawnRects_NoIntersection) { + auto rtree_factory = RTreeFactory(); + auto recorder = std::make_unique(); + auto recording_canvas = + recorder->beginRecording(SkRect::MakeIWH(1000, 1000), &rtree_factory); + + auto rect_paint = SkPaint(); + rect_paint.setColor(SkColors::kCyan); + rect_paint.setStyle(SkPaint::Style::kFill_Style); + + // If no rect is intersected with the query rect, then the result list is + // empty. + recording_canvas->drawRect(SkRect::MakeLTRB(20, 20, 40, 40), rect_paint); + recorder->finishRecordingAsPicture(); + + auto hits = rtree_factory.getInstance()->searchNonOverlappingDrawnRects( + SkRect::MakeLTRB(40, 40, 80, 80)); + ASSERT_TRUE(hits.empty()); +} + +TEST(RTree, searchNonOverlappingDrawnRects_SingleRectIntersection) { + auto rtree_factory = RTreeFactory(); + auto recorder = std::make_unique(); + auto recording_canvas = + recorder->beginRecording(SkRect::MakeIWH(1000, 1000), &rtree_factory); + + auto rect_paint = SkPaint(); + rect_paint.setColor(SkColors::kCyan); + rect_paint.setStyle(SkPaint::Style::kFill_Style); + + // Given a single rect A that intersects with the query rect, + // the result list contains this rect. + recording_canvas->drawRect(SkRect::MakeLTRB(120, 120, 160, 160), rect_paint); + + recorder->finishRecordingAsPicture(); + + auto hits = rtree_factory.getInstance()->searchNonOverlappingDrawnRects( + SkRect::MakeLTRB(140, 140, 150, 150)); + ASSERT_EQ(1UL, hits.size()); + ASSERT_EQ(*hits.begin(), SkRect::MakeLTRB(120, 120, 160, 160)); +} + +TEST(RTree, searchNonOverlappingDrawnRects_IgnoresNonDrawingRecords) { + auto rtree_factory = RTreeFactory(); + auto recorder = std::make_unique(); + auto recording_canvas = + recorder->beginRecording(SkRect::MakeIWH(1000, 1000), &rtree_factory); + + auto rect_paint = SkPaint(); + rect_paint.setColor(SkColors::kCyan); + rect_paint.setStyle(SkPaint::Style::kFill_Style); + + // Creates two non drawing records. + recording_canvas->translate(100, 100); + // The result list should only contain the clipping rect. + recording_canvas->clipRect(SkRect::MakeLTRB(40, 40, 50, 50), + SkClipOp::kIntersect); + recording_canvas->drawRect(SkRect::MakeLTRB(20, 20, 80, 80), rect_paint); + + recorder->finishRecordingAsPicture(); + + // The rtree has a translate, a clip and a rect record. + ASSERT_EQ(3, rtree_factory.getInstance()->getCount()); + + auto hits = rtree_factory.getInstance()->searchNonOverlappingDrawnRects( + SkRect::MakeLTRB(0, 0, 1000, 1000)); + ASSERT_EQ(1UL, hits.size()); + ASSERT_EQ(*hits.begin(), SkRect::MakeLTRB(120, 120, 180, 180)); +} + +TEST(RTree, searchNonOverlappingDrawnRects_MultipleRectIntersection) { + auto rtree_factory = RTreeFactory(); + auto recorder = std::make_unique(); + auto recording_canvas = + recorder->beginRecording(SkRect::MakeIWH(1000, 1000), &rtree_factory); + + auto rect_paint = SkPaint(); + rect_paint.setColor(SkColors::kCyan); + rect_paint.setStyle(SkPaint::Style::kFill_Style); + + // Given the A, B that intersect with the query rect, + // there should be A and B in the result list since + // they don't intersect with each other. + // + // +-----+ +-----+ + // | A | | B | + // +-----+ +-----+ + // A + recording_canvas->drawRect(SkRect::MakeLTRB(100, 100, 200, 200), rect_paint); + // B + recording_canvas->drawRect(SkRect::MakeLTRB(300, 100, 400, 200), rect_paint); + + recorder->finishRecordingAsPicture(); + + auto hits = rtree_factory.getInstance()->searchNonOverlappingDrawnRects( + SkRect::MakeLTRB(0, 0, 1000, 1050)); + ASSERT_EQ(2UL, hits.size()); + ASSERT_EQ(*hits.begin(), SkRect::MakeLTRB(100, 100, 200, 200)); + ASSERT_EQ(*std::next(hits.begin(), 1), SkRect::MakeLTRB(300, 100, 400, 200)); +} + +TEST(RTree, searchNonOverlappingDrawnRects_JoinRectsWhenIntersectedCase1) { + auto rtree_factory = RTreeFactory(); + auto recorder = std::make_unique(); + auto recording_canvas = + recorder->beginRecording(SkRect::MakeIWH(1000, 1000), &rtree_factory); + + auto rect_paint = SkPaint(); + rect_paint.setColor(SkColors::kCyan); + 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. + // + // +-----+ + // | A | + // | +-----+ + // | | C | + // | +-----+ + // | | + // +-----+ + + // A + recording_canvas->drawRect(SkRect::MakeLTRB(100, 100, 150, 150), rect_paint); + // B + recording_canvas->drawRect(SkRect::MakeLTRB(125, 125, 175, 175), rect_paint); + + recorder->finishRecordingAsPicture(); + + 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)); +} + +TEST(RTree, searchNonOverlappingDrawnRects_JoinRectsWhenIntersectedCase2) { + auto rtree_factory = RTreeFactory(); + auto recorder = std::make_unique(); + auto recording_canvas = + recorder->beginRecording(SkRect::MakeIWH(1000, 1000), &rtree_factory); + + auto rect_paint = SkPaint(); + rect_paint.setColor(SkColors::kCyan); + rect_paint.setStyle(SkPaint::Style::kFill_Style); + + // Given the A, B, and C rects that intersect with the query rect, + // there should be only C in the result list, + // since A and B are contained in C. + // + // +---------------------+ + // | C | + // | +-----+ +-----+ | + // | | A | | B | | + // | +-----+ +-----+ | + // +---------------------+ + // +-----+ + // | D | + // +-----+ + + // A + recording_canvas->drawRect(SkRect::MakeLTRB(100, 100, 200, 200), rect_paint); + // B + recording_canvas->drawRect(SkRect::MakeLTRB(300, 100, 400, 200), rect_paint); + // C + recording_canvas->drawRect(SkRect::MakeLTRB(50, 50, 500, 250), rect_paint); + // D + recording_canvas->drawRect(SkRect::MakeLTRB(280, 100, 280, 320), rect_paint); + + recorder->finishRecordingAsPicture(); + + 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, 500, 250)); +} + +TEST(RTree, searchNonOverlappingDrawnRects_JoinRectsWhenIntersectedCase3) { + auto rtree_factory = RTreeFactory(); + auto recorder = std::make_unique(); + auto recording_canvas = + recorder->beginRecording(SkRect::MakeIWH(1000, 1000), &rtree_factory); + + auto rect_paint = SkPaint(); + rect_paint.setColor(SkColors::kCyan); + 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 + // these four rects. + // + // +------------------------------+ + // | D | + // | +-----+ +-----+ +-----+ | + // | | A | | B | | C | | + // | +-----+ +-----+ | | | + // +----------------------| |-+ + // +-----+ + // +-----+ + // | E | + // +-----+ + + // A + recording_canvas->drawRect(SkRect::MakeLTRB(100, 100, 200, 200), rect_paint); + // B + recording_canvas->drawRect(SkRect::MakeLTRB(300, 100, 400, 200), rect_paint); + // C + recording_canvas->drawRect(SkRect::MakeLTRB(500, 100, 600, 300), rect_paint); + // D + recording_canvas->drawRect(SkRect::MakeLTRB(50, 50, 620, 250), rect_paint); + // E + recording_canvas->drawRect(SkRect::MakeLTRB(280, 100, 280, 320), rect_paint); + + recorder->finishRecordingAsPicture(); + + 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)); +} + +} // namespace testing +} // namespace flutter