Coverage Report

Created: 2024-05-20 07:14

/src/skia/src/gpu/graphite/ClipStack_graphite.h
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright 2022 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_ClipStack_DEFINED
9
#define skgpu_graphite_ClipStack_DEFINED
10
11
#include "include/core/SkClipOp.h"
12
#include "include/private/base/SkTArray.h"
13
#include "src/base/SkTBlockList.h"
14
#include "src/gpu/graphite/DrawOrder.h"
15
#include "src/gpu/graphite/DrawParams.h"
16
#include "src/gpu/graphite/geom/Shape.h"
17
#include "src/gpu/graphite/geom/Transform_graphite.h"
18
19
class SkShader;
20
class SkStrokeRec;
21
22
namespace skgpu::graphite {
23
24
class BoundsManager;
25
class Device;
26
class Geometry;
27
28
// TODO: Port over many of the unit tests for skgpu/v1/ClipStack defined in GrClipStackTest since
29
// those tests do a thorough job of enumerating the different element combinations.
30
class ClipStack {
31
public:
32
    // TODO: Some of these states reflect what SkDevice requires. Others are based on what Ganesh
33
    // could handle analytically. They will likely change as graphite's clips are sorted out
34
    enum class ClipState : uint8_t {
35
        kEmpty, kWideOpen, kDeviceRect, kDeviceRRect, kComplex
36
    };
37
38
    // All data describing a geometric modification to the clip
39
    struct Element {
40
        Shape     fShape;
41
        Transform fLocalToDevice; // TODO: reference a cached Transform like DrawList?
42
        SkClipOp  fOp;
43
    };
44
45
    // 'owningDevice' must outlive the clip stack.
46
    ClipStack(Device* owningDevice);
47
48
    ~ClipStack();
49
50
    ClipStack(const ClipStack&) = delete;
51
    ClipStack& operator=(const ClipStack&) = delete;
52
53
0
    ClipState clipState() const { return this->currentSaveRecord().state(); }
54
0
    int maxDeferredClipDraws() const { return fElements.count(); }
55
    Rect conservativeBounds() const;
56
57
    class ElementIter;
58
    // Provides for-range over active, valid clip elements from most recent to oldest.
59
    // The iterator provides items as "const Element&".
60
    inline ElementIter begin() const;
61
    inline ElementIter end() const;
62
63
    // Clip stack manipulation
64
    void save();
65
    void restore();
66
67
    void clipShape(const Transform& localToDevice, const Shape& shape, SkClipOp op);
68
    void clipShader(sk_sp<SkShader> shader);
69
70
    // Compute the bounds and the effective elements of the clip stack when applied to the draw
71
    // described by the provided transform, shape, and stroke.
72
    //
73
    // Applying clips to a draw is a mostly lazy operation except for what is returned:
74
    //  - The Clip's scissor is set to 'conservativeBounds()'.
75
    //  - The Clip stores the draw's clipped bounds, taking into account its transform, styling, and
76
    //    the above scissor.
77
    //  - The Clip also stores the draw's fill-style invariant clipped bounds which is used in atlas
78
    //    draws and may differ from the draw bounds.
79
    //
80
    // All clip elements that affect the draw will be returned in `outEffectiveElements` alongside
81
    // the bounds. This method does not have any side-effects and the per-clip element state has to
82
    // be explicitly updated by calling `updateClipStateForDraw()` which prepares the clip stack for
83
    // later rendering.
84
    //
85
    // The returned clip element list will be empty if the shape is clipped out or if the draw is
86
    // unaffected by any of the clip elements.
87
    using ElementList = skia_private::STArray<4, const Element*>;
88
    Clip visitClipStackForDraw(const Transform&,
89
                               const Geometry&,
90
                               const SkStrokeRec&,
91
                               bool outsetBoundsForAA,
92
                               ElementList* outEffectiveElements) const;
93
94
    // Update the per-clip element state for later rendering using pre-computed clip state data for
95
    // a particular draw. The provided 'z' value is the depth value that the draw will use if it's
96
    // not clipped out entirely.
97
    //
98
    // The returned CompressedPaintersOrder is the largest order that will be used by any of the
99
    // clip elements that affect the draw.
100
    //
101
    // If the provided `clipState` indicates that the draw will be clipped out, then this method has
102
    // no effect and returns DrawOrder::kNoIntersection.
103
    CompressedPaintersOrder updateClipStateForDraw(const Clip& clip,
104
                                                   const ElementList& effectiveElements,
105
                                                   const BoundsManager*,
106
                                                   PaintersDepth z);
107
108
    void recordDeferredClipDraws();
109
110
private:
111
    // SaveRecords and Elements are stored in two parallel stacks. The top-most SaveRecord is the
112
    // active record, older records represent earlier save points and aren't modified until they
113
    // become active again. Elements may be owned by the active SaveRecord, in which case they are
114
    // fully mutable, or they may be owned by a prior SaveRecord. However, Elements from both the
115
    // active SaveRecord and older records can be valid and affect draw operations. Elements are
116
    // marked inactive when new elements are determined to supersede their effect completely.
117
    // Inactive elements of the active SaveRecord can be deleted immediately; inactive elements of
118
    // older SaveRecords may become active again as the save stack is popped back.
119
    //
120
    // See go/grclipstack-2.0 for additional details and visualization of the data structures.
121
    class SaveRecord;
122
123
    // Internally, a lot of clip reasoning is based on an op, outer bounds, and whether a shape
124
    // contains another (possibly just conservatively based on inner/outer device-space bounds).
125
    // Element and SaveRecord store this information directly. A draw is equivalent to a clip
126
    // element with the intersection op. TransformedShape is a lightweight wrapper that can convert
127
    // these different types into a common type that Simplify() can reason about.
128
    struct TransformedShape;
129
    // This captures which of the two elements in (A op B) would be required when they are combined,
130
    // where op is intersect or difference.
131
    enum class SimplifyResult {
132
        kEmpty,
133
        kAOnly,
134
        kBOnly,
135
        kBoth
136
    };
137
    static SimplifyResult Simplify(const TransformedShape& a, const TransformedShape& b);
138
139
    // Wraps the geometric Element data with logic for containment and bounds testing.
140
    class RawElement : public Element {
141
    public:
142
        using Stack = SkTBlockList<RawElement, 1>;
143
144
        RawElement(const Rect& deviceBounds,
145
                   const Transform& localToDevice,
146
                   const Shape& shape,
147
                   SkClipOp op);
148
149
0
        ~RawElement() {
150
            // A pending draw means the element affects something already recorded, so its own
151
            // shape needs to be recorded as a draw. Since recording requires the Device (and
152
            // DrawContext), it must happen before we destroy the element itself.
153
0
            SkASSERT(!this->hasPendingDraw());
154
0
        }
Unexecuted instantiation: skgpu::graphite::ClipStack::RawElement::~RawElement()
Unexecuted instantiation: skgpu::graphite::ClipStack::RawElement::~RawElement()
155
156
        // Silence warnings about implicit copy ctor/assignment because we're declaring a dtor
157
0
        RawElement(const RawElement&) = default;
158
0
        RawElement& operator=(const RawElement&) = default;
159
160
        operator TransformedShape() const;
161
162
0
        bool             hasPendingDraw() const { return fOrder != DrawOrder::kNoIntersection; }
163
0
        const Shape&     shape()          const { return fShape;         }
164
0
        const Transform& localToDevice()  const { return fLocalToDevice; }
165
0
        const Rect&      outerBounds()    const { return fOuterBounds;   }
166
0
        const Rect&      innerBounds()    const { return fInnerBounds;   }
167
0
        SkClipOp         op()             const { return fOp;            }
168
        ClipState        clipType()       const;
169
170
        // As new elements are pushed on to the stack, they may make older elements redundant.
171
        // The old elements are marked invalid so they are skipped during clip application, but may
172
        // become active again when a save record is restored.
173
0
        bool isInvalid() const { return fInvalidatedByIndex >= 0; }
174
        void markInvalid(const SaveRecord& current);
175
        void restoreValid(const SaveRecord& current);
176
177
        // 'added' represents a new op added to the element stack. Its combination with this element
178
        // can result in a number of possibilities:
179
        //  1. The entire clip is empty (signaled by both this and 'added' being invalidated).
180
        //  2. The 'added' op supercedes this element (this element is invalidated).
181
        //  3. This op supercedes the 'added' element (the added element is marked invalidated).
182
        //  4. Their combination can be represented by a single new op (in which case this
183
        //     element should be invalidated, and the combined shape stored in 'added').
184
        //  5. Or both elements remain needed to describe the clip (both are valid and unchanged).
185
        //
186
        // The calling element will only modify its invalidation index since it could belong
187
        // to part of the inactive stack (that might be restored later). All merged state/geometry
188
        // is handled by modifying 'added'.
189
        void updateForElement(RawElement* added, const SaveRecord& current);
190
191
        // Returns how this element affects the draw after more detailed analysis.
192
        enum class DrawInfluence {
193
            kNone,       // The element does not affect the draw
194
            kClipOut,    // The element causes the draw shape to be entirely clipped out
195
            kIntersect,  // The element intersects the draw shape in a complex way
196
        };
197
        DrawInfluence testForDraw(const TransformedShape& draw) const;
198
199
        // Updates usage tracking to incorporate the bounds and Z value for the new draw call.
200
        // If this element hasn't affected any prior draws, it will use the bounds manager to
201
        // assign itself a compressed painters order for later rendering.
202
        //
203
        // This method assumes that this element affects the draw in a complex way, such that
204
        // calling `testForDraw()` on the same draw would return `DrawInfluence::kIntersect`. It is
205
        // assumed that `testForDraw()` was called beforehand to ensure that this is the case.
206
        //
207
        // Assuming that this element does not clip out the draw, returns the painters order the
208
        // draw must sort after.
209
        CompressedPaintersOrder updateForDraw(const BoundsManager* boundsManager,
210
                                              const Rect& drawBounds,
211
                                              PaintersDepth drawZ);
212
213
        // Record a depth-only draw to the given device, restricted to the portion of the clip that
214
        // is actually required based on prior recorded draws. Resets usage tracking for subsequent
215
        // passes.
216
        void drawClip(Device*);
217
218
        void validate() const;
219
220
    private:
221
        // TODO: Should only combine elements within the same save record, that don't have pending
222
        // draws already. Otherwise, we're changing the geometry that will be rasterized and it
223
        // could lead to gaps even if in a perfect the world the analytically intersected shape was
224
        // equivalent. Can't combine with other save records, since they *might* become pending
225
        // later on.
226
        bool combine(const RawElement& other, const SaveRecord& current);
227
228
        // Device space bounds. These bounds are not snapped to pixels with the assumption that if
229
        // a relation (intersects, contains, etc.) is true for the bounds it will be true for the
230
        // rasterization of the coordinates that produced those bounds.
231
        Rect fInnerBounds;
232
        Rect fOuterBounds;
233
        // TODO: Convert fOuterBounds to a ComplementRect to make intersection tests faster?
234
        // Would need to store both original and complement, since the intersection test is
235
        // Rect + ComplementRect and Element/SaveRecord could be on either side of operation.
236
237
        // State tracking how this clip element needs to be recorded into the draw context. As the
238
        // clip stack is applied to additional draws, the clip's Z and usage bounds grow to account
239
        // for it; its compressed painter's order is selected the first time a draw is affected.
240
        Rect fUsageBounds;
241
        CompressedPaintersOrder fOrder;
242
        PaintersDepth fMaxZ;
243
244
        // Elements are invalidated by SaveRecords as the record is updated with new elements that
245
        // override old geometry. An invalidated element stores the index of the first element of
246
        // the save record that invalidated it. This makes it easy to undo when the save record is
247
        // popped from the stack, and is stable as the current save record is modified.
248
        int fInvalidatedByIndex;
249
    };
250
251
    // Represents a saved point in the clip stack, and manages the life time of elements added to
252
    // stack within the record's life time. Also provides the logic for determining active elements
253
    // given a draw query.
254
    class SaveRecord {
255
    public:
256
        using Stack = SkTBlockList<SaveRecord, 2>;
257
258
        explicit SaveRecord(const Rect& deviceBounds);
259
260
        SaveRecord(const SaveRecord& prior, int startingElementIndex);
261
262
0
        const SkShader* shader()      const { return fShader.get(); }
263
0
        const Rect&     outerBounds() const { return fOuterBounds;  }
264
0
        const Rect&     innerBounds() const { return fInnerBounds;  }
265
0
        SkClipOp        op()          const { return fStackOp;      }
266
        ClipState       state()       const;
267
268
0
        int  firstActiveElementIndex() const { return fStartingElementIndex;     }
269
0
        int  oldestElementIndex()      const { return fOldestValidIndex;         }
270
0
        bool canBeUpdated()            const { return (fDeferredSaveCount == 0); }
271
272
        Rect scissor(const Rect& deviceBounds, const Rect& drawBounds) const;
273
274
        // Deferred save manipulation
275
0
        void pushSave() {
276
0
            SkASSERT(fDeferredSaveCount >= 0);
277
0
            fDeferredSaveCount++;
278
0
        }
Unexecuted instantiation: skgpu::graphite::ClipStack::SaveRecord::pushSave()
Unexecuted instantiation: skgpu::graphite::ClipStack::SaveRecord::pushSave()
279
        // Returns true if the record should stay alive. False means the ClipStack must delete it
280
0
        bool popSave() {
281
0
            fDeferredSaveCount--;
282
0
            SkASSERT(fDeferredSaveCount >= -1);
283
0
            return fDeferredSaveCount >= 0;
284
0
        }
Unexecuted instantiation: skgpu::graphite::ClipStack::SaveRecord::popSave()
Unexecuted instantiation: skgpu::graphite::ClipStack::SaveRecord::popSave()
285
286
        // Return true if the element was added to 'elements', or otherwise affected the save record
287
        // (e.g. turned it empty).
288
        bool addElement(RawElement&& toAdd, RawElement::Stack* elements, Device*);
289
290
        void addShader(sk_sp<SkShader> shader);
291
292
        // Remove the elements owned by this save record, which must happen before the save record
293
        // itself is removed from the clip stack. Records draws for any removed elements that have
294
        // draw usages.
295
        void removeElements(RawElement::Stack* elements, Device*);
296
297
        // Restore element validity now that this record is the new top of the stack.
298
        void restoreElements(RawElement::Stack* elements);
299
300
    private:
301
        // These functions modify 'elements' and element-dependent state of the record
302
        // (such as valid index and fState). Records draws for any clips that have deferred usages
303
        // that are inactivated and cannot be restored (i.e. part of the active save record).
304
        bool appendElement(RawElement&& toAdd, RawElement::Stack* elements, Device*);
305
        void replaceWithElement(RawElement&& toAdd, RawElement::Stack* elements, Device*);
306
307
        // Inner bounds is always contained in outer bounds, or it is empty. All bounds will be
308
        // contained in the device bounds.
309
        Rect fInnerBounds; // Inside is full coverage (stack op == intersect) or 0 cov (diff)
310
        Rect fOuterBounds; // Outside is 0 coverage (op == intersect) or full cov (diff)
311
312
        // A save record can have up to one shader, multiple shaders are automatically blended
313
        sk_sp<SkShader> fShader;
314
315
        const int fStartingElementIndex; // First element owned by this save record
316
        int       fOldestValidIndex;     // Index of oldest element that's valid for this record
317
        int       fDeferredSaveCount;    // Number of save() calls without modifications (yet)
318
319
        // Will be kIntersect unless every valid element is kDifference, which is significant
320
        // because if kDifference then there is an implicit extra outer bounds at the device edges.
321
        SkClipOp  fStackOp;
322
        ClipState fState;
323
    };
324
325
    Rect deviceBounds() const;
326
327
0
    const SaveRecord& currentSaveRecord() const {
328
0
        SkASSERT(!fSaves.empty());
329
0
        return fSaves.back();
330
0
    }
Unexecuted instantiation: skgpu::graphite::ClipStack::currentSaveRecord() const
Unexecuted instantiation: skgpu::graphite::ClipStack::currentSaveRecord() const
331
332
    // Will return the current save record, properly updating deferred saves
333
    // and initializing a first record if it were empty.
334
    SaveRecord& writableSaveRecord(bool* wasDeferred);
335
336
    RawElement::Stack fElements;
337
    SaveRecord::Stack fSaves; // always has one wide open record at the top
338
339
    Device* fDevice; // the device this clip stack is coupled with
340
};
341
342
// Clip element iteration
343
class ClipStack::ElementIter {
344
public:
345
0
    bool operator!=(const ElementIter& o) const {
346
0
        return o.fItem != fItem && o.fRemaining != fRemaining;
347
0
    }
348
349
0
    const Element& operator*() const { return *fItem; }
350
351
0
    ElementIter& operator++() {
352
        // Skip over invalidated elements
353
0
        do {
354
0
            fRemaining--;
355
0
            ++fItem;
356
0
        } while(fRemaining > 0 && (*fItem).isInvalid());
357
358
0
        return *this;
359
0
    }
360
361
0
    ElementIter(RawElement::Stack::CRIter::Item item, int r) : fItem(item), fRemaining(r) {}
362
363
    RawElement::Stack::CRIter::Item fItem;
364
    int fRemaining;
365
366
    friend class ClipStack;
367
};
368
369
0
ClipStack::ElementIter ClipStack::begin() const {
370
0
    if (this->currentSaveRecord().state() == ClipState::kEmpty ||
371
0
        this->currentSaveRecord().state() == ClipState::kWideOpen) {
372
        // No visible clip elements when empty or wide open
373
0
        return this->end();
374
0
    }
375
0
    int count = fElements.count() - this->currentSaveRecord().oldestElementIndex();
376
0
    return ElementIter(fElements.ritems().begin(), count);
377
0
}
378
379
0
ClipStack::ElementIter ClipStack::end() const {
380
0
    return ElementIter(fElements.ritems().end(), 0);
381
0
}
382
383
} // namespace skgpu::graphite
384
385
#endif // skgpu_graphite_ClipStack_DEFINED