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