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