/src/skia/src/gpu/graphite/geom/BoundsManager.h
Line | Count | Source (jump to first uncovered line) |
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: |
35 | 0 | 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: |
51 | 0 | ~NaiveBoundsManager() override {} |
52 | | |
53 | 0 | CompressedPaintersOrder getMostRecentDraw(const Rect& bounds) const override { |
54 | 0 | return fLatestDraw; |
55 | 0 | } |
56 | | |
57 | | |
58 | 0 | void recordDraw(const Rect& bounds, CompressedPaintersOrder order) override { |
59 | 0 | if (fLatestDraw < order) { |
60 | 0 | fLatestDraw = order; |
61 | 0 | } |
62 | 0 | } |
63 | | |
64 | 0 | void reset() override { |
65 | 0 | fLatestDraw = CompressedPaintersOrder::First(); |
66 | 0 | } |
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: |
76 | 0 | ~BruteForceBoundsManager() override {} |
77 | | |
78 | 0 | CompressedPaintersOrder getMostRecentDraw(const Rect& bounds) const override { |
79 | 0 | SkASSERT(fRects.count() == fOrders.count()); |
80 | |
|
81 | 0 | Rect::ComplementRect boundsComplement(bounds); |
82 | 0 | CompressedPaintersOrder max = CompressedPaintersOrder::First(); |
83 | 0 | auto orderIter = fOrders.items().begin(); |
84 | 0 | for (const Rect& r : fRects.items()) { |
85 | 0 | if (r.intersects(boundsComplement) && max < *orderIter) { |
86 | 0 | max = *orderIter; |
87 | 0 | } |
88 | 0 | ++orderIter; |
89 | 0 | } |
90 | 0 | return max; |
91 | 0 | } Unexecuted instantiation: skgpu::graphite::BruteForceBoundsManager::getMostRecentDraw(skgpu::graphite::Rect const&) const Unexecuted instantiation: skgpu::graphite::BruteForceBoundsManager::getMostRecentDraw(skgpu::graphite::Rect const&) const |
92 | | |
93 | 0 | void recordDraw(const Rect& bounds, CompressedPaintersOrder order) override { |
94 | 0 | fRects.push_back(bounds); |
95 | 0 | fOrders.push_back(order); |
96 | 0 | } |
97 | | |
98 | 0 | void reset() override { |
99 | 0 | fRects.reset(); |
100 | 0 | fOrders.reset(); |
101 | 0 | } |
102 | | |
103 | 0 | int count() const { return fRects.count(); } |
104 | | |
105 | 0 | void replayDraws(BoundsManager* manager) const { |
106 | 0 | auto orderIter = fOrders.items().begin(); |
107 | 0 | for (const Rect& r : fRects.items()) { |
108 | 0 | manager->recordDraw(r, *orderIter); |
109 | 0 | ++orderIter; |
110 | 0 | } |
111 | 0 | } |
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. |
125 | | static std::unique_ptr<GridBoundsManager> Make(const SkISize& deviceSize, |
126 | 0 | const SkISize& gridSize) { |
127 | 0 | SkASSERT(deviceSize.width() > 0 && deviceSize.height() > 0); |
128 | 0 | SkASSERT(gridSize.width() >= 1 && gridSize.height() >= 1); |
129 | |
|
130 | 0 | return std::unique_ptr<GridBoundsManager>(new GridBoundsManager(deviceSize, gridSize)); |
131 | 0 | } Unexecuted instantiation: skgpu::graphite::GridBoundsManager::Make(SkISize const&, SkISize const&) Unexecuted instantiation: skgpu::graphite::GridBoundsManager::Make(SkISize const&, SkISize const&) |
132 | | |
133 | 0 | static std::unique_ptr<GridBoundsManager> Make(const SkISize& deviceSize, int gridSize) { |
134 | 0 | return Make(deviceSize, {gridSize, gridSize}); |
135 | 0 | } |
136 | | |
137 | | static std::unique_ptr<GridBoundsManager> MakeRes(SkISize deviceSize, |
138 | | int gridCellSize, |
139 | 0 | int maxGridSize=0) { |
140 | 0 | SkASSERT(deviceSize.width() > 0 && deviceSize.height() > 0); |
141 | 0 | SkASSERT(gridCellSize >= 1); |
142 | |
|
143 | 0 | int gridWidth = SkScalarCeilToInt(deviceSize.width() / (float) gridCellSize); |
144 | 0 | 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 | 0 | gridWidth = maxGridSize; |
148 | 0 | } else { |
149 | | // Pad out the device size to keep cell size the same |
150 | 0 | deviceSize.fWidth = gridWidth * gridCellSize; |
151 | 0 | } |
152 | |
|
153 | 0 | int gridHeight = SkScalarCeilToInt(deviceSize.height() / (float) gridCellSize); |
154 | 0 | if (maxGridSize > 0 && gridHeight > maxGridSize) { |
155 | 0 | gridHeight = maxGridSize; |
156 | 0 | } else { |
157 | 0 | deviceSize.fHeight = gridHeight * gridCellSize; |
158 | 0 | } |
159 | 0 | return Make(deviceSize, {gridWidth, gridHeight}); |
160 | 0 | } Unexecuted instantiation: skgpu::graphite::GridBoundsManager::MakeRes(SkISize, int, int) Unexecuted instantiation: skgpu::graphite::GridBoundsManager::MakeRes(SkISize, int, int) |
161 | | |
162 | 0 | ~GridBoundsManager() override {} |
163 | | |
164 | | |
165 | 0 | CompressedPaintersOrder getMostRecentDraw(const Rect& bounds) const override { |
166 | 0 | SkASSERT(!bounds.isEmptyNegativeOrNaN()); |
167 | |
|
168 | 0 | auto ltrb = this->getGridCoords(bounds); |
169 | 0 | const CompressedPaintersOrder* p = fNodes.data() + ltrb[1] * fGridWidth + ltrb[0]; |
170 | 0 | int h = ltrb[3] - ltrb[1]; |
171 | 0 | int w = ltrb[2] - ltrb[0]; |
172 | |
|
173 | 0 | CompressedPaintersOrder max = CompressedPaintersOrder::First(); |
174 | 0 | for (int y = 0; y <= h; ++y ) { |
175 | 0 | for (int x = 0; x <= w; ++x) { |
176 | 0 | CompressedPaintersOrder v = *(p + x); |
177 | 0 | if (v > max) { |
178 | 0 | max = v; |
179 | 0 | } |
180 | 0 | } |
181 | 0 | p = p + fGridWidth; |
182 | 0 | } |
183 | |
|
184 | 0 | return max; |
185 | 0 | } Unexecuted instantiation: skgpu::graphite::GridBoundsManager::getMostRecentDraw(skgpu::graphite::Rect const&) const Unexecuted instantiation: skgpu::graphite::GridBoundsManager::getMostRecentDraw(skgpu::graphite::Rect const&) const |
186 | | |
187 | 0 | void recordDraw(const Rect& bounds, CompressedPaintersOrder order) override { |
188 | 0 | SkASSERT(!bounds.isEmptyNegativeOrNaN()); |
189 | |
|
190 | 0 | auto ltrb = this->getGridCoords(bounds); |
191 | 0 | CompressedPaintersOrder* p = fNodes.data() + ltrb[1] * fGridWidth + ltrb[0]; |
192 | 0 | int h = ltrb[3] - ltrb[1]; |
193 | 0 | int w = ltrb[2] - ltrb[0]; |
194 | |
|
195 | 0 | for (int y = 0; y <= h; ++y) { |
196 | 0 | for (int x = 0; x <= w; ++x) { |
197 | 0 | CompressedPaintersOrder v = *(p + x); |
198 | 0 | if (order > v) { |
199 | 0 | *(p + x) = order; |
200 | 0 | } |
201 | 0 | } |
202 | 0 | p = p + fGridWidth; |
203 | 0 | } |
204 | 0 | } Unexecuted instantiation: skgpu::graphite::GridBoundsManager::recordDraw(skgpu::graphite::Rect const&, skgpu::graphite::MonotonicValue<skgpu::graphite::CompressedPaintersOrderSequence>) Unexecuted instantiation: skgpu::graphite::GridBoundsManager::recordDraw(skgpu::graphite::Rect const&, skgpu::graphite::MonotonicValue<skgpu::graphite::CompressedPaintersOrderSequence>) |
205 | | |
206 | 0 | void reset() override { |
207 | 0 | memset(fNodes.data(), 0, sizeof(CompressedPaintersOrder) * fGridWidth * fGridHeight); |
208 | 0 | } |
209 | | |
210 | | private: |
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 | 0 | , fNodes((size_t) fGridWidth * fGridHeight) { |
217 | | // Reset is needed to zero-out the uninitialized fNodes values. |
218 | 0 | this->reset(); |
219 | 0 | } |
220 | | |
221 | 0 | 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 | 0 | return pin(skvx::cast<int32_t>(bounds.ltrb() * skvx::float2(fScaleX, fScaleY).xyxy()), |
225 | 0 | skvx::int4(0), |
226 | 0 | skvx::int2(fGridWidth, fGridHeight).xyxy() - 1); |
227 | 0 | } |
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) |
249 | | : fDeviceSize(deviceSize) |
250 | | , fGridCellSize(gridCellSize) |
251 | | , fMaxBruteForceN(maxBruteForceN) |
252 | | , fMaxGridSize(maxGridSize) |
253 | 0 | , fCurrentManager(&fBruteForceManager) { |
254 | 0 | SkASSERT(deviceSize.width() >= 1 && deviceSize.height() >= 1 && |
255 | 0 | gridCellSize >= 1 && maxBruteForceN >= 1); |
256 | 0 | } Unexecuted instantiation: skgpu::graphite::HybridBoundsManager::HybridBoundsManager(SkISize const&, int, int, int) Unexecuted instantiation: skgpu::graphite::HybridBoundsManager::HybridBoundsManager(SkISize const&, int, int, int) |
257 | | |
258 | 0 | CompressedPaintersOrder getMostRecentDraw(const Rect& bounds) const override { |
259 | 0 | return fCurrentManager->getMostRecentDraw(bounds); |
260 | 0 | } |
261 | | |
262 | 0 | void recordDraw(const Rect& bounds, CompressedPaintersOrder order) override { |
263 | 0 | this->updateCurrentManagerIfNeeded(); |
264 | 0 | fCurrentManager->recordDraw(bounds, order); |
265 | 0 | } |
266 | | |
267 | 0 | void reset() override { |
268 | 0 | const bool usedGrid = fCurrentManager == fGridManager.get(); |
269 | 0 | if (usedGrid) { |
270 | | // Reset the grid manager so it's ready to use next frame, but don't delete it. |
271 | 0 | fGridManager->reset(); |
272 | | // Assume brute force manager was reset when we swapped to the grid originally |
273 | 0 | fCurrentManager = &fBruteForceManager; |
274 | 0 | } else { |
275 | 0 | 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 | 0 | fGridManager = nullptr; |
280 | 0 | } |
281 | 0 | fBruteForceManager.reset(); |
282 | 0 | SkASSERT(fCurrentManager == &fBruteForceManager); |
283 | 0 | } |
284 | 0 | } Unexecuted instantiation: skgpu::graphite::HybridBoundsManager::reset() Unexecuted instantiation: skgpu::graphite::HybridBoundsManager::reset() |
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 | | |
302 | 0 | void updateCurrentManagerIfNeeded() { |
303 | 0 | if (fCurrentManager == fGridManager.get() || |
304 | 0 | 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 | 0 | return; |
308 | 0 | } |
309 | | // Else we need to switch from the brute force manager to the grid manager |
310 | 0 | if (!fGridManager) { |
311 | 0 | fGridManager = GridBoundsManager::MakeRes(fDeviceSize, fGridCellSize, fMaxGridSize); |
312 | 0 | } |
313 | 0 | fCurrentManager = fGridManager.get(); |
314 | | |
315 | | // Fill out the grid manager with the recorded draws in the brute force manager |
316 | 0 | fBruteForceManager.replayDraws(fCurrentManager); |
317 | 0 | fBruteForceManager.reset(); |
318 | 0 | } |
319 | | }; |
320 | | |
321 | | } // namespace skgpu::graphite |
322 | | |
323 | | #endif // skgpu_graphite_geom_BoundsManager_DEFINED |