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