• 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> fRects{16, SkBlockAllocator::GrowthPolicy::kFibonacci};
117     SkTBlockList<CompressedPaintersOrder> fOrders{16, 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 
MakeRes(const SkISize & deviceSize,int gridCellSize)137     static std::unique_ptr<GridBoundsManager> MakeRes(const SkISize& deviceSize, int gridCellSize) {
138         SkASSERT(deviceSize.width() > 0 && deviceSize.height() > 0);
139         SkASSERT(gridCellSize >= 1);
140 
141         int gridWidth = SkScalarCeilToInt(deviceSize.width() / (float) gridCellSize);
142         int gridHeight = SkScalarCeilToInt(deviceSize.height() / (float) gridCellSize);
143 
144         // This keeps the grid cells exactly at the requested resolution, but pads the right and
145         // bottom edges out to a multiple of the cell size. The alternative is pass in the unpadded
146         // device size, which would mean the actual cell size will be smaller than the requested
147         // (by (deviceSize % gridCellSize)/gridDims).
148         SkISize paddedDeviceSize = {gridWidth * gridCellSize, gridHeight * gridCellSize};
149         return Make(paddedDeviceSize, {gridWidth, gridHeight});
150     }
151 
~GridBoundsManager()152     ~GridBoundsManager() override {}
153 
154 
getMostRecentDraw(const Rect & bounds)155     CompressedPaintersOrder getMostRecentDraw(const Rect& bounds) const override {
156         SkASSERT(!bounds.isEmptyNegativeOrNaN());
157 
158         auto ltrb = this->getGridCoords(bounds);
159         const CompressedPaintersOrder* p = fNodes.data() + ltrb[1] * fGridWidth + ltrb[0];
160         int h = ltrb[3] - ltrb[1];
161         int w = ltrb[2] - ltrb[0];
162 
163         CompressedPaintersOrder max = CompressedPaintersOrder::First();
164         for (int y = 0; y <= h; ++y ) {
165             for (int x = 0; x <= w; ++x) {
166                 CompressedPaintersOrder v = *(p + x);
167                 if (v > max) {
168                     max = v;
169                 }
170             }
171             p = p + fGridWidth;
172         }
173 
174         return max;
175     }
176 
recordDraw(const Rect & bounds,CompressedPaintersOrder order)177     void recordDraw(const Rect& bounds, CompressedPaintersOrder order) override {
178         SkASSERT(!bounds.isEmptyNegativeOrNaN());
179 
180         auto ltrb = this->getGridCoords(bounds);
181         CompressedPaintersOrder* p = fNodes.data() + ltrb[1] * fGridWidth + ltrb[0];
182         int h = ltrb[3] - ltrb[1];
183         int w = ltrb[2] - ltrb[0];
184 
185         for (int y = 0; y <= h; ++y) {
186             for (int x = 0; x <= w; ++x) {
187                 CompressedPaintersOrder v = *(p + x);
188                 if (order > v) {
189                     *(p + x) = order;
190                 }
191             }
192             p = p + fGridWidth;
193         }
194     }
195 
reset()196     void reset() override {
197         memset(fNodes.data(), 0, sizeof(CompressedPaintersOrder) * fGridWidth * fGridHeight);
198     }
199 
200 private:
GridBoundsManager(const SkISize & deviceSize,const SkISize & gridSize)201     GridBoundsManager(const SkISize& deviceSize, const SkISize& gridSize)
202             : fScaleX(gridSize.width() / (float) deviceSize.width())
203             , fScaleY(gridSize.height() / (float) deviceSize.height())
204             , fGridWidth(gridSize.width())
205             , fGridHeight(gridSize.height())
206             , fNodes((size_t) fGridWidth * fGridHeight) {
207         // Reset is needed to zero-out the uninitialized fNodes values.
208         this->reset();
209     }
210 
getGridCoords(const Rect & bounds)211     skvx::int4 getGridCoords(const Rect& bounds) const {
212         // Normalize bounds by 1/wh of device bounds, then scale up to number of cells per side.
213         // fScaleXY includes both 1/wh and the grid dimension scaling, then clamp to [0, gridDim-1].
214         return pin(skvx::cast<int32_t>(bounds.ltrb() * skvx::float2(fScaleX, fScaleY).xyxy()),
215                    skvx::int4(0),
216                    skvx::int2(fGridWidth, fGridHeight).xyxy() - 1);
217     }
218 
219     const float fScaleX;
220     const float fScaleY;
221 
222     const int   fGridWidth;
223     const int   fGridHeight;
224 
225     skia_private::AutoTMalloc<CompressedPaintersOrder> fNodes;
226 };
227 
228 // A BoundsManager that first relies on BruteForceBoundsManager for N draw calls, and then switches
229 // to the GridBoundsManager if it exceeds its limit. For low N, the brute force approach is
230 // surprisingly efficient, has the highest accuracy, and very low memory overhead. Once the draw
231 // call count is large enough, the grid's lower performance complexity outweigh its memory cost and
232 // reduced accuracy.
233 class HybridBoundsManager : public BoundsManager {
234 public:
HybridBoundsManager(const SkISize & deviceSize,int gridCellSize,int maxBruteForceN)235     HybridBoundsManager(const SkISize& deviceSize,
236                         int gridCellSize,
237                         int maxBruteForceN)
238             : fDeviceSize(deviceSize)
239             , fGridCellSize(gridCellSize)
240             , fMaxBruteForceN(maxBruteForceN)
241             , fCurrentManager(&fBruteForceManager) {
242         SkASSERT(deviceSize.width() >= 1 && deviceSize.height() >= 1 &&
243                  gridCellSize >= 1 && maxBruteForceN >= 1);
244     }
245 
getMostRecentDraw(const Rect & bounds)246     CompressedPaintersOrder getMostRecentDraw(const Rect& bounds) const override {
247         return fCurrentManager->getMostRecentDraw(bounds);
248     }
249 
recordDraw(const Rect & bounds,CompressedPaintersOrder order)250     void recordDraw(const Rect& bounds, CompressedPaintersOrder order) override {
251         this->updateCurrentManagerIfNeeded();
252         fCurrentManager->recordDraw(bounds, order);
253     }
254 
reset()255     void reset() override {
256         const bool usedGrid = fCurrentManager == fGridManager.get();
257         if (usedGrid) {
258             // Reset the grid manager so it's ready to use next frame, but don't delete it.
259             fGridManager->reset();
260             // Assume brute force manager was reset when we swapped to the grid originally
261             fCurrentManager = &fBruteForceManager;
262         } else {
263             if (fGridManager) {
264                 // Clean up the grid manager that was created over a frame ago without being used.
265                 // This could lead to re-allocating the grid every-other frame, but it's a simple
266                 // way to ensure we don't hold onto the grid in perpetuity if it's not needed.
267                 fGridManager = nullptr;
268             }
269             fBruteForceManager.reset();
270             SkASSERT(fCurrentManager == &fBruteForceManager);
271         }
272     }
273 
274 private:
275     const SkISize fDeviceSize;
276     const int     fGridCellSize;
277     const int     fMaxBruteForceN;
278 
279     BoundsManager* fCurrentManager;
280 
281     BruteForceBoundsManager                  fBruteForceManager;
282 
283     // The grid manager starts out null and is created the first time we exceed fMaxBruteForceN.
284     // However, even if we reset back to the brute force manager, we keep the grid around under the
285     // assumption that the owning Device will have similar frame-to-frame draw counts and will need
286     // to upgrade to the grid manager again.
287     std::unique_ptr<GridBoundsManager>       fGridManager;
288 
updateCurrentManagerIfNeeded()289     void updateCurrentManagerIfNeeded() {
290         if (fCurrentManager == fGridManager.get() ||
291             fBruteForceManager.count() < fMaxBruteForceN) {
292             // Already using the grid or the about-to-be-recorded draw will not cause us to exceed
293             // the brute force limit, so no need to change the current manager implementation.
294             return;
295         }
296         // Else we need to switch from the brute force manager to the grid manager
297         if (!fGridManager) {
298             fGridManager = GridBoundsManager::MakeRes(fDeviceSize, fGridCellSize);
299         }
300         fCurrentManager = fGridManager.get();
301 
302         // Fill out the grid manager with the recorded draws in the brute force manager
303         fBruteForceManager.replayDraws(fCurrentManager);
304         fBruteForceManager.reset();
305     }
306 };
307 
308 } // namespace skgpu::graphite
309 
310 #endif // skgpu_graphite_geom_BoundsManager_DEFINED
311