1 /*
2 * Copyright 2021 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "src/gpu/graphite/geom/IntersectionTree.h"
9
10 #include "include/core/SkTypes.h"
11 #include "include/private/base/SkTPin.h"
12 #include "src/base/SkVx.h"
13
14 #include <algorithm>
15 #include <limits>
16
17 namespace skgpu::graphite {
18
19 // BSP node. Space is partitioned by an either vertical or horizontal line. Note that if a rect
20 // straddles the partition line, it will need to go on both sides of the tree.
21 template<IntersectionTree::SplitType kSplitType>
22 class IntersectionTree::TreeNode final : public Node {
23 public:
TreeNode(float splitCoord,Node * lo,Node * hi)24 TreeNode(float splitCoord, Node* lo, Node* hi)
25 : fSplitCoord(splitCoord), fLo(lo), fHi(hi) {
26 }
27
intersects(Rect rect)28 bool intersects(Rect rect) override {
29 if (GetLoVal(rect) < fSplitCoord && fLo->intersects(rect)) {
30 return true;
31 }
32 if (GetHiVal(rect) > fSplitCoord && fHi->intersects(rect)) {
33 return true;
34 }
35 return false;
36 }
37
addNonIntersecting(Rect rect,SkArenaAlloc * arena)38 Node* addNonIntersecting(Rect rect, SkArenaAlloc* arena) override {
39 if (GetLoVal(rect) < fSplitCoord) {
40 fLo = fLo->addNonIntersecting(rect, arena);
41 }
42 if (GetHiVal(rect) > fSplitCoord) {
43 fHi = fHi->addNonIntersecting(rect, arena);
44 }
45 return this;
46 }
47
48 private:
GetLoVal(const Rect & rect)49 SK_ALWAYS_INLINE static float GetLoVal(const Rect& rect) {
50 return (kSplitType == SplitType::kX) ? rect.left() : rect.top();
51 }
GetHiVal(const Rect & rect)52 SK_ALWAYS_INLINE static float GetHiVal(const Rect& rect) {
53 return (kSplitType == SplitType::kX) ? rect.right() : rect.bot();
54 }
55
56 float fSplitCoord;
57 Node* fLo;
58 Node* fHi;
59 };
60
61 // Leaf node. Rects are kept in a simple list and intersection testing is performed by brute force.
62 class IntersectionTree::LeafNode final : public Node {
63 public:
64 // Max number of rects to store in this node before splitting. With SSE/NEON optimizations, ~64
65 // brute force rect comparisons seems to be the optimal number.
66 constexpr static int kMaxRectsInList = 64;
67
LeafNode()68 LeafNode() {
69 this->popAll();
70 // Initialize our arrays with maximally negative rects. These have the advantage of always
71 // failing intersection tests, thus allowing us to test for intersection beyond fNumRects
72 // without failing.
73 constexpr static float infinity = std::numeric_limits<float>::infinity();
74 std::fill_n(fLefts, kMaxRectsInList, infinity);
75 std::fill_n(fTops, kMaxRectsInList, infinity);
76 std::fill_n(fNegRights, kMaxRectsInList, infinity);
77 std::fill_n(fNegBots, kMaxRectsInList, infinity);
78 }
79
popAll()80 void popAll() {
81 fNumRects = 0;
82 fSplittableBounds = -std::numeric_limits<float>::infinity();
83 fRectValsSum = 0;
84 // Leave the rect arrays untouched. Since we know they are either already valid in the tree,
85 // or else maximally negative, this allows the future list to check for intersection beyond
86 // fNumRects without failing.
87 }
88
intersects(Rect rect)89 bool intersects(Rect rect) override {
90 // Test for intersection in sets of 4. Since all the data in our rect arrays is either
91 // maximally negative, or valid from somewhere else in the tree, we can test beyond
92 // fNumRects without failing.
93 static_assert(kMaxRectsInList % 4 == 0);
94 SkASSERT(fNumRects <= kMaxRectsInList);
95 auto comp = Rect::ComplementRect(rect).fVals;
96 for (int i = 0; i < fNumRects; i += 4) {
97 auto l = skvx::float4::Load(fLefts + i);
98 auto t = skvx::float4::Load(fTops + i);
99 auto nr = skvx::float4::Load(fNegRights + i);
100 auto nb = skvx::float4::Load(fNegBots + i);
101 if (any((l < comp[0]) &
102 (t < comp[1]) &
103 (nr < comp[2]) &
104 (nb < comp[3]))) {
105 return true;
106 }
107 }
108 return false;
109 }
110
addNonIntersecting(Rect rect,SkArenaAlloc * arena)111 Node* addNonIntersecting(Rect rect, SkArenaAlloc* arena) override {
112 if (fNumRects == kMaxRectsInList) {
113 // The new rect doesn't fit. Split our rect list first and then add.
114 return this->split(arena)->addNonIntersecting(rect, arena);
115 }
116 this->appendToList(rect);
117 return this;
118 }
119
120 private:
appendToList(Rect rect)121 void appendToList(Rect rect) {
122 SkASSERT(fNumRects < kMaxRectsInList);
123 int i = fNumRects++;
124 // [maxLeft, maxTop, -minRight, -minBot]
125 fSplittableBounds = max(fSplittableBounds, rect.vals());
126 fRectValsSum += rect.vals(); // [sum(left), sum(top), -sum(right), -sum(bot)]
127 fLefts[i] = rect.vals()[0];
128 fTops[i] = rect.vals()[1];
129 fNegRights[i] = rect.vals()[2];
130 fNegBots[i] = rect.vals()[3];
131 }
132
loadRect(int i) const133 Rect loadRect(int i) const {
134 return Rect::FromVals({fLefts[i], fTops[i], fNegRights[i], fNegBots[i]});
135 }
136
137 // Splits this node with a new LeafNode, then returns a TreeNode that reuses our "this" pointer
138 // along with the new node.
split(SkArenaAlloc * arena)139 IntersectionTree::Node* split(SkArenaAlloc* arena) {
140 // This should only get called when our list is full.
141 SkASSERT(fNumRects == kMaxRectsInList);
142
143 // Since rects cannot overlap, there will always be a split that places at least one pairing
144 // of rects on opposite sides. The region:
145 //
146 // fSplittableBounds == [maxLeft, maxTop, -minRight, -minBot] == [r, b, -l, -t]
147 //
148 // Represents the region of splits that guarantee a strict subdivision of our rect list.
149 auto splittableSize = fSplittableBounds.xy() + fSplittableBounds.zw(); // == [r-l, b-t]
150 SkASSERT(max(splittableSize) >= 0);
151 SplitType splitType = (splittableSize.x() > splittableSize.y()) ? SplitType::kX
152 : SplitType::kY;
153
154 float splitCoord;
155 const float *loVals, *negHiVals;
156 if (splitType == SplitType::kX) {
157 // Split horizontally, at the geometric midpoint if it falls within the splittable
158 // bounds.
159 splitCoord = (fRectValsSum.x() - fRectValsSum.z()) * (.5f/kMaxRectsInList);
160 splitCoord = SkTPin(splitCoord, -fSplittableBounds.z(), fSplittableBounds.x());
161 loVals = fLefts;
162 negHiVals = fNegRights;
163 } else {
164 // Split vertically, at the geometric midpoint if it falls within the splittable bounds.
165 splitCoord = (fRectValsSum.y() - fRectValsSum.w()) * (.5f/kMaxRectsInList);
166 splitCoord = SkTPin(splitCoord, -fSplittableBounds.w(), fSplittableBounds.y());
167 loVals = fTops;
168 negHiVals = fNegBots;
169 }
170
171 // Split "this", leaving all rects below "splitCoord" in this, and placing all rects above
172 // splitCoord in "hiNode". There may be some reduncancy between lists, but we made sure to
173 // select a split that would leave both lists strictly smaller than the original.
174 LeafNode* hiNode = arena->make<LeafNode>();
175 int numCombinedRects = fNumRects;
176 float negSplitCoord = -splitCoord;
177 this->popAll();
178 for (int i = 0; i < numCombinedRects; ++i) {
179 Rect rect = this->loadRect(i);
180 if (loVals[i] < splitCoord) {
181 this->appendToList(rect);
182 }
183 if (negHiVals[i] < negSplitCoord) {
184 hiNode->appendToList(rect);
185 }
186 }
187
188 SkASSERT(0 < fNumRects && fNumRects < numCombinedRects);
189 SkASSERT(0 < hiNode->fNumRects && hiNode->fNumRects < numCombinedRects);
190
191 return (splitType == SplitType::kX)
192 ? (Node*)arena->make<TreeNode<SplitType::kX>>(splitCoord, this, hiNode)
193 : (Node*)arena->make<TreeNode<SplitType::kY>>(splitCoord, this, hiNode);
194 }
195
196 int fNumRects;
197 skvx::float4 fSplittableBounds; // [maxLeft, maxTop, -minRight, -minBot]
198 skvx::float4 fRectValsSum; // [sum(left), sum(top), -sum(right), -sum(bot)]
199 alignas(Rect) float fLefts[kMaxRectsInList];
200 alignas(Rect) float fTops[kMaxRectsInList];
201 alignas(Rect) float fNegRights[kMaxRectsInList];
202 alignas(Rect) float fNegBots[kMaxRectsInList];
203 static_assert((kMaxRectsInList * sizeof(float)) % sizeof(Rect) == 0);
204 };
205
IntersectionTree()206 IntersectionTree::IntersectionTree()
207 : fRoot(fArena.make<LeafNode>()) {
208 static_assert(kTreeNodeSize == sizeof(TreeNode<SplitType::kX>));
209 static_assert(kTreeNodeSize == sizeof(TreeNode<SplitType::kY>));
210 static_assert(kLeafNodeSize == sizeof(LeafNode));
211 }
212
213 } // namespace skgpu::graphite
214