• 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 #ifndef skgpu_graphite_geom_BoundsManager_DEFINED
9 #define skgpu_graphite_geom_BoundsManager_DEFINED
10 
11 #include "include/core/SkSize.h"
12 #include "include/private/base/SkTemplates.h"
13 
14 #include "src/base/SkTBlockList.h"
15 #include "src/base/SkVx.h"
16 #include "src/gpu/graphite/DrawOrder.h"
17 #include "src/gpu/graphite/geom/Rect.h"
18 
19 #include <cstdint>
20 
21 namespace skgpu::graphite {
22 
23 /**
24  * BoundsManager is an acceleration structure for device-space related pixel bounds queries.
25  * The BoundsManager tracks a single ordinal value per bounds: the CompressedPaintersOrder of a draw
26  * The CompressedPaintersOrder enforces a specific submission order of draws to the GPU but can
27  * re-arrange draws out of their original painter's order if the GREATER depth test and the draw's Z
28  * value resolve out-of-order rendering.
29  *
30  * It supports querying the most recent draw intersecting a bounding rect (represented as a
31  * CompressedPaintersOrder value), and recording a (bounds, CompressedPaintersOrder) pair.
32  */
33 class BoundsManager {
34 public:
~BoundsManager()35     virtual ~BoundsManager() {}
36 
37     virtual CompressedPaintersOrder getMostRecentDraw(const Rect& bounds) const = 0;
38 
39     virtual void recordDraw(const Rect& bounds, CompressedPaintersOrder order) = 0;
40 
41     virtual void reset() = 0;
42 };
43 
44 // TODO: Select one most-effective BoundsManager implementation, make it the only option, and remove
45 // virtual-ness. For now, this seems useful for correctness testing by comparing against trivial
46 // implementations and for identifying how much "smarts" are actually worthwhile.
47 
48 // A BoundsManager that produces exact painter's order and assumes nothing is occluded.
49 class NaiveBoundsManager final : public BoundsManager {
50 public:
~NaiveBoundsManager()51     ~NaiveBoundsManager() override {}
52 
getMostRecentDraw(const Rect & bounds)53     CompressedPaintersOrder getMostRecentDraw(const Rect& bounds) const override {
54         return fLatestDraw;
55     }
56 
57 
recordDraw(const Rect & bounds,CompressedPaintersOrder order)58     void recordDraw(const Rect& bounds, CompressedPaintersOrder order) override {
59         if (fLatestDraw < order) {
60             fLatestDraw = order;
61         }
62     }
63 
reset()64     void reset() override {
65         fLatestDraw = CompressedPaintersOrder::First();
66     }
67 
68 private:
69     CompressedPaintersOrder fLatestDraw = CompressedPaintersOrder::First();
70 };
71 
72 // A BoundsManager that tracks every draw and can exactly determine all queries
73 // using a brute force search.
74 class BruteForceBoundsManager : public BoundsManager {
75 public:
~BruteForceBoundsManager()76     ~BruteForceBoundsManager() override {}
77 
getMostRecentDraw(const Rect & bounds)78     CompressedPaintersOrder getMostRecentDraw(const Rect& bounds) const override {
79         SkASSERT(fRects.count() == fOrders.count());
80 
81         Rect::ComplementRect boundsComplement(bounds);
82         CompressedPaintersOrder max = CompressedPaintersOrder::First();
83         auto orderIter = fOrders.items().begin();
84         for (const Rect& r : fRects.items()) {
85             if (r.intersects(boundsComplement) && max < *orderIter) {
86                 max = *orderIter;
87             }
88             ++orderIter;
89         }
90         return max;
91     }
92 
recordDraw(const Rect & bounds,CompressedPaintersOrder order)93     void recordDraw(const Rect& bounds, CompressedPaintersOrder order) override {
94         fRects.push_back(bounds);
95         fOrders.push_back(order);
96     }
97 
reset()98     void reset() override {
99         fRects.reset();
100         fOrders.reset();
101     }
102 
count()103     int count() const { return fRects.count(); }
104 
replayDraws(BoundsManager * manager)105     void replayDraws(BoundsManager* manager) const {
106         auto orderIter = fOrders.items().begin();
107         for (const Rect& r : fRects.items()) {
108             manager->recordDraw(r, *orderIter);
109             ++orderIter;
110         }
111     }
112 
113 private:
114     // fRects and fOrders are parallel, but kept separate to avoid wasting padding since Rect is
115     // an over-aligned type.
116     SkTBlockList<Rect, 16> fRects{SkBlockAllocator::GrowthPolicy::kFibonacci};
117     SkTBlockList<CompressedPaintersOrder, 16> fOrders{SkBlockAllocator::GrowthPolicy::kFibonacci};
118 };
119 
120 // A BoundsManager that tracks highest CompressedPaintersOrder over a uniform spatial grid.
121 class GridBoundsManager : public BoundsManager {
122 public:
123     // 'gridSize' is the number of cells in the X and Y directions, splitting the pixels from [0,0]
124     // to 'deviceSize' into uniformly-sized cells.
Make(const SkISize & deviceSize,const SkISize & gridSize)125     static std::unique_ptr<GridBoundsManager> Make(const SkISize& deviceSize,
126                                                    const SkISize& gridSize) {
127         SkASSERT(deviceSize.width() > 0 && deviceSize.height() > 0);
128         SkASSERT(gridSize.width() >= 1 && gridSize.height() >= 1);
129 
130         return std::unique_ptr<GridBoundsManager>(new GridBoundsManager(deviceSize, gridSize));
131     }
132 
Make(const SkISize & deviceSize,int gridSize)133     static std::unique_ptr<GridBoundsManager> Make(const SkISize& deviceSize, int gridSize) {
134         return Make(deviceSize, {gridSize, gridSize});
135     }
136 
137     static std::unique_ptr<GridBoundsManager> MakeRes(SkISize deviceSize,
138                                                       int gridCellSize,
139                                                       int maxGridSize=0) {
140         SkASSERT(deviceSize.width() > 0 && deviceSize.height() > 0);
141         SkASSERT(gridCellSize >= 1);
142 
143         int gridWidth = SkScalarCeilToInt(deviceSize.width() / (float) gridCellSize);
144         if (maxGridSize > 0 && gridWidth > maxGridSize) {
145             // We'd have too many sizes so clamp the grid resolution, leave the device size alone
146             // since the grid cell size can't be preserved anyways.
147             gridWidth = maxGridSize;
148         } else {
149              // Pad out the device size to keep cell size the same
150             deviceSize.fWidth = gridWidth * gridCellSize;
151         }
152 
153         int gridHeight = SkScalarCeilToInt(deviceSize.height() / (float) gridCellSize);
154         if (maxGridSize > 0 && gridHeight > maxGridSize) {
155             gridHeight = maxGridSize;
156         } else {
157             deviceSize.fHeight = gridHeight * gridCellSize;
158         }
159         return Make(deviceSize, {gridWidth, gridHeight});
160     }
161 
~GridBoundsManager()162     ~GridBoundsManager() override {}
163 
164 
getMostRecentDraw(const Rect & bounds)165     CompressedPaintersOrder getMostRecentDraw(const Rect& bounds) const override {
166         SkASSERT(!bounds.isEmptyNegativeOrNaN());
167 
168         auto ltrb = this->getGridCoords(bounds);
169         const CompressedPaintersOrder* p = fNodes.data() + ltrb[1] * fGridWidth + ltrb[0];
170         int h = ltrb[3] - ltrb[1];
171         int w = ltrb[2] - ltrb[0];
172 
173         CompressedPaintersOrder max = CompressedPaintersOrder::First();
174         for (int y = 0; y <= h; ++y ) {
175             for (int x = 0; x <= w; ++x) {
176                 CompressedPaintersOrder v = *(p + x);
177                 if (v > max) {
178                     max = v;
179                 }
180             }
181             p = p + fGridWidth;
182         }
183 
184         return max;
185     }
186 
recordDraw(const Rect & bounds,CompressedPaintersOrder order)187     void recordDraw(const Rect& bounds, CompressedPaintersOrder order) override {
188         SkASSERT(!bounds.isEmptyNegativeOrNaN());
189 
190         auto ltrb = this->getGridCoords(bounds);
191         CompressedPaintersOrder* p = fNodes.data() + ltrb[1] * fGridWidth + ltrb[0];
192         int h = ltrb[3] - ltrb[1];
193         int w = ltrb[2] - ltrb[0];
194 
195         for (int y = 0; y <= h; ++y) {
196             for (int x = 0; x <= w; ++x) {
197                 CompressedPaintersOrder v = *(p + x);
198                 if (order > v) {
199                     *(p + x) = order;
200                 }
201             }
202             p = p + fGridWidth;
203         }
204     }
205 
reset()206     void reset() override {
207         memset(fNodes.data(), 0, sizeof(CompressedPaintersOrder) * fGridWidth * fGridHeight);
208     }
209 
210 private:
GridBoundsManager(const SkISize & deviceSize,const SkISize & gridSize)211     GridBoundsManager(const SkISize& deviceSize, const SkISize& gridSize)
212             : fScaleX(gridSize.width() / (float) deviceSize.width())
213             , fScaleY(gridSize.height() / (float) deviceSize.height())
214             , fGridWidth(gridSize.width())
215             , fGridHeight(gridSize.height())
216             , fNodes((size_t) fGridWidth * fGridHeight) {
217         // Reset is needed to zero-out the uninitialized fNodes values.
218         this->reset();
219     }
220 
getGridCoords(const Rect & bounds)221     skvx::int4 getGridCoords(const Rect& bounds) const {
222         // Normalize bounds by 1/wh of device bounds, then scale up to number of cells per side.
223         // fScaleXY includes both 1/wh and the grid dimension scaling, then clamp to [0, gridDim-1].
224         return pin(skvx::cast<int32_t>(bounds.ltrb() * skvx::float2(fScaleX, fScaleY).xyxy()),
225                    skvx::int4(0),
226                    skvx::int2(fGridWidth, fGridHeight).xyxy() - 1);
227     }
228 
229     const float fScaleX;
230     const float fScaleY;
231 
232     const int   fGridWidth;
233     const int   fGridHeight;
234 
235     skia_private::AutoTMalloc<CompressedPaintersOrder> fNodes;
236 };
237 
238 // A BoundsManager that first relies on BruteForceBoundsManager for N draw calls, and then switches
239 // to the GridBoundsManager if it exceeds its limit. For low N, the brute force approach is
240 // surprisingly efficient, has the highest accuracy, and very low memory overhead. Once the draw
241 // call count is large enough, the grid's lower performance complexity outweigh its memory cost and
242 // reduced accuracy.
243 class HybridBoundsManager : public BoundsManager {
244 public:
245     HybridBoundsManager(const SkISize& deviceSize,
246                         int gridCellSize,
247                         int maxBruteForceN,
248                         int maxGridSize=0)
fDeviceSize(deviceSize)249             : fDeviceSize(deviceSize)
250             , fGridCellSize(gridCellSize)
251             , fMaxBruteForceN(maxBruteForceN)
252             , fMaxGridSize(maxGridSize)
253             , fCurrentManager(&fBruteForceManager) {
254         SkASSERT(deviceSize.width() >= 1 && deviceSize.height() >= 1 &&
255                  gridCellSize >= 1 && maxBruteForceN >= 1);
256     }
257 
getMostRecentDraw(const Rect & bounds)258     CompressedPaintersOrder getMostRecentDraw(const Rect& bounds) const override {
259         return fCurrentManager->getMostRecentDraw(bounds);
260     }
261 
recordDraw(const Rect & bounds,CompressedPaintersOrder order)262     void recordDraw(const Rect& bounds, CompressedPaintersOrder order) override {
263         this->updateCurrentManagerIfNeeded();
264         fCurrentManager->recordDraw(bounds, order);
265     }
266 
reset()267     void reset() override {
268         const bool usedGrid = fCurrentManager == fGridManager.get();
269         if (usedGrid) {
270             // Reset the grid manager so it's ready to use next frame, but don't delete it.
271             fGridManager->reset();
272             // Assume brute force manager was reset when we swapped to the grid originally
273             fCurrentManager = &fBruteForceManager;
274         } else {
275             if (fGridManager) {
276                 // Clean up the grid manager that was created over a frame ago without being used.
277                 // This could lead to re-allocating the grid every-other frame, but it's a simple
278                 // way to ensure we don't hold onto the grid in perpetuity if it's not needed.
279                 fGridManager = nullptr;
280             }
281             fBruteForceManager.reset();
282             SkASSERT(fCurrentManager == &fBruteForceManager);
283         }
284     }
285 
286 private:
287     const SkISize fDeviceSize;
288     const int     fGridCellSize;
289     const int     fMaxBruteForceN;
290     const int     fMaxGridSize;
291 
292     BoundsManager* fCurrentManager;
293 
294     BruteForceBoundsManager                  fBruteForceManager;
295 
296     // The grid manager starts out null and is created the first time we exceed fMaxBruteForceN.
297     // However, even if we reset back to the brute force manager, we keep the grid around under the
298     // assumption that the owning Device will have similar frame-to-frame draw counts and will need
299     // to upgrade to the grid manager again.
300     std::unique_ptr<GridBoundsManager>       fGridManager;
301 
updateCurrentManagerIfNeeded()302     void updateCurrentManagerIfNeeded() {
303         if (fCurrentManager == fGridManager.get() ||
304             fBruteForceManager.count() < fMaxBruteForceN) {
305             // Already using the grid or the about-to-be-recorded draw will not cause us to exceed
306             // the brute force limit, so no need to change the current manager implementation.
307             return;
308         }
309         // Else we need to switch from the brute force manager to the grid manager
310         if (!fGridManager) {
311             fGridManager = GridBoundsManager::MakeRes(fDeviceSize, fGridCellSize, fMaxGridSize);
312         }
313         fCurrentManager = fGridManager.get();
314 
315         // Fill out the grid manager with the recorded draws in the brute force manager
316         fBruteForceManager.replayDraws(fCurrentManager);
317         fBruteForceManager.reset();
318     }
319 };
320 
321 } // namespace skgpu::graphite
322 
323 #endif // skgpu_graphite_geom_BoundsManager_DEFINED
324