Flutter Engine
The Flutter Engine
ClipStack_graphite.h
Go to the documentation of this file.
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
18
19class SkShader;
20class SkStrokeRec;
21
22namespace skgpu::graphite {
23
24class BoundsManager;
25class Device;
26class 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.
30class ClipStack {
31public:
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 {
41 Transform fLocalToDevice; // TODO: reference a cached Transform like DrawList?
43 };
44
45 // 'owningDevice' must outlive the clip stack.
46 ClipStack(Device* owningDevice);
47
49
50 ClipStack(const ClipStack&) = delete;
51 ClipStack& operator=(const ClipStack&) = delete;
52
53 ClipState clipState() const { return this->currentSaveRecord().state(); }
54 int maxDeferredClipDraws() const { return fElements.count(); }
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.
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.
104 const ElementList& effectiveElements,
105 const BoundsManager*,
106 PaintersDepth z);
107
109
110private:
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 ~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
162 bool hasPendingDraw() const { return fOrder != DrawOrder::kNoIntersection; }
163 const Shape& shape() const { return fShape; }
164 const Transform& localToDevice() const { return fLocalToDevice; }
165 const Rect& outerBounds() const { return fOuterBounds; }
166 const Rect& innerBounds() const { return fInnerBounds; }
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.
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;
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 const SkShader* shader() const { return fShader.get(); }
263 const Rect& outerBounds() const { return fOuterBounds; }
264 const Rect& innerBounds() const { return fInnerBounds; }
265 SkClipOp op() const { return fStackOp; }
266 ClipState state() const;
267
268 int firstActiveElementIndex() const { return fStartingElementIndex; }
269 int oldestElementIndex() const { return fOldestValidIndex; }
270 bool canBeUpdated() const { return (fDeferredSaveCount == 0); }
271
272 Rect scissor(const Rect& deviceBounds, const Rect& drawBounds) const;
273
274 // Deferred save manipulation
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
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
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
344public:
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
352 // Skip over invalidated elements
353 do {
354 fRemaining--;
355 ++fItem;
356 } while(fRemaining > 0 && (*fItem).isInvalid());
357
358 return *this;
359 }
360
361 ElementIter(RawElement::Stack::CRIter::Item item, int r) : fItem(item), fRemaining(r) {}
362
363 RawElement::Stack::CRIter::Item fItem;
365
366 friend class ClipStack;
367};
368
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
380 return ElementIter(fElements.ritems().end(), 0);
381}
382
383} // namespace skgpu::graphite
384
385#endif // skgpu_graphite_ClipStack_DEFINED
int count
Definition: FontMgrTest.cpp:50
#define SkASSERT(cond)
Definition: SkAssert.h:116
SkClipOp
Definition: SkClipOp.h:13
bool SK_API Simplify(const SkPath &path, SkPath *result)
static SkPath clip(const SkPath &path, const SkHalfPlane &plane)
Definition: SkPath.cpp:3892
GrStyledShape fShape
static void draw(SkCanvas *canvas, SkRect &target, int x, int y)
Definition: aaclip.cpp:27
Shape
Definition: aaxfermodes.cpp:43
Item begin() const
Definition: SkTBlockList.h:450
Item end() const
Definition: SkTBlockList.h:451
RIter ritems()
Definition: SkTBlockList.h:301
int count() const
Definition: SkTBlockList.h:167
bool empty() const
Definition: SkTBlockList.h:185
ElementIter(RawElement::Stack::CRIter::Item item, int r)
bool operator!=(const ElementIter &o) const
RawElement::Stack::CRIter::Item fItem
void clipShader(sk_sp< SkShader > shader)
ClipStack(Device *owningDevice)
CompressedPaintersOrder updateClipStateForDraw(const Clip &clip, const ElementList &effectiveElements, const BoundsManager *, PaintersDepth z)
Clip visitClipStackForDraw(const Transform &, const Geometry &, const SkStrokeRec &, bool outsetBoundsForAA, ElementList *outEffectiveElements) const
ClipStack & operator=(const ClipStack &)=delete
ClipStack(const ClipStack &)=delete
void clipShape(const Transform &localToDevice, const Shape &shape, SkClipOp op)
static constexpr CompressedPaintersOrder kNoIntersection
Definition: DrawOrder.h:113
static bool b
struct MyStruct a[10]
AtkStateType state
Definition: dart.idl:42
TRect< Scalar > Rect
Definition: rect.h:769
MonotonicValue< PaintersDepthSequence > PaintersDepth
Definition: DrawOrder.h:86
MonotonicValue< CompressedPaintersOrderSequence > CompressedPaintersOrder
Definition: DrawOrder.h:58
skgpu::graphite::Transform Transform