• 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 #include "src/gpu/graphite/ClipStack_graphite.h"
9 
10 #include "include/core/SkMatrix.h"
11 #include "include/core/SkShader.h"
12 #include "include/core/SkStrokeRec.h"
13 #include "src/base/SkTLazy.h"
14 #include "src/core/SkMatrixProvider.h"
15 #include "src/core/SkPathPriv.h"
16 #include "src/core/SkRRectPriv.h"
17 #include "src/core/SkRectPriv.h"
18 #include "src/gpu/graphite/Device.h"
19 #include "src/gpu/graphite/DrawParams.h"
20 #include "src/gpu/graphite/geom/BoundsManager.h"
21 #include "src/gpu/graphite/geom/Geometry.h"
22 
23 namespace skgpu::graphite {
24 
25 namespace {
26 
subtract(const Rect & a,const Rect & b,bool exact)27 Rect subtract(const Rect& a, const Rect& b, bool exact) {
28     SkRect diff;
29     if (SkRectPriv::Subtract(a.asSkRect(), b.asSkRect(), &diff) || !exact) {
30         // Either A-B is exactly the rectangle stored in diff, or we don't need an exact answer
31         // and can settle for the subrect of A excluded from B (which is also 'diff')
32         return Rect{diff};
33     } else {
34         // For our purposes, we want the original A when A-B cannot be exactly represented
35         return a;
36     }
37 }
38 
oriented_bbox_intersection(const Rect & a,const Transform & aXform,const Rect & b,const Transform & bXform)39 bool oriented_bbox_intersection(const Rect& a, const Transform& aXform,
40                                 const Rect& b, const Transform& bXform) {
41     // NOTE: We intentionally exclude projected bounds for two reasons:
42     //   1. We can skip the division by w and worring about clipping to w = 0.
43     //   2. W/o the projective case, the separating axes are simpler to compute (see below).
44     SkASSERT(aXform.type() != Transform::Type::kProjection &&
45              bXform.type() != Transform::Type::kProjection);
46     SkV4 quadA[4], quadB[4];
47 
48     aXform.mapPoints(a, quadA);
49     bXform.mapPoints(b, quadB);
50 
51     // There are 4 separating axes, defined by the two normals from quadA and from quadB, but
52     // since they were produced by transforming a rectangle by an affine transform, we know the
53     // normals are orthoganal to the basis vectors of upper 2x2 of their two transforms.
54     auto axesX = skvx::float4(-aXform.matrix().rc(1,0), -aXform.matrix().rc(1,1),
55                               -bXform.matrix().rc(1,0), -bXform.matrix().rc(1,1));
56     auto axesY = skvx::float4(aXform.matrix().rc(0,0), aXform.matrix().rc(0,1),
57                               bXform.matrix().rc(0,0), bXform.matrix().rc(0,1));
58 
59     // Projections of the 4 corners of each quadrilateral vs. the 4 axes. For orthonormal
60     // transforms, the projections of a quad's corners to its own normal axes should work out
61     // to the original dimensions of the rectangle, but this code handles skew and scale factors
62     // without branching.
63     auto aProj0 = quadA[0].x * axesX + quadA[0].y * axesY;
64     auto aProj1 = quadA[1].x * axesX + quadA[1].y * axesY;
65     auto aProj2 = quadA[2].x * axesX + quadA[2].y * axesY;
66     auto aProj3 = quadA[3].x * axesX + quadA[3].y * axesY;
67 
68     auto bProj0 = quadB[0].x * axesX + quadB[0].y * axesY;
69     auto bProj1 = quadB[1].x * axesX + quadB[1].y * axesY;
70     auto bProj2 = quadB[2].x * axesX + quadB[2].y * axesY;
71     auto bProj3 = quadB[3].x * axesX + quadB[3].y * axesY;
72 
73     // Minimum and maximum projected values against the 4 axes, for both quadA and quadB, which
74     // gives us four pairs of intervals to test for separation.
75     auto minA = min(min(aProj0, aProj1), min(aProj2, aProj3));
76     auto maxA = max(max(aProj0, aProj1), max(aProj2, aProj3));
77     auto minB = min(min(bProj0, bProj1), min(bProj2, bProj3));
78     auto maxB = max(max(bProj0, bProj1), max(bProj2, bProj3));
79 
80     auto overlaps = (minB <= maxA) & (minA <= maxB);
81     return all(overlaps); // any non-overlapping interval would imply no intersection
82 }
83 
84 static const Transform kIdentity{SkM44()};
85 
86 } // anonymous namespace
87 
88 ///////////////////////////////////////////////////////////////////////////////
89 // ClipStack::TransformedShape
90 
91 // A flyweight object describing geometry, subject to a local-to-device transform.
92 // This can be used by SaveRecords, Elements, and draws to determine how two shape operations
93 // interact with each other, without needing to share a base class, friend each other, or have a
94 // template for each combination of two types.
95 struct ClipStack::TransformedShape {
96     const Transform& fLocalToDevice;
97     const Shape&     fShape;
98     const Rect&      fOuterBounds;
99     const Rect&      fInnerBounds;
100 
101     SkClipOp         fOp;
102 
103     // contains() performs a fair amount of work to be as accurate as possible since it can mean
104     // greatly simplifying the clip stack. However, in some contexts this isn't worth doing because
105     // the actual shape is only an approximation (save records), or there's no current way to take
106     // advantage of knowing this shape contains another (draws containing a clip hypothetically
107     // could replace their geometry to draw the clip directly, but that isn't implemented now).
108     bool fContainsChecksOnlyBounds = false;
109 
110     bool intersects(const TransformedShape&) const;
111     bool contains(const TransformedShape&) const;
112 };
113 
intersects(const TransformedShape & o) const114 bool ClipStack::TransformedShape::intersects(const TransformedShape& o) const {
115     if (!fOuterBounds.intersects(o.fOuterBounds)) {
116         return false;
117     }
118 
119     if (fLocalToDevice.type() <= Transform::Type::kRectStaysRect &&
120         o.fLocalToDevice.type() <= Transform::Type::kRectStaysRect) {
121         // The two shape's coordinate spaces are different but both rect-stays-rect or simpler.
122         // This means, though, that their outer bounds approximations are tight to their transormed
123         // shape bounds. There's no point to do further tests given that and that we already found
124         // that these outer bounds *do* intersect.
125         return true;
126     } else if (fLocalToDevice == o.fLocalToDevice) {
127         // Since the two shape's local coordinate spaces are the same, we can compare shape
128         // bounds directly for a more accurate intersection test. We intentionally do not go
129         // further and do shape-specific intersection tests since these could have unknown
130         // complexity (for paths) and limited utility (e.g. two round rects that are disjoint
131         // solely from their corner curves).
132         return fShape.bounds().intersects(o.fShape.bounds());
133     } else if (fLocalToDevice.type() != Transform::Type::kProjection &&
134                o.fLocalToDevice.type() != Transform::Type::kProjection) {
135         // The shapes don't share the same coordinate system, and their approximate 'outer'
136         // bounds in device space could have substantial outsetting to contain the transformed
137         // shape (e.g. 45 degree rotation). Perform a more detailed check on their oriented
138         // bounding boxes.
139         return oriented_bbox_intersection(fShape.bounds(), fLocalToDevice,
140                                           o.fShape.bounds(), o.fLocalToDevice);
141     }
142     // Else multiple perspective transforms are involved, so assume intersection and allow the
143     // rasterizer to handle perspective clipping.
144     return true;
145 }
146 
contains(const TransformedShape & o) const147 bool ClipStack::TransformedShape::contains(const TransformedShape& o) const {
148     if (fInnerBounds.contains(o.fOuterBounds)) {
149         return true;
150     }
151     // Skip more expensive contains() checks if configured not to, or if the extent of 'o' exceeds
152     // this shape's outer bounds. When that happens there must be some part of 'o' that cannot be
153     // contained in this shape.
154     if (fContainsChecksOnlyBounds || !fOuterBounds.contains(o.fOuterBounds)) {
155         return false;
156     }
157 
158     if (fContainsChecksOnlyBounds) {
159         return false; // don't do any more work
160     }
161 
162     if (fLocalToDevice == o.fLocalToDevice) {
163         // Test the shapes directly against each other, with a special check for a rrect+rrect
164         // containment (a intersect b == a implies b contains a) and paths (same gen ID, or same
165         // path for small paths means they contain each other).
166         static constexpr int kMaxPathComparePoints = 16;
167         if (fShape.isRRect() && o.fShape.isRRect()) {
168             return SkRRectPriv::ConservativeIntersect(fShape.rrect(), o.fShape.rrect())
169                     == o.fShape.rrect();
170         } else if (fShape.isPath() && o.fShape.isPath()) {
171             // TODO: Is this worth doing still if clips only cost as much as a single draw?
172             return (fShape.path().getGenerationID() == o.fShape.path().getGenerationID()) ||
173                     (fShape.path().countPoints() <= kMaxPathComparePoints &&
174                     fShape.path() == o.fShape.path());
175         } else {
176             return fShape.conservativeContains(o.fShape.bounds());
177         }
178     } else if (fLocalToDevice.type() <= Transform::Type::kRectStaysRect &&
179                o.fLocalToDevice.type() <= Transform::Type::kRectStaysRect) {
180         // Optimize the common case where o's bounds can be mapped tightly into this coordinate
181         // space and then tested against our shape.
182         Rect localBounds = fLocalToDevice.inverseMapRect(
183                 o.fLocalToDevice.mapRect(o.fShape.bounds()));
184         return fShape.conservativeContains(localBounds);
185     } else if (fShape.convex()) {
186         // Since this shape is convex, if all four corners of o's bounding box are inside it
187         // then the entirety of o is also guaranteed to be inside it.
188         SkV4 deviceQuad[4];
189         o.fLocalToDevice.mapPoints(o.fShape.bounds(), deviceQuad);
190         SkV4 localQuad[4];
191         fLocalToDevice.inverseMapPoints(deviceQuad, localQuad, 4);
192         for (int i = 0; i < 4; ++i) {
193             // TODO: Would be nice to make this consistent with how the GPU clips NDC w.
194             if (deviceQuad[i].w < SkPathPriv::kW0PlaneDistance ||
195                 localQuad[i].w < SkPathPriv::kW0PlaneDistance) {
196                 // Something in O actually projects behind the W = 0 plane and would be clipped
197                 // to infinity, so it's extremely unlikely that this contains O.
198                 return false;
199             }
200             if (!fShape.conservativeContains(skvx::float2::Load(localQuad + i) / localQuad[i].w)) {
201                 return false;
202             }
203         }
204         return true;
205     }
206 
207     // Else not an easily comparable pair of shapes so assume this doesn't contain O
208     return false;
209 }
210 
Simplify(const TransformedShape & a,const TransformedShape & b)211 ClipStack::SimplifyResult ClipStack::Simplify(const TransformedShape& a,
212                                               const TransformedShape& b) {
213     enum class ClipCombo {
214         kDD = 0b00,
215         kDI = 0b01,
216         kID = 0b10,
217         kII = 0b11
218     };
219 
220     switch(static_cast<ClipCombo>(((int) a.fOp << 1) | (int) b.fOp)) {
221         case ClipCombo::kII:
222             // Intersect (A) + Intersect (B)
223             if (!a.intersects(b)) {
224                 // Regions with non-zero coverage are disjoint, so intersection = empty
225                 return SimplifyResult::kEmpty;
226             } else if (b.contains(a)) {
227                 // B's full coverage region contains entirety of A, so intersection = A
228                 return SimplifyResult::kAOnly;
229             } else if (a.contains(b)) {
230                 // A's full coverage region contains entirety of B, so intersection = B
231                 return SimplifyResult::kBOnly;
232             } else {
233                 // The shapes intersect in some non-trivial manner
234                 return SimplifyResult::kBoth;
235             }
236         case ClipCombo::kID:
237             // Intersect (A) + Difference (B)
238             if (!a.intersects(b)) {
239                 // A only intersects B's full coverage region, so intersection = A
240                 return SimplifyResult::kAOnly;
241             } else if (b.contains(a)) {
242                 // B's zero coverage region completely contains A, so intersection = empty
243                 return SimplifyResult::kEmpty;
244             } else {
245                 // Intersection cannot be simplified. Note that the combination of a intersect
246                 // and difference op in this order cannot produce kBOnly
247                 return SimplifyResult::kBoth;
248             }
249         case ClipCombo::kDI:
250             // Difference (A) + Intersect (B) - the mirror of Intersect(A) + Difference(B),
251             // but combining is commutative so this is equivalent barring naming.
252             if (!b.intersects(a)) {
253                 // B only intersects A's full coverage region, so intersection = B
254                 return SimplifyResult::kBOnly;
255             } else if (a.contains(b)) {
256                 // A's zero coverage region completely contains B, so intersection = empty
257                 return SimplifyResult::kEmpty;
258             } else {
259                 // Cannot be simplified
260                 return SimplifyResult::kBoth;
261             }
262         case ClipCombo::kDD:
263             // Difference (A) + Difference (B)
264             if (a.contains(b)) {
265                 // A's zero coverage region contains B, so B doesn't remove any extra
266                 // coverage from their intersection.
267                 return SimplifyResult::kAOnly;
268             } else if (b.contains(a)) {
269                 // Mirror of the above case, intersection = B instead
270                 return SimplifyResult::kBOnly;
271             } else {
272                 // Intersection of the two differences cannot be simplified. Note that for
273                 // this op combination it is not possible to produce kEmpty.
274                 return SimplifyResult::kBoth;
275             }
276     }
277     SkUNREACHABLE;
278 }
279 
280 ///////////////////////////////////////////////////////////////////////////////
281 // ClipStack::Element
282 
RawElement(const Rect & deviceBounds,const Transform & localToDevice,const Shape & shape,SkClipOp op)283 ClipStack::RawElement::RawElement(const Rect& deviceBounds,
284                                   const Transform& localToDevice,
285                                   const Shape& shape,
286                                   SkClipOp op)
287         : Element{shape, localToDevice, op}
288         , fUsageBounds{Rect::InfiniteInverted()}
289         , fOrder(DrawOrder::kNoIntersection)
290         , fMaxZ(DrawOrder::kClearDepth)
291         , fInvalidatedByIndex(-1) {
292     // Discard shapes that don't have any area (including when a transform can't be inverted, since
293     // it means the two dimensions are collapsed to 0 or 1 dimension in device space).
294     if (fShape.isLine() || !localToDevice.valid()) {
295         fShape.reset();
296     }
297     // Make sure the shape is not inverted. An inverted shape is equivalent to a non-inverted shape
298     // with the clip op toggled.
299     if (fShape.inverted()) {
300         fOp = (fOp == SkClipOp::kIntersect) ? SkClipOp::kDifference : SkClipOp::kIntersect;
301     }
302 
303     fOuterBounds = fLocalToDevice.mapRect(fShape.bounds()).makeIntersect(deviceBounds);
304     fInnerBounds = Rect::InfiniteInverted();
305 
306     // Apply rect-stays-rect transforms to rects and round rects to reduce the number of unique
307     // local coordinate systems that are in play.
308     if (!fOuterBounds.isEmptyNegativeOrNaN() &&
309         fLocalToDevice.type() <= Transform::Type::kRectStaysRect) {
310         if (fShape.isRect()) {
311             // The actual geometry can be updated to the device-intersected bounds and we know the
312             // inner bounds are equal to the outer.
313             fShape.setRect(fOuterBounds);
314             fLocalToDevice = kIdentity;
315             fInnerBounds = fOuterBounds;
316         } else if (fShape.isRRect()) {
317             // Can't transform in place and must still check transform result since some very
318             // ill-formed scale+translate matrices can cause invalid rrect radii.
319             SkRRect xformed;
320             if (fShape.rrect().transform(fLocalToDevice, &xformed)) {
321                 fShape.setRRect(xformed);
322                 fLocalToDevice = kIdentity;
323                 // Refresh outer bounds to match the transformed round rect in case
324                 // SkRRect::transform produces slightly different results from Transform::mapRect.
325                 fOuterBounds = fShape.bounds().makeIntersect(deviceBounds);
326                 fInnerBounds = Rect{SkRRectPriv::InnerBounds(xformed)}.makeIntersect(fOuterBounds);
327             }
328         }
329     }
330 
331     if (fOuterBounds.isEmptyNegativeOrNaN()) {
332         // Either was already an empty shape or a non-empty shape is offscreen, so treat it as such.
333         fShape.reset();
334         fInnerBounds = Rect::InfiniteInverted();
335     }
336 
337     // Now that fOp and fShape are canonical, set the shape's fill type to match how it needs to be
338     // drawn as a depth-only shape everywhere that is clipped out (intersect is thus inverse-filled)
339     fShape.setInverted(fOp == SkClipOp::kIntersect);
340 
341     // Post-conditions on inner and outer bounds
342     SkASSERT(fShape.isEmpty() || deviceBounds.contains(fOuterBounds));
343     this->validate();
344 }
345 
operator ClipStack::TransformedShape() const346 ClipStack::RawElement::operator ClipStack::TransformedShape() const {
347     return {fLocalToDevice, fShape, fOuterBounds, fInnerBounds, fOp};
348 }
349 
drawClip(Device * device)350 void ClipStack::RawElement::drawClip(Device* device) {
351     this->validate();
352 
353     // Skip elements that have not affected any draws
354     if (!this->hasPendingDraw()) {
355         SkASSERT(fUsageBounds.isEmptyNegativeOrNaN());
356         return;
357     }
358 
359     SkASSERT(!fUsageBounds.isEmptyNegativeOrNaN());
360     // For clip draws, the usage bounds is the scissor.
361     Rect scissor = fUsageBounds.makeRoundOut();
362     Rect drawBounds = fOuterBounds.makeIntersect(scissor);
363     if (!drawBounds.isEmptyNegativeOrNaN()) {
364         // Although we are recording this clip draw after all the draws it affects, 'fOrder' was
365         // determined at the first usage, so after sorting by DrawOrder the clip draw will be in the
366         // right place. Unlike regular draws that use their own "Z", by writing (1 + max Z this clip
367         // affects), it will cause those draws to fail either GREATER and GEQUAL depth tests where
368         // they need to be clipped.
369         DrawOrder order{fMaxZ.next(), fOrder};
370         // An element's clip op is encoded in the shape's fill type. Inverse fills are intersect ops
371         // and regular fills are difference ops. This means fShape is already in the right state to
372         // draw directly.
373         SkASSERT((fOp == SkClipOp::kDifference && !fShape.inverted()) ||
374                  (fOp == SkClipOp::kIntersect && fShape.inverted()));
375         device->drawClipShape(fLocalToDevice, fShape, Clip{drawBounds, scissor.asSkIRect()}, order);
376     }
377 
378     // After the clip shape is drawn, reset its state. If the clip element is being popped off the
379     // stack or overwritten because a new clip invalidated it, this won't matter. But if the clips
380     // were drawn because the Device had to flush pending work while the clip stack was not empty,
381     // subsequent draws will still need to be clipped to the elements. In this case, the usage
382     // accumulation process will begin again and automatically use the Device's post-flush Z values
383     // and BoundsManager state.
384     fUsageBounds = Rect::InfiniteInverted();
385     fOrder = DrawOrder::kNoIntersection;
386     fMaxZ = DrawOrder::kClearDepth;
387 }
388 
validate() const389 void ClipStack::RawElement::validate() const {
390     // If the shape type isn't empty, the outer bounds shouldn't be empty; if the inner bounds are
391     // not empty, they must be contained in outer.
392     SkASSERT((fShape.isEmpty() || !fOuterBounds.isEmptyNegativeOrNaN()) &&
393              (fInnerBounds.isEmptyNegativeOrNaN() || fOuterBounds.contains(fInnerBounds)));
394     SkASSERT((fOp == SkClipOp::kDifference && !fShape.inverted()) ||
395              (fOp == SkClipOp::kIntersect && fShape.inverted()));
396     SkASSERT(!this->hasPendingDraw() || !fUsageBounds.isEmptyNegativeOrNaN());
397 }
398 
markInvalid(const SaveRecord & current)399 void ClipStack::RawElement::markInvalid(const SaveRecord& current) {
400     SkASSERT(!this->isInvalid());
401     fInvalidatedByIndex = current.firstActiveElementIndex();
402     // NOTE: We don't draw the accumulated clip usage when the element is marked invalid. Some
403     // invalidated elements are part of earlier save records so can become re-active after a restore
404     // in which case they should continue to accumulate. Invalidated elements that are part of the
405     // active save record are removed at the end of the stack modification, which is when they are
406     // explicitly drawn.
407 }
408 
restoreValid(const SaveRecord & current)409 void ClipStack::RawElement::restoreValid(const SaveRecord& current) {
410     if (current.firstActiveElementIndex() < fInvalidatedByIndex) {
411         fInvalidatedByIndex = -1;
412     }
413 }
414 
combine(const RawElement & other,const SaveRecord & current)415 bool ClipStack::RawElement::combine(const RawElement& other, const SaveRecord& current) {
416     // Don't combine elements that have collected draw usage, since that changes their geometry.
417     if (this->hasPendingDraw() || other.hasPendingDraw()) {
418         return false;
419     }
420     // To reduce the number of possibilities, only consider intersect+intersect. Difference and
421     // mixed op cases could be analyzed to simplify one of the shapes, but that is a rare
422     // occurrence and the math is much more complicated.
423     if (other.fOp != SkClipOp::kIntersect || fOp != SkClipOp::kIntersect) {
424         return false;
425     }
426 
427     // At the moment, only rect+rect or rrect+rrect are supported (although rect+rrect is
428     // treated as a degenerate case of rrect+rrect).
429     bool shapeUpdated = false;
430     if (fShape.isRect() && other.fShape.isRect()) {
431         if (fLocalToDevice == other.fLocalToDevice) {
432             Rect intersection = fShape.rect().makeIntersect(other.fShape.rect());
433             // Simplify() should have caught this case
434             SkASSERT(!intersection.isEmptyNegativeOrNaN());
435             fShape.setRect(intersection);
436             shapeUpdated = true;
437         }
438     } else if ((fShape.isRect() || fShape.isRRect()) &&
439                (other.fShape.isRect() || other.fShape.isRRect())) {
440         if (fLocalToDevice == other.fLocalToDevice) {
441             // Treat rrect+rect intersections as rrect+rrect
442             SkRRect a = fShape.isRect() ? SkRRect::MakeRect(fShape.rect().asSkRect())
443                                         : fShape.rrect();
444             SkRRect b = other.fShape.isRect() ? SkRRect::MakeRect(other.fShape.rect().asSkRect())
445                                               : other.fShape.rrect();
446 
447             SkRRect joined = SkRRectPriv::ConservativeIntersect(a, b);
448             if (!joined.isEmpty()) {
449                 // Can reduce to a single element
450                 if (joined.isRect()) {
451                     // And with a simplified type
452                     fShape.setRect(joined.rect());
453                 } else {
454                     fShape.setRRect(joined);
455                 }
456                 shapeUpdated = true;
457             }
458             // else the intersection isn't representable as a rrect, or doesn't actually intersect.
459             // ConservativeIntersect doesn't disambiguate those two cases, and just testing bounding
460             // boxes for non-intersection would have already been caught by Simplify(), so
461             // just don't combine the two elements and let rasterization resolve the combination.
462         }
463     }
464 
465     if (shapeUpdated) {
466         // This logic works under the assumption that both combined elements were intersect.
467         SkASSERT(fOp == SkClipOp::kIntersect && other.fOp == SkClipOp::kIntersect);
468         fOuterBounds.intersect(other.fOuterBounds);
469         fInnerBounds.intersect(other.fInnerBounds);
470         // Inner bounds can become empty, but outer bounds should not be able to.
471         SkASSERT(!fOuterBounds.isEmptyNegativeOrNaN());
472         fShape.setInverted(true); // the setR[R]ect operations reset to non-inverse
473         this->validate();
474         return true;
475     } else {
476         return false;
477     }
478 }
479 
updateForElement(RawElement * added,const SaveRecord & current)480 void ClipStack::RawElement::updateForElement(RawElement* added, const SaveRecord& current) {
481     if (this->isInvalid()) {
482         // Already doesn't do anything, so skip this element
483         return;
484     }
485 
486     // 'A' refers to this element, 'B' refers to 'added'.
487     switch (Simplify(*this, *added)) {
488         case SimplifyResult::kEmpty:
489             // Mark both elements as invalid to signal that the clip is fully empty
490             this->markInvalid(current);
491             added->markInvalid(current);
492             break;
493 
494         case SimplifyResult::kAOnly:
495             // This element already clips more than 'added', so mark 'added' is invalid to skip it
496             added->markInvalid(current);
497             break;
498 
499         case SimplifyResult::kBOnly:
500             // 'added' clips more than this element, so mark this as invalid
501             this->markInvalid(current);
502             break;
503 
504         case SimplifyResult::kBoth:
505             // Else the bounds checks think we need to keep both, but depending on the combination
506             // of the ops and shape kinds, we may be able to do better.
507             if (added->combine(*this, current)) {
508                 // 'added' now fully represents the combination of the two elements
509                 this->markInvalid(current);
510             }
511             break;
512     }
513 }
514 
515 std::pair<bool, CompressedPaintersOrder>
updateForDraw(const BoundsManager * boundsManager,const TransformedShape & draw,PaintersDepth drawZ)516 ClipStack::RawElement::updateForDraw(const BoundsManager* boundsManager,
517                                      const TransformedShape& draw,
518                                      PaintersDepth drawZ) {
519     if (this->isInvalid()) {
520         // Cannot affect the draw
521         return {/*clippedOut=*/false, DrawOrder::kNoIntersection};
522     }
523 
524     // For this analysis, A refers to the Element and B refers to the draw
525     switch(Simplify(*this, draw)) {
526         case SimplifyResult::kEmpty:
527             // The more detailed per-element checks have determined the draw is clipped out.
528             return {/*clippedOut=*/true, DrawOrder::kNoIntersection};
529 
530         case SimplifyResult::kBOnly:
531             // This element does not affect the draw
532             return {/*clippedOut=*/false, DrawOrder::kNoIntersection};
533 
534         case SimplifyResult::kAOnly:
535             // If this were the only element, we could replace the draw's geometry but that only
536             // gives us a win if we know that the clip element would only be used by this draw.
537             // For now, just fall through to regular clip handling.
538             [[fallthrough]];
539 
540         case SimplifyResult::kBoth:
541             if (!this->hasPendingDraw()) {
542                 // No usage yet so we need an order that we will use when drawing to just the depth
543                 // attachment. It is sufficient to use the next CompressedPaintersOrder after the
544                 // most recent draw under this clip's outer bounds. It is necessary to use the
545                 // entire clip's outer bounds because the order has to be determined before the
546                 // final usage bounds are known and a subsequent draw could require a completely
547                 // different portion of the clip than this triggering draw.
548                 //
549                 // Lazily determining the order has several benefits to computing it when the clip
550                 // element was first created:
551                 //  - Elements that are invalidated by nested clips before draws are made do not
552                 //    waste time in the BoundsManager.
553                 //  - Elements that never actually modify a draw (e.g. a defensive clip) do not
554                 //    waste time in the BoundsManager.
555                 //  - A draw that triggers clip usage on multiple elements will more likely assign
556                 //    the same order to those elements, meaning their depth-only draws are more
557                 //    likely to batch in the final DrawPass.
558                 //
559                 // However, it does mean that clip elements can have the same order as each other,
560                 // or as later draws (e.g. after the clip has been popped off the stack). Any
561                 // overlap between clips or draws is addressed when the clip is drawn by selecting
562                 // an appropriate DisjointStencilIndex value. Stencil-aside, this order assignment
563                 // logic, max Z tracking, and the depth test during rasterization are able to
564                 // resolve everything correctly even if clips have the same order value.
565                 // See go/clip-stack-order for a detailed analysis of why this works.
566                 fOrder = boundsManager->getMostRecentDraw(fOuterBounds).next();
567                 fUsageBounds = draw.fOuterBounds;
568                 fMaxZ = drawZ;
569             } else {
570                 // Earlier draws have already used this element so we cannot change where the
571                 // depth-only draw will be sorted to, but we need to ensure we cover the new draw's
572                 // bounds and use a Z value that will clip out its pixels as appropriate.
573                 fUsageBounds.join(draw.fOuterBounds);
574                 if (drawZ > fMaxZ) {
575                     fMaxZ = drawZ;
576                 }
577             }
578 
579             return {/*clippedOut=*/false, fOrder};
580     }
581 
582     SkUNREACHABLE;
583 }
584 
clipType() const585 ClipStack::ClipState ClipStack::RawElement::clipType() const {
586     // Map from the internal shape kind to the clip state enum
587     switch (fShape.type()) {
588         case Shape::Type::kEmpty:
589             return ClipState::kEmpty;
590 
591         case Shape::Type::kRect:
592             return fOp == SkClipOp::kIntersect &&
593                    fLocalToDevice.type() == Transform::Type::kIdentity
594                         ? ClipState::kDeviceRect : ClipState::kComplex;
595 
596         case Shape::Type::kRRect:
597             return fOp == SkClipOp::kIntersect &&
598                    fLocalToDevice.type() == Transform::Type::kIdentity
599                         ? ClipState::kDeviceRRect : ClipState::kComplex;
600 
601         case Shape::Type::kLine:
602             // These types should never become RawElements, but call them kComplex in release builds
603             SkASSERT(false);
604             [[fallthrough]];
605 
606         case Shape::Type::kPath:
607             return ClipState::kComplex;
608     }
609     SkUNREACHABLE;
610 }
611 
612 ///////////////////////////////////////////////////////////////////////////////
613 // ClipStack::SaveRecord
614 
SaveRecord(const Rect & deviceBounds)615 ClipStack::SaveRecord::SaveRecord(const Rect& deviceBounds)
616         : fInnerBounds(deviceBounds)
617         , fOuterBounds(deviceBounds)
618         , fShader(nullptr)
619         , fStartingElementIndex(0)
620         , fOldestValidIndex(0)
621         , fDeferredSaveCount(0)
622         , fStackOp(SkClipOp::kIntersect)
623         , fState(ClipState::kWideOpen)  {}
624 
SaveRecord(const SaveRecord & prior,int startingElementIndex)625 ClipStack::SaveRecord::SaveRecord(const SaveRecord& prior,
626                                   int startingElementIndex)
627         : fInnerBounds(prior.fInnerBounds)
628         , fOuterBounds(prior.fOuterBounds)
629         , fShader(prior.fShader)
630         , fStartingElementIndex(startingElementIndex)
631         , fOldestValidIndex(prior.fOldestValidIndex)
632         , fDeferredSaveCount(0)
633         , fStackOp(prior.fStackOp)
634         , fState(prior.fState) {
635     // If the prior record added an element, this one will insert into the same index
636     // (that's okay since we'll remove it when this record is popped off the stack).
637     SkASSERT(startingElementIndex >= prior.fStartingElementIndex);
638 }
639 
state() const640 ClipStack::ClipState ClipStack::SaveRecord::state() const {
641     if (fShader && fState != ClipState::kEmpty) {
642         return ClipState::kComplex;
643     } else {
644         return fState;
645     }
646 }
647 
scissor(const Rect & deviceBounds,const Rect & drawBounds) const648 Rect ClipStack::SaveRecord::scissor(const Rect& deviceBounds, const Rect& drawBounds) const {
649     // This should only be called when the clip stack actually has something non-trivial to evaluate
650     // It is effectively a reduced version of Simplify() dealing only with device-space bounds and
651     // returning the intersection results.
652     SkASSERT(this->state() != ClipState::kEmpty && this->state() != ClipState::kWideOpen);
653     SkASSERT(deviceBounds.contains(drawBounds)); // This should have already been handled.
654 
655     if (fStackOp == SkClipOp::kDifference) {
656         // kDifference nominally uses the draw's bounds minus the save record's inner bounds as the
657         // scissor. However, if the draw doesn't intersect the clip at all then it doesn't have any
658         // visual effect and we can switch to the device bounds as the canonical scissor.
659         if (!fOuterBounds.intersects(drawBounds)) {
660             return deviceBounds;
661         } else {
662             // This automatically detects the case where the draw is contained in inner bounds and
663             // would be entirely clipped out.
664             return subtract(drawBounds, fInnerBounds, /*exact=*/true);
665         }
666     } else {
667         // kIntersect nominally uses the save record's outer bounds as the scissor. However, if the
668         // draw is contained entirely within those bounds, it doesn't have any visual effect so
669         // switch to using the device bounds as the canonical scissor to minimize state changes.
670         if (fOuterBounds.contains(drawBounds)) {
671             return deviceBounds;
672         } else {
673             // This automatically detects the case where the draw does not intersect the clip.
674             return fOuterBounds;
675         }
676     }
677 }
678 
removeElements(RawElement::Stack * elements,Device * device)679 void ClipStack::SaveRecord::removeElements(RawElement::Stack* elements, Device* device) {
680     while (elements->count() > fStartingElementIndex) {
681         // Since the element is being deleted now, it won't be in the ClipStack when the Device
682         // calls recordDeferredClipDraws(). Record the clip's draw now (if it needs it).
683         elements->back().drawClip(device);
684         elements->pop_back();
685     }
686 }
687 
restoreElements(RawElement::Stack * elements)688 void ClipStack::SaveRecord::restoreElements(RawElement::Stack* elements) {
689     // Presumably this SaveRecord is the new top of the stack, and so it owns the elements
690     // from its starting index to restoreCount - 1. Elements from the old save record have
691     // been destroyed already, so their indices would have been >= restoreCount, and any
692     // still-present element can be un-invalidated based on that.
693     int i = elements->count() - 1;
694     for (RawElement& e : elements->ritems()) {
695         if (i < fOldestValidIndex) {
696             break;
697         }
698         e.restoreValid(*this);
699         --i;
700     }
701 }
702 
addShader(sk_sp<SkShader> shader)703 void ClipStack::SaveRecord::addShader(sk_sp<SkShader> shader) {
704     SkASSERT(shader);
705     SkASSERT(this->canBeUpdated());
706     if (!fShader) {
707         fShader = std::move(shader);
708     } else {
709         // The total coverage is computed by multiplying the coverage from each element (shape or
710         // shader), but since multiplication is associative, we can use kSrcIn blending to make
711         // a new shader that represents 'shader' * 'fShader'
712         fShader = SkShaders::Blend(SkBlendMode::kSrcIn, std::move(shader), fShader);
713     }
714 }
715 
addElement(RawElement && toAdd,RawElement::Stack * elements,Device * device)716 bool ClipStack::SaveRecord::addElement(RawElement&& toAdd,
717                                        RawElement::Stack* elements,
718                                        Device* device) {
719     // Validity check the element's state first
720     toAdd.validate();
721 
722     // And we shouldn't be adding an element if we have a deferred save
723     SkASSERT(this->canBeUpdated());
724 
725     if (fState == ClipState::kEmpty) {
726         // The clip is already empty, and we only shrink, so there's no need to record this element.
727         return false;
728     } else if (toAdd.shape().isEmpty()) {
729         // An empty difference op should have been detected earlier, since it's a no-op
730         SkASSERT(toAdd.op() == SkClipOp::kIntersect);
731         fState = ClipState::kEmpty;
732         this->removeElements(elements, device);
733         return true;
734     }
735 
736     // Here we treat the SaveRecord as a "TransformedShape" with the identity transform, and a shape
737     // equal to its outer bounds. This lets us get accurate intersection tests against the new
738     // element, but we pass true to skip more detailed contains checks because the SaveRecord's
739     // shape is potentially very different from its aggregate outer bounds.
740     Shape outerSaveBounds{fOuterBounds};
741     TransformedShape save{kIdentity, outerSaveBounds, fOuterBounds, fInnerBounds, fStackOp,
742                           /*containsChecksOnlyBounds=*/true};
743 
744     // In this invocation, 'A' refers to the existing stack's bounds and 'B' refers to the new
745     // element.
746     switch (Simplify(save, toAdd)) {
747         case SimplifyResult::kEmpty:
748             // The combination results in an empty clip
749             fState = ClipState::kEmpty;
750             this->removeElements(elements, device);
751             return true;
752 
753         case SimplifyResult::kAOnly:
754             // The combination would not be any different than the existing clip
755             return false;
756 
757         case SimplifyResult::kBOnly:
758             // The combination would invalidate the entire existing stack and can be replaced with
759             // just the new element.
760             this->replaceWithElement(std::move(toAdd), elements, device);
761             return true;
762 
763         case SimplifyResult::kBoth:
764             // The new element combines in a complex manner, so update the stack's bounds based on
765             // the combination of its and the new element's ops (handled below)
766             break;
767     }
768 
769     if (fState == ClipState::kWideOpen) {
770         // When the stack was wide open and the clip effect was kBoth, the "complex" manner is
771         // simply to keep the element and update the stack bounds to be the element's intersected
772         // with the device.
773         this->replaceWithElement(std::move(toAdd), elements, device);
774         return true;
775     }
776 
777     // Some form of actual clip element(s) to combine with.
778     if (fStackOp == SkClipOp::kIntersect) {
779         if (toAdd.op() == SkClipOp::kIntersect) {
780             // Intersect (stack) + Intersect (toAdd)
781             //  - Bounds updates is simply the paired intersections of outer and inner.
782             fOuterBounds.intersect(toAdd.outerBounds());
783             fInnerBounds.intersect(toAdd.innerBounds());
784             // Outer should not have become empty, but is allowed to if there's no intersection.
785             SkASSERT(!fOuterBounds.isEmptyNegativeOrNaN());
786         } else {
787             // Intersect (stack) + Difference (toAdd)
788             //  - Shrink the stack's outer bounds if the difference op's inner bounds completely
789             //    cuts off an edge.
790             //  - Shrink the stack's inner bounds to completely exclude the op's outer bounds.
791             fOuterBounds = subtract(fOuterBounds, toAdd.innerBounds(), /* exact */ true);
792             fInnerBounds = subtract(fInnerBounds, toAdd.outerBounds(), /* exact */ false);
793         }
794     } else {
795         if (toAdd.op() == SkClipOp::kIntersect) {
796             // Difference (stack) + Intersect (toAdd)
797             //  - Bounds updates are just the mirror of Intersect(stack) + Difference(toAdd)
798             Rect oldOuter = fOuterBounds;
799             fOuterBounds = subtract(toAdd.outerBounds(), fInnerBounds, /* exact */ true);
800             fInnerBounds = subtract(toAdd.innerBounds(), oldOuter,     /* exact */ false);
801         } else {
802             // Difference (stack) + Difference (toAdd)
803             //  - The updated outer bounds is the union of outer bounds and the inner becomes the
804             //    largest of the two possible inner bounds
805             fOuterBounds.join(toAdd.outerBounds());
806             if (toAdd.innerBounds().area() > fInnerBounds.area()) {
807                 fInnerBounds = toAdd.innerBounds();
808             }
809         }
810     }
811 
812     // If we get here, we're keeping the new element and the stack's bounds have been updated.
813     // We ought to have caught the cases where the stack bounds resemble an empty or wide open
814     // clip, so assert that's the case.
815     SkASSERT(!fOuterBounds.isEmptyNegativeOrNaN() &&
816              (fInnerBounds.isEmptyNegativeOrNaN() || fOuterBounds.contains(fInnerBounds)));
817 
818     return this->appendElement(std::move(toAdd), elements, device);
819 }
820 
appendElement(RawElement && toAdd,RawElement::Stack * elements,Device * device)821 bool ClipStack::SaveRecord::appendElement(RawElement&& toAdd,
822                                           RawElement::Stack* elements,
823                                           Device* device) {
824     // Update past elements to account for the new element
825     int i = elements->count() - 1;
826 
827     // After the loop, elements between [max(youngestValid, startingIndex)+1, count-1] can be
828     // removed from the stack (these are the active elements that have been invalidated by the
829     // newest element; since it's the active part of the stack, no restore() can bring them back).
830     int youngestValid = fStartingElementIndex - 1;
831     // After the loop, elements between [0, oldestValid-1] are all invalid. The value of oldestValid
832     // becomes the save record's new fLastValidIndex value.
833     int oldestValid = elements->count();
834     // After the loop, this is the earliest active element that was invalidated. It may be
835     // older in the stack than earliestValid, so cannot be popped off, but can be used to store
836     // the new element instead of allocating more.
837     RawElement* oldestActiveInvalid = nullptr;
838     int oldestActiveInvalidIndex = elements->count();
839 
840     for (RawElement& existing : elements->ritems()) {
841         if (i < fOldestValidIndex) {
842             break;
843         }
844         // We don't need to pass the actual index that toAdd will be saved to; just the minimum
845         // index of this save record, since that will result in the same restoration behavior later.
846         existing.updateForElement(&toAdd, *this);
847 
848         if (toAdd.isInvalid()) {
849             if (existing.isInvalid()) {
850                 // Both new and old invalid implies the entire clip becomes empty
851                 fState = ClipState::kEmpty;
852                 return true;
853             } else {
854                 // The new element doesn't change the clip beyond what the old element already does
855                 return false;
856             }
857         } else if (existing.isInvalid()) {
858             // The new element cancels out the old element. The new element may have been modified
859             // to account for the old element's geometry.
860             if (i >= fStartingElementIndex) {
861                 // Still active, so the invalidated index could be used to store the new element
862                 oldestActiveInvalid = &existing;
863                 oldestActiveInvalidIndex = i;
864             }
865         } else {
866             // Keep both new and old elements
867             oldestValid = i;
868             if (i > youngestValid) {
869                 youngestValid = i;
870             }
871         }
872 
873         --i;
874     }
875 
876     // Post-iteration validity check
877     SkASSERT(oldestValid == elements->count() ||
878              (oldestValid >= fOldestValidIndex && oldestValid < elements->count()));
879     SkASSERT(youngestValid == fStartingElementIndex - 1 ||
880              (youngestValid >= fStartingElementIndex && youngestValid < elements->count()));
881     SkASSERT((oldestActiveInvalid && oldestActiveInvalidIndex >= fStartingElementIndex &&
882               oldestActiveInvalidIndex < elements->count()) || !oldestActiveInvalid);
883 
884     // Update final state
885     SkASSERT(oldestValid >= fOldestValidIndex);
886     fOldestValidIndex = std::min(oldestValid, oldestActiveInvalidIndex);
887     fState = oldestValid == elements->count() ? toAdd.clipType() : ClipState::kComplex;
888     if (fStackOp == SkClipOp::kDifference && toAdd.op() == SkClipOp::kIntersect) {
889         // The stack remains in difference mode only as long as all elements are difference
890         fStackOp = SkClipOp::kIntersect;
891     }
892 
893     int targetCount = youngestValid + 1;
894     if (!oldestActiveInvalid || oldestActiveInvalidIndex >= targetCount) {
895         // toAdd will be stored right after youngestValid
896         targetCount++;
897         oldestActiveInvalid = nullptr;
898     }
899     while (elements->count() > targetCount) {
900         SkASSERT(oldestActiveInvalid != &elements->back()); // shouldn't delete what we'll reuse
901         elements->back().drawClip(device);
902         elements->pop_back();
903     }
904     if (oldestActiveInvalid) {
905         oldestActiveInvalid->drawClip(device);
906         *oldestActiveInvalid = std::move(toAdd);
907     } else if (elements->count() < targetCount) {
908         elements->push_back(std::move(toAdd));
909     } else {
910         elements->back().drawClip(device);
911         elements->back() = std::move(toAdd);
912     }
913 
914     return true;
915 }
916 
replaceWithElement(RawElement && toAdd,RawElement::Stack * elements,Device * device)917 void ClipStack::SaveRecord::replaceWithElement(RawElement&& toAdd,
918                                                RawElement::Stack* elements,
919                                                Device* device) {
920     // The aggregate state of the save record mirrors the element
921     fInnerBounds = toAdd.innerBounds();
922     fOuterBounds = toAdd.outerBounds();
923     fStackOp = toAdd.op();
924     fState = toAdd.clipType();
925 
926     // All prior active element can be removed from the stack: [startingIndex, count - 1]
927     int targetCount = fStartingElementIndex + 1;
928     while (elements->count() > targetCount) {
929         elements->back().drawClip(device);
930         elements->pop_back();
931     }
932     if (elements->count() < targetCount) {
933         elements->push_back(std::move(toAdd));
934     } else {
935         elements->back().drawClip(device);
936         elements->back() = std::move(toAdd);
937     }
938 
939     SkASSERT(elements->count() == fStartingElementIndex + 1);
940 
941     // This invalidates all older elements that are owned by save records lower in the clip stack.
942     fOldestValidIndex = fStartingElementIndex;
943 }
944 
945 ///////////////////////////////////////////////////////////////////////////////
946 // ClipStack
947 
948 // NOTE: Based on draw calls in all GMs, SKPs, and SVGs as of 08/20, 98% use a clip stack with
949 // one Element and up to two SaveRecords, thus the inline size for RawElement::Stack and
950 // SaveRecord::Stack (this conveniently keeps the size of ClipStack manageable). The max
951 // encountered element stack depth was 5 and the max save depth was 6. Using an increment of 8 for
952 // these stacks means that clip management will incur a single allocation for the remaining 2%
953 // of the draws, with extra head room for more complex clips encountered in the wild.
954 static constexpr int kElementStackIncrement = 8;
955 static constexpr int kSaveStackIncrement = 8;
956 
ClipStack(Device * owningDevice)957 ClipStack::ClipStack(Device* owningDevice)
958         : fElements(kElementStackIncrement)
959         , fSaves(kSaveStackIncrement)
960         , fDevice(owningDevice) {
961     // Start with a save record that is wide open
962     fSaves.emplace_back(this->deviceBounds());
963 }
964 
965 ClipStack::~ClipStack() = default;
966 
save()967 void ClipStack::save() {
968     SkASSERT(!fSaves.empty());
969     fSaves.back().pushSave();
970 }
971 
restore()972 void ClipStack::restore() {
973     SkASSERT(!fSaves.empty());
974     SaveRecord& current = fSaves.back();
975     if (current.popSave()) {
976         // This was just a deferred save being undone, so the record doesn't need to be removed yet
977         return;
978     }
979 
980     // When we remove a save record, we delete all elements >= its starting index and any masks
981     // that were rasterized for it.
982     current.removeElements(&fElements, fDevice);
983 
984     fSaves.pop_back();
985     // Restore any remaining elements that were only invalidated by the now-removed save record.
986     fSaves.back().restoreElements(&fElements);
987 }
988 
deviceBounds() const989 Rect ClipStack::deviceBounds() const {
990     return Rect::WH(fDevice->width(), fDevice->height());
991 }
992 
conservativeBounds() const993 Rect ClipStack::conservativeBounds() const {
994     const SaveRecord& current = this->currentSaveRecord();
995     if (current.state() == ClipState::kEmpty) {
996         return Rect::InfiniteInverted();
997     } else if (current.state() == ClipState::kWideOpen) {
998         return this->deviceBounds();
999     } else {
1000         if (current.op() == SkClipOp::kDifference) {
1001             // The outer/inner bounds represent what's cut out, so full bounds remains the device
1002             // bounds, minus any fully clipped content that spans the device edge.
1003             return subtract(this->deviceBounds(), current.innerBounds(), /* exact */ true);
1004         } else {
1005             SkASSERT(this->deviceBounds().contains(current.outerBounds()));
1006             return current.outerBounds();
1007         }
1008     }
1009 }
1010 
writableSaveRecord(bool * wasDeferred)1011 ClipStack::SaveRecord& ClipStack::writableSaveRecord(bool* wasDeferred) {
1012     SaveRecord& current = fSaves.back();
1013     if (current.canBeUpdated()) {
1014         // Current record is still open, so it can be modified directly
1015         *wasDeferred = false;
1016         return current;
1017     } else {
1018         // Must undefer the save to get a new record.
1019         SkAssertResult(current.popSave());
1020         *wasDeferred = true;
1021         return fSaves.emplace_back(current, fElements.count());
1022     }
1023 }
1024 
clipShader(sk_sp<SkShader> shader)1025 void ClipStack::clipShader(sk_sp<SkShader> shader) {
1026     // Shaders can't bring additional coverage
1027     if (this->currentSaveRecord().state() == ClipState::kEmpty) {
1028         return;
1029     }
1030 
1031     bool wasDeferred;
1032     this->writableSaveRecord(&wasDeferred).addShader(std::move(shader));
1033     // Geometry elements are not invalidated by updating the clip shader
1034     // TODO: Integrating clipShader into graphite needs more thought, particularly around how to
1035     // handle the shader explosion and where to put the effects in the GraphicsPipelineDesc.
1036     // One idea is to use sample locations and draw the clipShader into the depth buffer.
1037     // Another is resolve the clip shader into an alpha mask image that is sampled by the draw.
1038 }
1039 
clipShape(const Transform & localToDevice,const Shape & shape,SkClipOp op)1040 void ClipStack::clipShape(const Transform& localToDevice,
1041                           const Shape& shape,
1042                           SkClipOp op) {
1043     if (this->currentSaveRecord().state() == ClipState::kEmpty) {
1044         return;
1045     }
1046 
1047     // This will apply the transform if it's shape-type preserving, and clip the element's bounds
1048     // to the device bounds (NOT the conservative clip bounds, since those are based on the net
1049     // effect of all elements while device bounds clipping happens implicitly. During addElement,
1050     // we may still be able to invalidate some older elements).
1051     // NOTE: Does not try to simplify the shape type by inspecting the SkPath.
1052     RawElement element{this->deviceBounds(), localToDevice, shape, op};
1053 
1054     // An empty op means do nothing (for difference), or close the save record, so we try and detect
1055     // that early before doing additional unnecessary save record allocation.
1056     if (element.shape().isEmpty()) {
1057         if (element.op() == SkClipOp::kDifference) {
1058             // If the shape is empty and we're subtracting, this has no effect on the clip
1059             return;
1060         }
1061         // else we will make the clip empty, but we need a new save record to record that change
1062         // in the clip state; fall through to below and updateForElement() will handle it.
1063     }
1064 
1065     bool wasDeferred;
1066     SaveRecord& save = this->writableSaveRecord(&wasDeferred);
1067     SkDEBUGCODE(int elementCount = fElements.count();)
1068     if (!save.addElement(std::move(element), &fElements, fDevice)) {
1069         if (wasDeferred) {
1070             // We made a new save record, but ended up not adding an element to the stack.
1071             // So instead of keeping an empty save record around, pop it off and restore the counter
1072             SkASSERT(elementCount == fElements.count());
1073             fSaves.pop_back();
1074             fSaves.back().pushSave();
1075         }
1076     }
1077 }
1078 
applyClipToDraw(const BoundsManager * boundsManager,const Transform & localToDevice,const Geometry & geometry,const SkStrokeRec & style,PaintersDepth z)1079 std::pair<Clip, CompressedPaintersOrder> ClipStack::applyClipToDraw(
1080         const BoundsManager* boundsManager,
1081         const Transform& localToDevice,
1082         const Geometry& geometry,
1083         const SkStrokeRec& style,
1084         PaintersDepth z) {
1085     const SaveRecord& cs = this->currentSaveRecord();
1086     if (cs.state() == ClipState::kEmpty) {
1087         // We know the draw is clipped out so don't bother computing the base draw bounds.
1088         return {Clip{Rect::InfiniteInverted(), SkIRect::MakeEmpty()}, DrawOrder::kNoIntersection};
1089     }
1090     // Compute draw bounds, clipped only to our device bounds since we need to return that even if
1091     // the clip stack is known to be wide-open.
1092     const Rect deviceBounds = this->deviceBounds();
1093 
1094     // When 'style' isn't fill, 'shape' describes the pre-stroke shape so we can't use it to check
1095     // against clip elements and this will be set to the bounds of the post-stroked shape instead.
1096     SkTCopyOnFirstWrite<Shape> styledShape;
1097     if (geometry.isShape()) {
1098         styledShape.init(geometry.shape());
1099     } else {
1100         // The geometry is something special like text or vertices, in which case it's definitely
1101         // not a shape that could simplify cleanly with the clip stack.
1102         styledShape.initIfNeeded(geometry.bounds());
1103     }
1104 
1105     Rect drawBounds; // defined in device space
1106     if (styledShape->inverted()) {
1107         // Inverse-filled shapes always fill the entire device (restricted to the clip).
1108         drawBounds = deviceBounds;
1109         styledShape.writable()->setRect(drawBounds);
1110     } else {
1111         // Regular filled shapes and strokes get larger based on style and transform
1112         drawBounds = styledShape->bounds();
1113         if (!style.isHairlineStyle()) {
1114             float localStyleOutset = style.getInflationRadius();
1115             drawBounds.outset(localStyleOutset);
1116 
1117             if (!style.isFillStyle()) {
1118                 // While this loses any shape type, the bounds remain local so hopefully tests are
1119                 // fairly accurate.
1120                 styledShape.writable()->setRect(drawBounds);
1121             }
1122         }
1123         drawBounds = localToDevice.mapRect(drawBounds);
1124 
1125         // Hairlines get an extra pixel *after* transforming to device space
1126         if (style.isHairlineStyle()) {
1127             drawBounds.outset(0.5f);
1128             // and the associated transform must be kIdentity since drawBounds has been mapped by
1129             // localToDevice already.
1130             styledShape.writable()->setRect(drawBounds);
1131         }
1132 
1133         // Restrict bounds to the device limits
1134         drawBounds.intersect(deviceBounds);
1135     }
1136 
1137     if (drawBounds.isEmptyNegativeOrNaN() || cs.state() == ClipState::kWideOpen) {
1138         // Either the draw is off screen, so it's clipped out regardless of the state of the
1139         // SaveRecord, or there are no elements to apply to the draw. In both cases, 'drawBounds'
1140         // has the correct value, the scissor is the device bounds (ignored if clipped-out), and
1141         // we can return kNoIntersection for the painter's order.
1142         return {Clip{drawBounds, deviceBounds.asSkIRect()}, DrawOrder::kNoIntersection};
1143     }
1144 
1145     // We don't evaluate Simplify() on the SaveRecord and the draw because a reduced version of
1146     // Simplify is effectively performed in computing the scissor rect.
1147     // Given that, we can skip iterating over the clip elements when:
1148     //  - the draw's *scissored* bounds are empty, which happens when the draw was clipped out.
1149     //  - the draw's *bounds* are contained in our inner bounds, which happens if all we need to
1150     //    apply to the draw is the computed scissor rect.
1151     // TODO: The Clip's scissor is defined in terms of integer pixel coords, but if we move to
1152     // clip plane distances in the vertex shader, it can be defined in terms of the original float
1153     // coordinates.
1154     Rect scissor = cs.scissor(deviceBounds, drawBounds).makeRoundOut();
1155     drawBounds.intersect(scissor);
1156     if (drawBounds.isEmptyNegativeOrNaN() || cs.innerBounds().contains(drawBounds)) {
1157         // Like above, in both cases drawBounds holds the right value and can return kNoIntersection
1158         return {Clip{drawBounds, scissor.asSkIRect()}, DrawOrder::kNoIntersection};
1159     }
1160 
1161     // If we made it here, the clip stack affects the draw in a complex way so iterate each element.
1162     // A draw is a transformed shape that "intersects" the clip. We use empty inner bounds because
1163     // there's currently no way to re-write the draw as the clip's geometry, so there's no need to
1164     // check if the draw contains the clip (vice versa is still checked and represents an unclipped
1165     // draw so is very useful to identify).
1166     TransformedShape draw{style.isHairlineStyle() ? kIdentity : localToDevice,
1167                           *styledShape,
1168                           /*outerBounds=*/drawBounds,
1169                           /*innerBounds=*/Rect::InfiniteInverted(),
1170                           /*op=*/SkClipOp::kIntersect,
1171                           /*containsChecksOnlyBounds=*/true};
1172 
1173     CompressedPaintersOrder maxClipOrder = DrawOrder::kNoIntersection;
1174     int i = fElements.count();
1175     for (RawElement& e : fElements.ritems()) {
1176         --i;
1177         if (i < cs.oldestElementIndex()) {
1178             // All earlier elements have been invalidated by elements already processed so the draw
1179             // can't be affected by them and cannot contribute to their usage bounds.
1180             break;
1181         }
1182 
1183         auto [clippedOut, order] = e.updateForDraw(boundsManager, draw, z);
1184         if (clippedOut) {
1185             drawBounds = Rect::InfiniteInverted();
1186             break;
1187         } else {
1188             maxClipOrder = std::max(order, maxClipOrder);
1189         }
1190     }
1191 
1192     return {Clip{drawBounds, scissor.asSkIRect()}, maxClipOrder};
1193 }
1194 
recordDeferredClipDraws()1195 void ClipStack::recordDeferredClipDraws() {
1196     for (auto& e : fElements.items()) {
1197         // When a Device requires all clip elements to be recorded, we have to iterate all elements,
1198         // and will draw clip shapes for elements that are still marked as invalid from the clip
1199         // stack, including those that are older than the current save record's oldest valid index,
1200         // because they could have accumulated draw usage prior to being invalidated, but weren't
1201         // flushed when they were invalidated because of an intervening save.
1202         e.drawClip(fDevice);
1203     }
1204 }
1205 
1206 } // namespace skgpu
1207