Coverage Report

Created: 2024-05-20 07:14

/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