• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
clipState()53     ClipState clipState() const { return this->currentSaveRecord().state(); }
maxDeferredClipDraws()54     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 
~RawElement()149         ~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             SkASSERT(!this->hasPendingDraw());
154         }
155 
156         // Silence warnings about implicit copy ctor/assignment because we're declaring a dtor
157         RawElement(const RawElement&) = default;
158         RawElement& operator=(const RawElement&) = default;
159 
160         operator TransformedShape() const;
161 
hasPendingDraw()162         bool             hasPendingDraw() const { return fOrder != DrawOrder::kNoIntersection; }
shape()163         const Shape&     shape()          const { return fShape;         }
localToDevice()164         const Transform& localToDevice()  const { return fLocalToDevice; }
outerBounds()165         const Rect&      outerBounds()    const { return fOuterBounds;   }
innerBounds()166         const Rect&      innerBounds()    const { return fInnerBounds;   }
op()167         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.
isInvalid()173         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 
shader()262         const SkShader* shader()      const { return fShader.get(); }
outerBounds()263         const Rect&     outerBounds() const { return fOuterBounds;  }
innerBounds()264         const Rect&     innerBounds() const { return fInnerBounds;  }
op()265         SkClipOp        op()          const { return fStackOp;      }
266         ClipState       state()       const;
267 
firstActiveElementIndex()268         int  firstActiveElementIndex() const { return fStartingElementIndex;     }
oldestElementIndex()269         int  oldestElementIndex()      const { return fOldestValidIndex;         }
canBeUpdated()270         bool canBeUpdated()            const { return (fDeferredSaveCount == 0); }
271 
272         Rect scissor(const Rect& deviceBounds, const Rect& drawBounds) const;
273 
274         // Deferred save manipulation
pushSave()275         void pushSave() {
276             SkASSERT(fDeferredSaveCount >= 0);
277             fDeferredSaveCount++;
278         }
279         // Returns true if the record should stay alive. False means the ClipStack must delete it
popSave()280         bool popSave() {
281             fDeferredSaveCount--;
282             SkASSERT(fDeferredSaveCount >= -1);
283             return fDeferredSaveCount >= 0;
284         }
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 
currentSaveRecord()327     const SaveRecord& currentSaveRecord() const {
328         SkASSERT(!fSaves.empty());
329         return fSaves.back();
330     }
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     bool operator!=(const ElementIter& o) const {
346         return o.fItem != fItem && o.fRemaining != fRemaining;
347     }
348 
349     const Element& operator*() const { return *fItem; }
350 
351     ElementIter& operator++() {
352         // Skip over invalidated elements
353         do {
354             fRemaining--;
355             ++fItem;
356         } while(fRemaining > 0 && (*fItem).isInvalid());
357 
358         return *this;
359     }
360 
ElementIter(RawElement::Stack::CRIter::Item item,int r)361     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 
begin()369 ClipStack::ElementIter ClipStack::begin() const {
370     if (this->currentSaveRecord().state() == ClipState::kEmpty ||
371         this->currentSaveRecord().state() == ClipState::kWideOpen) {
372         // No visible clip elements when empty or wide open
373         return this->end();
374     }
375     int count = fElements.count() - this->currentSaveRecord().oldestElementIndex();
376     return ElementIter(fElements.ritems().begin(), count);
377 }
378 
end()379 ClipStack::ElementIter ClipStack::end() const {
380     return ElementIter(fElements.ritems().end(), 0);
381 }
382 
383 } // namespace skgpu::graphite
384 
385 #endif // skgpu_graphite_ClipStack_DEFINED
386