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