| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef CC_BASE_RTREE_H_ |
| #define CC_BASE_RTREE_H_ |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <array> |
| #include <cmath> |
| #include <map> |
| #include <optional> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/check_op.h" |
| #include "base/containers/heap_array.h" |
| #include "base/memory/raw_ptr_exclusion.h" |
| #include "base/numerics/clamped_math.h" |
| #include "ui/gfx/geometry/rect.h" |
| |
| namespace cc { |
| |
| // The following description and most of the implementation is borrowed from |
| // Skia's SkRTree implementation. |
| // |
| // An R-Tree implementation. In short, it is a balanced n-ary tree containing a |
| // hierarchy of bounding rectangles. |
| // |
| // It only supports bulk-loading, i.e. creation from a batch of bounding |
| // rectangles. This performs a bottom-up bulk load using the STR |
| // (sort-tile-recursive) algorithm. |
| // |
| // Things to do: Experiment with other bulk-load algorithms (in particular the |
| // Hilbert pack variant, which groups rects by position on the Hilbert curve, is |
| // probably worth a look). There also exist top-down bulk load variants |
| // (VAMSplit, TopDownGreedy, etc). |
| // |
| // For more details see: |
| // |
| // Beckmann, N.; Kriegel, H. P.; Schneider, R.; Seeger, B. (1990). |
| // "The R*-tree: an efficient and robust access method for points and |
| // rectangles" |
| template <typename T> |
| class RTree { |
| public: |
| RTree(); |
| RTree(const RTree&) = delete; |
| ~RTree(); |
| |
| RTree& operator=(const RTree&) = delete; |
| |
| // Constructs the rtree from a given container of gfx::Rects. Queries using |
| // Search will then return indices into this container. |
| template <typename Container> |
| void Build(const Container& items); |
| |
| // Build helper that takes a functions to provide rects and payloads. |
| // `bounds_getter(i)` should return the gfx::Rect representing the bounds of |
| // the ith item, and `payload_getter(i)` should return the payload (aka T) of |
| // the ith item. |
| template <typename BoundsFunctor, typename PayloadFunctor> |
| void Build(size_t item_count, |
| const BoundsFunctor& bounds_getter, |
| const PayloadFunctor& payload_getter); |
| |
| // If false, this rtree does not have valid bounds and: |
| // - Search* will have degraded performance. |
| bool has_valid_bounds() const { return has_valid_bounds_; } |
| |
| // Given a query rect, for each element that intersects the rect, |
| // result_handler is called with the payload and the rect of the element, |
| // in the order they appeared in the initial container. |
| template <typename ResultFunctor> |
| void Search(const gfx::Rect& query, |
| const ResultFunctor& result_handler) const; |
| |
| // Given a query rect, returns elements that intersect the rect. Elements are |
| // returned in the order they appeared in the initial container. |
| void Search(const gfx::Rect& query, |
| std::vector<T>* results, |
| std::vector<gfx::Rect>* rects = nullptr) const; |
| |
| // Given a query rect, returns non-owning pointers to elements that intersect |
| // the rect. Elements are returned in the order they appeared in the initial |
| // container. |
| void SearchRefs(const gfx::Rect& query, std::vector<const T*>* results) const; |
| |
| // Returns the total bounds of all items in this rtree. |
| std::optional<gfx::Rect> bounds() const; |
| |
| // Returns respective bounds of all items in this rtree in the order of items. |
| // Production code except tracing should not use this method. |
| std::map<T, gfx::Rect> GetAllBoundsForTracing() const; |
| |
| private: |
| // These values were empirically determined to produce reasonable performance |
| // in most cases. |
| static constexpr size_t kMinChildren = 6; |
| static constexpr size_t kMaxChildren = 11; |
| |
| struct Node; |
| |
| struct Branch { |
| // When the node level is 0, then the node is a leaf and the branch has a |
| // valid index pointing to an element in the vector that was used to build |
| // this rtree. When the level is not 0, it's an internal node and it has a |
| // valid subtree pointer. |
| // RAW_PTR_EXCLUSION: Performance reasons (based on analysis of |
| // speedometer3). |
| RAW_PTR_EXCLUSION Node* subtree = nullptr; |
| T payload; |
| |
| gfx::Rect bounds; |
| |
| Branch() = default; |
| Branch(T payload, const gfx::Rect& bounds) |
| : payload(std::move(payload)), bounds(bounds) {} |
| }; |
| |
| struct Node { |
| uint16_t num_children = 0; |
| uint16_t level = 0; |
| std::array<Branch, kMaxChildren> children; |
| |
| Node() = default; |
| explicit Node(uint16_t level) : level(level) {} |
| }; |
| |
| template <typename ResultFunctor> |
| static void SearchRecursive(const Node& node, |
| const gfx::Rect& query, |
| const ResultFunctor& result_handler); |
| |
| // The following two functions are slow fallback versions of SearchRecursive |
| // and SearchRefsRecursive for when !has_valid_bounds(). |
| template <typename ResultFunctor> |
| static void SearchRecursiveFallback(const Node& node, |
| const gfx::Rect& query, |
| const ResultFunctor& result_handler); |
| |
| // Consumes the input array. |
| Node* AllocateNodeAtLevel(uint16_t level, size_t& nodes_size); |
| Branch BuildRecursive(std::vector<Branch>& branches, |
| uint16_t level, |
| size_t& nodes_size); |
| |
| static void GetAllBoundsRecursive(const Node& node, |
| std::map<T, gfx::Rect>* results); |
| |
| // This is the count of data elements (rather than total nodes in the |
| // tree) |
| size_t num_data_elements_ = 0u; |
| base::HeapArray<Node> nodes_; |
| Branch root_; |
| |
| // If false, the rtree encountered overflow does not have reliable bounds. |
| bool has_valid_bounds_ = true; |
| }; |
| |
| template <typename T> |
| RTree<T>::RTree() = default; |
| |
| template <typename T> |
| RTree<T>::~RTree() = default; |
| |
| template <typename T> |
| template <typename Container> |
| void RTree<T>::Build(const Container& items) { |
| Build( |
| items.size(), [&items](size_t index) { return items[index]; }, |
| [](size_t index) { return index; }); |
| } |
| |
| template <typename T> |
| template <typename BoundsFunctor, typename PayloadFunctor> |
| void RTree<T>::Build(size_t item_count, |
| const BoundsFunctor& bounds_getter, |
| const PayloadFunctor& payload_getter) { |
| DCHECK_EQ(0u, num_data_elements_); |
| |
| std::vector<Branch> branches; |
| branches.reserve(item_count); |
| |
| for (size_t i = 0; i < item_count; i++) { |
| const gfx::Rect& bounds = bounds_getter(i); |
| if (bounds.IsEmpty()) { |
| continue; |
| } |
| branches.emplace_back(payload_getter(i), bounds); |
| } |
| |
| num_data_elements_ = branches.size(); |
| size_t nodes_size = 0; |
| if (num_data_elements_ == 1u) { |
| // Node is not trivially constructible (because gfx::Rect is not), so we |
| // cannot use base::HeapArray::Uninit. |
| nodes_ = base::HeapArray<Node>::WithSize(1); |
| Node* node = AllocateNodeAtLevel(0, nodes_size); |
| root_.subtree = node; |
| root_.bounds = branches[0].bounds; |
| node->num_children = 1; |
| node->children[0] = std::move(branches[0]); |
| } else if (num_data_elements_ > 1u) { |
| // Determine a precise upper bound on the number of nodes. This is a |
| // bottom-up calculation that determines the number of nodes required at |
| // each level of the tree. |
| // |
| // The total node count is the sum of a geometric series that converges to |
| // N / (kMaxChildren - 1). Since N is the size of a vector, and each element |
| // is at least 24 bytes, the sum will never overflow SIZE_MAX. |
| // |
| // If this calculation is ever wrong, the bounds check in |
| // AllocateNodeAtLevel will catch it. |
| size_t node_count = 0; |
| for (size_t n = num_data_elements_; n > 1;) { |
| n = (n + kMaxChildren - 1) / kMaxChildren; |
| node_count += n; |
| } |
| // Node is not trivially constructible (because gfx::Rect is not), so we |
| // cannot use base::HeapArray::Uninit. |
| nodes_ = base::HeapArray<Node>::WithSize(node_count); |
| root_ = BuildRecursive(branches, 0, nodes_size); |
| } |
| // We should've initialized exactly the number of nodes we calculated. |
| CHECK_EQ(nodes_.size(), nodes_size); |
| } |
| |
| template <typename T> |
| auto RTree<T>::AllocateNodeAtLevel(uint16_t level, size_t& nodes_size) |
| -> Node* { |
| // HeapArray does not support reallocations, so pointers to nodes are stable |
| // for the lifetime of the RTree. HeapArray's operator[] will CHECK if we |
| // exceed the pre-allocated capacity. |
| Node& node = nodes_[nodes_size++]; |
| node = Node(level); |
| return &node; |
| } |
| |
| template <typename T> |
| auto RTree<T>::BuildRecursive(std::vector<Branch>& branches, |
| uint16_t level, |
| size_t& nodes_size) -> Branch { |
| // Only one branch. It will be the root. |
| if (branches.size() == 1) { |
| return std::move(branches[0]); |
| } |
| |
| // TODO(vmpstr): Investigate if branches should be sorted in y. |
| // The comment from Skia reads: |
| // We might sort our branches here, but we expect Blink gives us a reasonable |
| // x,y order. Skipping a call to sort (in Y) here resulted in a 17% win for |
| // recording with negligible difference in playback speed. |
| size_t remainder = branches.size() % kMaxChildren; |
| |
| if (remainder > 0) { |
| // If the remainder isn't enough to fill a node, we'll add fewer nodes to |
| // other branches. |
| if (remainder >= kMinChildren) { |
| remainder = 0; |
| } else { |
| remainder = kMinChildren - remainder; |
| } |
| } |
| |
| size_t current_branch = 0; |
| |
| size_t new_branch_index = 0; |
| while (current_branch < branches.size()) { |
| size_t increment_by = kMaxChildren; |
| if (remainder != 0) { |
| // if need be, omit some nodes to make up for remainder |
| if (remainder <= kMaxChildren - kMinChildren) { |
| increment_by -= remainder; |
| remainder = 0; |
| } else { |
| increment_by = kMinChildren; |
| remainder -= kMaxChildren - kMinChildren; |
| } |
| } |
| Node* node = AllocateNodeAtLevel(level, nodes_size); |
| node->num_children = 1; |
| node->children[0] = branches[current_branch]; |
| |
| Branch branch; |
| branch.bounds = branches[current_branch].bounds; |
| branch.subtree = node; |
| ++current_branch; |
| int x = branch.bounds.x(); |
| int y = branch.bounds.y(); |
| int right = branch.bounds.right(); |
| int bottom = branch.bounds.bottom(); |
| for (size_t k = 1; k < increment_by && current_branch < branches.size(); |
| ++k) { |
| // We use a custom union instead of gfx::Rect::Union here, since this |
| // bypasses some empty checks and extra setters, which improves |
| // performance. |
| const auto& bounds = branches[current_branch].bounds; |
| x = std::min(x, bounds.x()); |
| y = std::min(y, bounds.y()); |
| right = std::max(right, bounds.right()); |
| bottom = std::max(bottom, bounds.bottom()); |
| |
| node->children[k] = branches[current_branch]; |
| ++node->num_children; |
| ++current_branch; |
| } |
| branch.bounds.SetRect(x, y, base::ClampSub(right, x), |
| base::ClampSub(bottom, y)); |
| |
| // If we had to clamp right/bottom values, we've overflowed. |
| bool overflow = |
| branch.bounds.right() != right || branch.bounds.bottom() != bottom; |
| has_valid_bounds_ &= !overflow; |
| |
| DCHECK_LT(new_branch_index, current_branch); |
| branches[new_branch_index] = std::move(branch); |
| ++new_branch_index; |
| } |
| branches.resize(new_branch_index); |
| return BuildRecursive(branches, level + 1, nodes_size); |
| } |
| |
| template <typename T> |
| template <typename ResultFunctor> |
| void RTree<T>::Search(const gfx::Rect& query, |
| const ResultFunctor& result_handler) const { |
| if (num_data_elements_ == 0) { |
| return; |
| } |
| CHECK(root_.subtree); |
| if (!has_valid_bounds_) { |
| SearchRecursiveFallback(*root_.subtree, query, result_handler); |
| } else if (query.Intersects(root_.bounds)) { |
| SearchRecursive(*root_.subtree, query, result_handler); |
| } |
| } |
| |
| template <typename T> |
| void RTree<T>::Search(const gfx::Rect& query, |
| std::vector<T>* results, |
| std::vector<gfx::Rect>* rects) const { |
| results->clear(); |
| if (rects) { |
| rects->clear(); |
| } |
| Search(query, [results, rects](const T& payload, const gfx::Rect& rect) { |
| results->push_back(payload); |
| if (rects) { |
| rects->push_back(rect); |
| } |
| }); |
| } |
| |
| template <typename T> |
| void RTree<T>::SearchRefs(const gfx::Rect& query, |
| std::vector<const T*>* results) const { |
| results->clear(); |
| Search(query, [results](const T& payload, const gfx::Rect&) { |
| results->push_back(&payload); |
| }); |
| } |
| |
| // static |
| template <typename T> |
| template <typename ResultFunctor> |
| void RTree<T>::SearchRecursive(const Node& node, |
| const gfx::Rect& query, |
| const ResultFunctor& result_handler) { |
| for (uint16_t i = 0; i < node.num_children; ++i) { |
| const auto& child = node.children[i]; |
| if (query.Intersects(child.bounds)) { |
| if (node.level == 0) { |
| result_handler(child.payload, child.bounds); |
| } else { |
| CHECK(child.subtree); |
| SearchRecursive(*child.subtree, query, result_handler); |
| } |
| } |
| } |
| } |
| |
| // When !has_valid_bounds(), any non-leaf bounds may have overflowed and be |
| // invalid. Iterate over the entire tree, checking bounds at each leaf. |
| // static |
| template <typename T> |
| template <typename ResultFunctor> |
| void RTree<T>::SearchRecursiveFallback(const Node& node, |
| const gfx::Rect& query, |
| const ResultFunctor& result_handler) { |
| for (uint16_t i = 0; i < node.num_children; ++i) { |
| const auto& child = node.children[i]; |
| if (node.level == 0) { |
| if (query.Intersects(child.bounds)) { |
| result_handler(child.payload, child.bounds); |
| } |
| } else { |
| CHECK(child.subtree); |
| SearchRecursive(*child.subtree, query, result_handler); |
| } |
| } |
| } |
| |
| template <typename T> |
| std::optional<gfx::Rect> RTree<T>::bounds() const { |
| if (has_valid_bounds_) { |
| return root_.bounds; |
| } |
| return std::nullopt; |
| } |
| |
| template <typename T> |
| std::map<T, gfx::Rect> RTree<T>::GetAllBoundsForTracing() const { |
| std::map<T, gfx::Rect> results; |
| if (num_data_elements_ > 0) { |
| CHECK(root_.subtree); |
| GetAllBoundsRecursive(*root_.subtree, &results); |
| } |
| return results; |
| } |
| |
| // static |
| template <typename T> |
| void RTree<T>::GetAllBoundsRecursive(const Node& node, |
| std::map<T, gfx::Rect>* results) { |
| for (uint16_t i = 0; i < node.num_children; ++i) { |
| const auto& child = node.children[i]; |
| if (node.level == 0) { |
| (*results)[child.payload] = child.bounds; |
| } else { |
| CHECK(child.subtree); |
| GetAllBoundsRecursive(*child.subtree, results); |
| } |
| } |
| } |
| |
| } // namespace cc |
| |
| #endif // CC_BASE_RTREE_H_ |