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