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