• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 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/v1/ClipStack.h"
9 
10 #include "include/core/SkMatrix.h"
11 #include "src/core/SkMatrixProvider.h"
12 #include "src/core/SkPathPriv.h"
13 #include "src/core/SkRRectPriv.h"
14 #include "src/core/SkRectPriv.h"
15 #include "src/core/SkTaskGroup.h"
16 #include "src/gpu/GrClip.h"
17 #include "src/gpu/GrDeferredProxyUploader.h"
18 #include "src/gpu/GrDirectContextPriv.h"
19 #include "src/gpu/GrFragmentProcessor.h"
20 #include "src/gpu/GrProxyProvider.h"
21 #include "src/gpu/GrRecordingContextPriv.h"
22 #include "src/gpu/GrSWMaskHelper.h"
23 #include "src/gpu/effects/GrBlendFragmentProcessor.h"
24 #include "src/gpu/effects/GrConvexPolyEffect.h"
25 #include "src/gpu/effects/GrRRectEffect.h"
26 #include "src/gpu/effects/GrTextureEffect.h"
27 #include "src/gpu/geometry/GrQuadUtils.h"
28 #include "src/gpu/ops/AtlasPathRenderer.h"
29 #include "src/gpu/ops/GrDrawOp.h"
30 #include "src/gpu/v1/StencilMaskHelper.h"
31 #include "src/gpu/v1/SurfaceDrawContext_v1.h"
32 
33 namespace {
34 
35 // This captures which of the two elements in (A op B) would be required when they are combined,
36 // where op is intersect or difference.
37 enum class ClipGeometry {
38     kEmpty,
39     kAOnly,
40     kBOnly,
41     kBoth
42 };
43 
44 // A and B can be Element, SaveRecord, or Draw. Supported combinations are, order not mattering,
45 // (Element, Element), (Element, SaveRecord), (Element, Draw), and (SaveRecord, Draw).
46 template<typename A, typename B>
get_clip_geometry(const A & a,const B & b)47 ClipGeometry get_clip_geometry(const A& a, const B& b) {
48     // NOTE: SkIRect::Intersects() returns false when two rectangles touch at an edge (so the result
49     // is empty). This behavior is desired for the following clip effect policies.
50     if (a.op() == SkClipOp::kIntersect) {
51         if (b.op() == SkClipOp::kIntersect) {
52             // Intersect (A) + Intersect (B)
53             if (!SkIRect::Intersects(a.outerBounds(), b.outerBounds())) {
54                 // Regions with non-zero coverage are disjoint, so intersection = empty
55                 return ClipGeometry::kEmpty;
56             } else if (b.contains(a)) {
57                 // B's full coverage region contains entirety of A, so intersection = A
58                 return ClipGeometry::kAOnly;
59             } else if (a.contains(b)) {
60                 // A's full coverage region contains entirety of B, so intersection = B
61                 return ClipGeometry::kBOnly;
62             } else {
63                 // The shapes intersect in some non-trivial manner
64                 return ClipGeometry::kBoth;
65             }
66         } else {
67             SkASSERT(b.op() == SkClipOp::kDifference);
68             // Intersect (A) + Difference (B)
69             if (!SkIRect::Intersects(a.outerBounds(), b.outerBounds())) {
70                 // A only intersects B's full coverage region, so intersection = A
71                 return ClipGeometry::kAOnly;
72             } else if (b.contains(a)) {
73                 // B's zero coverage region completely contains A, so intersection = empty
74                 return ClipGeometry::kEmpty;
75             } else {
76                 // Intersection cannot be simplified. Note that the combination of a intersect
77                 // and difference op in this order cannot produce kBOnly
78                 return ClipGeometry::kBoth;
79             }
80         }
81     } else {
82         SkASSERT(a.op() == SkClipOp::kDifference);
83         if (b.op() == SkClipOp::kIntersect) {
84             // Difference (A) + Intersect (B) - the mirror of Intersect(A) + Difference(B),
85             // but combining is commutative so this is equivalent barring naming.
86             if (!SkIRect::Intersects(b.outerBounds(), a.outerBounds())) {
87                 // B only intersects A's full coverage region, so intersection = B
88                 return ClipGeometry::kBOnly;
89             } else if (a.contains(b)) {
90                 // A's zero coverage region completely contains B, so intersection = empty
91                 return ClipGeometry::kEmpty;
92             } else {
93                 // Cannot be simplified
94                 return ClipGeometry::kBoth;
95             }
96         } else {
97             SkASSERT(b.op() == SkClipOp::kDifference);
98             // Difference (A) + Difference (B)
99             if (a.contains(b)) {
100                 // A's zero coverage region contains B, so B doesn't remove any extra
101                 // coverage from their intersection.
102                 return ClipGeometry::kAOnly;
103             } else if (b.contains(a)) {
104                 // Mirror of the above case, intersection = B instead
105                 return ClipGeometry::kBOnly;
106             } else {
107                 // Intersection of the two differences cannot be simplified. Note that for
108                 // this op combination it is not possible to produce kEmpty.
109                 return ClipGeometry::kBoth;
110             }
111         }
112     }
113 }
114 
115 // a.contains(b) where a's local space is defined by 'aToDevice', and b's possibly separate local
116 // space is defined by 'bToDevice'. 'a' and 'b' geometry are provided in their local spaces.
117 // Automatically takes into account if the anti-aliasing policies differ. When the policies match,
118 // we assume that coverage AA or GPU's non-AA rasterization will apply to A and B equivalently, so
119 // we can compare the original shapes. When the modes are mixed, we outset B in device space first.
shape_contains_rect(const GrShape & a,const SkMatrix & aToDevice,const SkMatrix & deviceToA,const SkRect & b,const SkMatrix & bToDevice,bool mixedAAMode)120 bool shape_contains_rect(const GrShape& a, const SkMatrix& aToDevice, const SkMatrix& deviceToA,
121                          const SkRect& b, const SkMatrix& bToDevice, bool mixedAAMode) {
122     if (!a.convex()) {
123         return false;
124     }
125 
126     if (!mixedAAMode && aToDevice == bToDevice) {
127         // A and B are in the same coordinate space, so don't bother mapping
128         return a.conservativeContains(b);
129     } else if (bToDevice.isIdentity() && aToDevice.preservesAxisAlignment()) {
130         // Optimize the common case of draws (B, with identity matrix) and axis-aligned shapes,
131         // instead of checking the four corners separately.
132         SkRect bInA = b;
133         if (mixedAAMode) {
134             bInA.outset(0.5f, 0.5f);
135         }
136         SkAssertResult(deviceToA.mapRect(&bInA));
137         return a.conservativeContains(bInA);
138     }
139 
140     // Test each corner for contains; since a is convex, if all 4 corners of b's bounds are
141     // contained, then the entirety of b is within a.
142     GrQuad deviceQuad = GrQuad::MakeFromRect(b, bToDevice);
143     if (any(deviceQuad.w4f() < SkPathPriv::kW0PlaneDistance)) {
144         // Something in B actually projects behind the W = 0 plane and would be clipped to infinity,
145         // so it's extremely unlikely that A can contain B.
146         return false;
147     }
148     if (mixedAAMode) {
149         // Outset it so its edges are 1/2px out, giving us a buffer to avoid cases where a non-AA
150         // clip or draw would snap outside an aa element.
151         GrQuadUtils::Outset({0.5f, 0.5f, 0.5f, 0.5f}, &deviceQuad);
152     }
153 
154     for (int i = 0; i < 4; ++i) {
155         SkPoint cornerInA = deviceQuad.point(i);
156         deviceToA.mapPoints(&cornerInA, 1);
157         if (!a.conservativeContains(cornerInA)) {
158             return false;
159         }
160     }
161 
162     return true;
163 }
164 
subtract(const SkIRect & a,const SkIRect & b,bool exact)165 SkIRect subtract(const SkIRect& a, const SkIRect& b, bool exact) {
166     SkIRect diff;
167     if (SkRectPriv::Subtract(a, b, &diff) || !exact) {
168         // Either A-B is exactly the rectangle stored in diff, or we don't need an exact answer
169         // and can settle for the subrect of A excluded from B (which is also 'diff')
170         return diff;
171     } else {
172         // For our purposes, we want the original A when A-B cannot be exactly represented
173         return a;
174     }
175 }
176 
get_clip_edge_type(SkClipOp op,GrAA aa)177 GrClipEdgeType get_clip_edge_type(SkClipOp op, GrAA aa) {
178     if (op == SkClipOp::kIntersect) {
179         return aa == GrAA::kYes ? GrClipEdgeType::kFillAA : GrClipEdgeType::kFillBW;
180     } else {
181         return aa == GrAA::kYes ? GrClipEdgeType::kInverseFillAA : GrClipEdgeType::kInverseFillBW;
182     }
183 }
184 
185 static uint32_t kInvalidGenID  = 0;
186 static uint32_t kEmptyGenID    = 1;
187 static uint32_t kWideOpenGenID = 2;
188 
next_gen_id()189 uint32_t next_gen_id() {
190     // 0-2 are reserved for invalid, empty & wide-open
191     static const uint32_t kFirstUnreservedGenID = 3;
192     static std::atomic<uint32_t> nextID{kFirstUnreservedGenID};
193 
194     uint32_t id;
195     do {
196         id = nextID.fetch_add(1, std::memory_order_relaxed);
197     } while (id < kFirstUnreservedGenID);
198     return id;
199 }
200 
201 // Functions for rendering / applying clip shapes in various ways
202 // The general strategy is:
203 //  - Represent the clip element as an analytic FP that tests sk_FragCoord vs. its device shape
204 //  - Render the clip element to the stencil, if stencil is allowed and supports the AA, and the
205 //    size of the element indicates stenciling will be worth it, vs. making a mask.
206 //  - Try to put the individual element into a clip atlas, which is then sampled during the draw
207 //  - Render the element into a SW mask and upload it. If possible, the SW rasterization happens
208 //    in parallel.
209 static constexpr GrSurfaceOrigin kMaskOrigin = kTopLeft_GrSurfaceOrigin;
210 
analytic_clip_fp(const skgpu::v1::ClipStack::Element & e,const GrShaderCaps & caps,std::unique_ptr<GrFragmentProcessor> fp)211 GrFPResult analytic_clip_fp(const skgpu::v1::ClipStack::Element& e,
212                             const GrShaderCaps& caps,
213                             std::unique_ptr<GrFragmentProcessor> fp) {
214     // All analytic clip shape FPs need to be in device space
215     GrClipEdgeType edgeType = get_clip_edge_type(e.fOp, e.fAA);
216     if (e.fLocalToDevice.isIdentity()) {
217         if (e.fShape.isRect()) {
218             return GrFPSuccess(GrFragmentProcessor::Rect(std::move(fp), edgeType, e.fShape.rect()));
219         } else if (e.fShape.isRRect()) {
220             return GrRRectEffect::Make(std::move(fp), edgeType, e.fShape.rrect(), caps);
221         }
222     }
223 
224     // A convex hull can be transformed into device space (this will handle rect shapes with a
225     // non-identity transform).
226     if (e.fShape.segmentMask() == SkPath::kLine_SegmentMask && e.fShape.convex()) {
227         SkPath devicePath;
228         e.fShape.asPath(&devicePath);
229         devicePath.transform(e.fLocalToDevice);
230         return GrConvexPolyEffect::Make(std::move(fp), edgeType, devicePath);
231     }
232 
233     return GrFPFailure(std::move(fp));
234 }
235 
236 // TODO: Currently this only works with tessellation because the tessellation path renderer owns and
237 // manages the atlas. The high-level concept could be generalized to support any path renderer going
238 // into a shared atlas.
clip_atlas_fp(const skgpu::v1::SurfaceDrawContext * sdc,const GrOp * opBeingClipped,skgpu::v1::AtlasPathRenderer * atlasPathRenderer,const SkIRect & scissorBounds,const skgpu::v1::ClipStack::Element & e,std::unique_ptr<GrFragmentProcessor> inputFP)239 GrFPResult clip_atlas_fp(const skgpu::v1::SurfaceDrawContext* sdc,
240                          const GrOp* opBeingClipped,
241                          skgpu::v1::AtlasPathRenderer* atlasPathRenderer,
242                          const SkIRect& scissorBounds,
243                          const skgpu::v1::ClipStack::Element& e,
244                          std::unique_ptr<GrFragmentProcessor> inputFP) {
245     if (e.fAA != GrAA::kYes) {
246         return GrFPFailure(std::move(inputFP));
247     }
248     SkPath path;
249     e.fShape.asPath(&path);
250     SkASSERT(!path.isInverseFillType());
251     if (e.fOp == SkClipOp::kDifference) {
252         // Toggling fill type does not affect the path's "generationID" key.
253         path.toggleInverseFillType();
254     }
255     return atlasPathRenderer->makeAtlasClipEffect(sdc, opBeingClipped, std::move(inputFP),
256                                                   scissorBounds, e.fLocalToDevice, path);
257 }
258 
draw_to_sw_mask(GrSWMaskHelper * helper,const skgpu::v1::ClipStack::Element & e,bool clearMask)259 void draw_to_sw_mask(GrSWMaskHelper* helper,
260                      const skgpu::v1::ClipStack::Element& e,
261                      bool clearMask) {
262     // If the first element to draw is an intersect, we clear to 0 and will draw it directly with
263     // coverage 1 (subsequent intersect elements will be inverse-filled and draw 0 outside).
264     // If the first element to draw is a difference, we clear to 1, and in all cases we draw the
265     // difference element directly with coverage 0.
266     if (clearMask) {
267         helper->clear(e.fOp == SkClipOp::kIntersect ? 0x00 : 0xFF);
268     }
269 
270     uint8_t alpha;
271     bool invert;
272     if (e.fOp == SkClipOp::kIntersect) {
273         // Intersect modifies pixels outside of its geometry. If this isn't the first op, we
274         // draw the inverse-filled shape with 0 coverage to erase everything outside the element
275         // But if we are the first element, we can draw directly with coverage 1 since we
276         // cleared to 0.
277         if (clearMask) {
278             alpha = 0xFF;
279             invert = false;
280         } else {
281             alpha = 0x00;
282             invert = true;
283         }
284     } else {
285         // For difference ops, can always just subtract the shape directly by drawing 0 coverage
286         SkASSERT(e.fOp == SkClipOp::kDifference);
287         alpha = 0x00;
288         invert = false;
289     }
290 
291     // Draw the shape; based on how we've initialized the buffer and chosen alpha+invert,
292     // every element is drawn with the kReplace_Op
293     if (invert) {
294         // Must invert the path
295         SkASSERT(!e.fShape.inverted());
296         // TODO: this is an extra copy effectively, just so we can toggle inversion; would be
297         // better perhaps to just call a drawPath() since we know it'll use path rendering w/
298         // the inverse fill type.
299         GrShape inverted(e.fShape);
300         inverted.setInverted(true);
301         helper->drawShape(inverted, e.fLocalToDevice, SkRegion::kReplace_Op, e.fAA, alpha);
302     } else {
303         helper->drawShape(e.fShape, e.fLocalToDevice, SkRegion::kReplace_Op, e.fAA, alpha);
304     }
305 }
306 
render_sw_mask(GrRecordingContext * context,const SkIRect & bounds,const skgpu::v1::ClipStack::Element ** elements,int count)307 GrSurfaceProxyView render_sw_mask(GrRecordingContext* context,
308                                   const SkIRect& bounds,
309                                   const skgpu::v1::ClipStack::Element** elements,
310                                   int count) {
311     SkASSERT(count > 0);
312 
313     SkTaskGroup* taskGroup = nullptr;
314     if (auto direct = context->asDirectContext()) {
315         taskGroup = direct->priv().getTaskGroup();
316     }
317 
318     if (taskGroup) {
319         const GrCaps* caps = context->priv().caps();
320         GrProxyProvider* proxyProvider = context->priv().proxyProvider();
321 
322         // Create our texture proxy
323         GrBackendFormat format = caps->getDefaultBackendFormat(GrColorType::kAlpha_8,
324                                                                GrRenderable::kNo);
325 
326         skgpu::Swizzle swizzle = context->priv().caps()->getReadSwizzle(format,
327                                                                         GrColorType::kAlpha_8);
328         auto proxy = proxyProvider->createProxy(format, bounds.size(), GrRenderable::kNo, 1,
329                                                 GrMipMapped::kNo, SkBackingFit::kApprox,
330                                                 SkBudgeted::kYes, GrProtected::kNo);
331 
332         // Since this will be rendered on another thread, make a copy of the elements in case
333         // the clip stack is modified on the main thread
334         using Uploader = GrTDeferredProxyUploader<SkTArray<skgpu::v1::ClipStack::Element>>;
335         std::unique_ptr<Uploader> uploader = std::make_unique<Uploader>(count);
336         for (int i = 0; i < count; ++i) {
337             uploader->data().push_back(*(elements[i]));
338         }
339 
340         Uploader* uploaderRaw = uploader.get();
341         auto drawAndUploadMask = [uploaderRaw, bounds] {
342             TRACE_EVENT0("skia.gpu", "Threaded SW Clip Mask Render");
343             GrSWMaskHelper helper(uploaderRaw->getPixels());
344             if (helper.init(bounds)) {
345                 for (int i = 0; i < uploaderRaw->data().count(); ++i) {
346                     draw_to_sw_mask(&helper, uploaderRaw->data()[i], i == 0);
347                 }
348             } else {
349                 SkDEBUGFAIL("Unable to allocate SW clip mask.");
350             }
351             uploaderRaw->signalAndFreeData();
352         };
353 
354         taskGroup->add(std::move(drawAndUploadMask));
355         proxy->texPriv().setDeferredUploader(std::move(uploader));
356 
357         return {std::move(proxy), kMaskOrigin, swizzle};
358     } else {
359         GrSWMaskHelper helper;
360         if (!helper.init(bounds)) {
361             return {};
362         }
363 
364         for (int i = 0; i < count; ++i) {
365             draw_to_sw_mask(&helper,*(elements[i]), i == 0);
366         }
367 
368         return helper.toTextureView(context, SkBackingFit::kApprox);
369     }
370 }
371 
render_stencil_mask(GrRecordingContext * rContext,skgpu::v1::SurfaceDrawContext * sdc,uint32_t genID,const SkIRect & bounds,const skgpu::v1::ClipStack::Element ** elements,int count,GrAppliedClip * out)372 void render_stencil_mask(GrRecordingContext* rContext,
373                          skgpu::v1::SurfaceDrawContext* sdc,
374                          uint32_t genID,
375                          const SkIRect& bounds,
376                          const skgpu::v1::ClipStack::Element** elements,
377                          int count,
378                          GrAppliedClip* out) {
379     skgpu::v1::StencilMaskHelper helper(rContext, sdc);
380     if (helper.init(bounds, genID, out->windowRectsState().windows(), 0)) {
381         // This follows the same logic as in draw_sw_mask
382         bool startInside = elements[0]->fOp == SkClipOp::kDifference;
383         helper.clear(startInside);
384         for (int i = 0; i < count; ++i) {
385             const skgpu::v1::ClipStack::Element& e = *(elements[i]);
386             SkRegion::Op op;
387             if (e.fOp == SkClipOp::kIntersect) {
388                 op = (i == 0) ? SkRegion::kReplace_Op : SkRegion::kIntersect_Op;
389             } else {
390                 op = SkRegion::kDifference_Op;
391             }
392             helper.drawShape(e.fShape, e.fLocalToDevice, op, e.fAA);
393         }
394         helper.finish();
395     }
396     out->hardClip().addStencilClip(genID);
397 }
398 
399 } // anonymous namespace
400 
401 namespace skgpu::v1 {
402 
403 class ClipStack::Draw {
404 public:
Draw(const SkRect & drawBounds,GrAA aa)405     Draw(const SkRect& drawBounds, GrAA aa)
406             : fBounds(GrClip::GetPixelIBounds(drawBounds, aa, BoundsType::kExterior))
407             , fAA(aa) {
408         // Be slightly more forgiving on whether or not a draw is inside a clip element.
409         fOriginalBounds = drawBounds.makeInset(GrClip::kBoundsTolerance, GrClip::kBoundsTolerance);
410         if (fOriginalBounds.isEmpty()) {
411             fOriginalBounds = drawBounds;
412         }
413     }
414 
415     // Common clip type interface
op() const416     SkClipOp op() const { return SkClipOp::kIntersect; }
outerBounds() const417     const SkIRect& outerBounds() const { return fBounds; }
418 
419     // Draw does not have inner bounds so cannot contain anything.
contains(const RawElement & e) const420     bool contains(const RawElement& e) const { return false; }
contains(const SaveRecord & s) const421     bool contains(const SaveRecord& s) const { return false; }
422 
applyDeviceBounds(const SkIRect & deviceBounds)423     bool applyDeviceBounds(const SkIRect& deviceBounds) {
424         return fBounds.intersect(deviceBounds);
425     }
426 
bounds() const427     const SkRect& bounds() const { return fOriginalBounds; }
aa() const428     GrAA aa() const { return fAA; }
429 
430 private:
431     SkRect  fOriginalBounds;
432     SkIRect fBounds;
433     GrAA    fAA;
434 };
435 
436 ///////////////////////////////////////////////////////////////////////////////
437 // ClipStack::Element
438 
RawElement(const SkMatrix & localToDevice,const GrShape & shape,GrAA aa,SkClipOp op)439 ClipStack::RawElement::RawElement(const SkMatrix& localToDevice, const GrShape& shape,
440                                   GrAA aa, SkClipOp op)
441         : Element{shape, localToDevice, op, aa}
442         , fInnerBounds(SkIRect::MakeEmpty())
443         , fOuterBounds(SkIRect::MakeEmpty())
444         , fInvalidatedByIndex(-1) {
445     if (!localToDevice.invert(&fDeviceToLocal)) {
446         // If the transform can't be inverted, it means that two dimensions are collapsed to 0 or
447         // 1 dimension, making the device-space geometry effectively empty.
448         fShape.reset();
449     }
450 }
451 
markInvalid(const SaveRecord & current)452 void ClipStack::RawElement::markInvalid(const SaveRecord& current) {
453     SkASSERT(!this->isInvalid());
454     fInvalidatedByIndex = current.firstActiveElementIndex();
455 }
456 
restoreValid(const SaveRecord & current)457 void ClipStack::RawElement::restoreValid(const SaveRecord& current) {
458     if (current.firstActiveElementIndex() < fInvalidatedByIndex) {
459         fInvalidatedByIndex = -1;
460     }
461 }
462 
contains(const Draw & d) const463 bool ClipStack::RawElement::contains(const Draw& d) const {
464     if (fInnerBounds.contains(d.outerBounds())) {
465         return true;
466     } else {
467         // If the draw is non-AA, use the already computed outer bounds so we don't need to use
468         // device-space outsetting inside shape_contains_rect.
469         SkRect queryBounds = d.aa() == GrAA::kYes ? d.bounds() : SkRect::Make(d.outerBounds());
470         return shape_contains_rect(fShape, fLocalToDevice, fDeviceToLocal,
471                                    queryBounds, SkMatrix::I(), /* mixed-aa */ false);
472     }
473 }
474 
contains(const SaveRecord & s) const475 bool ClipStack::RawElement::contains(const SaveRecord& s) const {
476     if (fInnerBounds.contains(s.outerBounds())) {
477         return true;
478     } else {
479         // This is very similar to contains(Draw) but we just have outerBounds to work with.
480         SkRect queryBounds = SkRect::Make(s.outerBounds());
481         return shape_contains_rect(fShape, fLocalToDevice, fDeviceToLocal,
482                                    queryBounds, SkMatrix::I(), /* mixed-aa */ false);
483     }
484 }
485 
contains(const RawElement & e) const486 bool ClipStack::RawElement::contains(const RawElement& e) const {
487     // This is similar to how RawElement checks containment for a Draw, except that both the tester
488     // and testee have a transform that needs to be considered.
489     if (fInnerBounds.contains(e.fOuterBounds)) {
490         return true;
491     }
492 
493     bool mixedAA = fAA != e.fAA;
494     if (!mixedAA && fLocalToDevice == e.fLocalToDevice) {
495         // Test the shapes directly against each other, with a special check for a rrect+rrect
496         // containment (a intersect b == a implies b contains a) and paths (same gen ID, or same
497         // path for small paths means they contain each other).
498         static constexpr int kMaxPathComparePoints = 16;
499         if (fShape.isRRect() && e.fShape.isRRect()) {
500             return SkRRectPriv::ConservativeIntersect(fShape.rrect(), e.fShape.rrect())
501                     == e.fShape.rrect();
502         } else if (fShape.isPath() && e.fShape.isPath()) {
503             return fShape.path().getGenerationID() == e.fShape.path().getGenerationID() ||
504                    (fShape.path().getPoints(nullptr, 0) <= kMaxPathComparePoints &&
505                     fShape.path() == e.fShape.path());
506         } // else fall through to shape_contains_rect
507     }
508 
509     return shape_contains_rect(fShape, fLocalToDevice, fDeviceToLocal,
510                                e.fShape.bounds(), e.fLocalToDevice, mixedAA);
511 
512 }
513 
simplify(const SkIRect & deviceBounds,bool forceAA)514 void ClipStack::RawElement::simplify(const SkIRect& deviceBounds, bool forceAA) {
515     // Make sure the shape is not inverted. An inverted shape is equivalent to a non-inverted shape
516     // with the clip op toggled.
517     if (fShape.inverted()) {
518         fOp = fOp == SkClipOp::kIntersect ? SkClipOp::kDifference : SkClipOp::kIntersect;
519         fShape.setInverted(false);
520     }
521 
522     // Then simplify the base shape, if it becomes empty, no need to update the bounds
523     fShape.simplify();
524     SkASSERT(!fShape.inverted());
525     if (fShape.isEmpty()) {
526         return;
527     }
528 
529     // Lines and points should have been turned into empty since we assume everything is filled
530     SkASSERT(!fShape.isPoint() && !fShape.isLine());
531     // Validity check, we have no public API to create an arc at the moment
532     SkASSERT(!fShape.isArc());
533 
534     SkRect outer = fLocalToDevice.mapRect(fShape.bounds());
535     if (!outer.intersect(SkRect::Make(deviceBounds))) {
536         // A non-empty shape is offscreen, so treat it as empty
537         fShape.reset();
538         return;
539     }
540 
541     // Except for axis-aligned clip rects, upgrade to AA when forced. We skip axis-aligned clip
542     // rects because a non-AA axis aligned rect can always be set as just a scissor test or window
543     // rect, avoiding an expensive stencil mask generation.
544     if (forceAA && !(fShape.isRect() && fLocalToDevice.preservesAxisAlignment())) {
545         fAA = GrAA::kYes;
546     }
547 
548     // Except for non-AA axis-aligned rects, the outer bounds is the rounded-out device-space
549     // mapped bounds of the shape.
550     fOuterBounds = GrClip::GetPixelIBounds(outer, fAA, BoundsType::kExterior);
551 
552     if (fLocalToDevice.preservesAxisAlignment()) {
553         if (fShape.isRect()) {
554             // The actual geometry can be updated to the device-intersected bounds and we can
555             // know the inner bounds
556             fShape.rect() = outer;
557             fLocalToDevice.setIdentity();
558             fDeviceToLocal.setIdentity();
559 
560             if (fAA == GrAA::kNo && outer.width() >= 1.f && outer.height() >= 1.f) {
561                 // NOTE: Legacy behavior to avoid performance regressions. For non-aa axis-aligned
562                 // clip rects we always just round so that they can be scissor-only (avoiding the
563                 // uncertainty in how a GPU might actually round an edge on fractional coords).
564                 fOuterBounds = outer.round();
565                 fInnerBounds = fOuterBounds;
566             } else {
567                 fInnerBounds = GrClip::GetPixelIBounds(outer, fAA, BoundsType::kInterior);
568                 SkASSERT(fOuterBounds.contains(fInnerBounds) || fInnerBounds.isEmpty());
569             }
570         } else if (fShape.isRRect()) {
571             // Can't transform in place and must still check transform result since some very
572             // ill-formed scale+translate matrices can cause invalid rrect radii.
573             SkRRect src;
574             if (fShape.rrect().transform(fLocalToDevice, &src)) {
575                 fShape.rrect() = src;
576                 fLocalToDevice.setIdentity();
577                 fDeviceToLocal.setIdentity();
578 
579                 SkRect inner = SkRRectPriv::InnerBounds(fShape.rrect());
580                 fInnerBounds = GrClip::GetPixelIBounds(inner, fAA, BoundsType::kInterior);
581                 if (!fInnerBounds.intersect(deviceBounds)) {
582                     fInnerBounds = SkIRect::MakeEmpty();
583                 }
584             }
585         }
586     }
587 
588     if (fOuterBounds.isEmpty()) {
589         // This can happen if we have non-AA shapes smaller than a pixel that do not cover a pixel
590         // center. We could round out, but rasterization would still result in an empty clip.
591         fShape.reset();
592     }
593 
594     // Post-conditions on inner and outer bounds
595     SkASSERT(fShape.isEmpty() || (!fOuterBounds.isEmpty() && deviceBounds.contains(fOuterBounds)));
596     SkASSERT(fShape.isEmpty() || fInnerBounds.isEmpty() || fOuterBounds.contains(fInnerBounds));
597 }
598 
combine(const RawElement & other,const SaveRecord & current)599 bool ClipStack::RawElement::combine(const RawElement& other, const SaveRecord& current) {
600     // To reduce the number of possibilities, only consider intersect+intersect. Difference and
601     // mixed op cases could be analyzed to simplify one of the shapes, but that is a rare
602     // occurrence and the math is much more complicated.
603     if (other.fOp != SkClipOp::kIntersect || fOp != SkClipOp::kIntersect) {
604         return false;
605     }
606 
607     // At the moment, only rect+rect or rrect+rrect are supported (although rect+rrect is
608     // treated as a degenerate case of rrect+rrect).
609     bool shapeUpdated = false;
610     if (fShape.isRect() && other.fShape.isRect()) {
611         bool aaMatch = fAA == other.fAA;
612         if (fLocalToDevice.isIdentity() && other.fLocalToDevice.isIdentity() && !aaMatch) {
613             if (GrClip::IsPixelAligned(fShape.rect())) {
614                 // Our AA type doesn't really matter, take other's since its edges may not be
615                 // pixel aligned, so after intersection clip behavior should respect its aa type.
616                 fAA = other.fAA;
617             } else if (!GrClip::IsPixelAligned(other.fShape.rect())) {
618                 // Neither shape is pixel aligned and AA types don't match so can't combine
619                 return false;
620             }
621             // Either we've updated this->fAA to actually match, or other->fAA doesn't matter so
622             // this can be set to true. We just can't modify other to set it's aa to this->fAA.
623             // But since 'this' becomes the combo of the two, other will be deleted so that's fine.
624             aaMatch = true;
625         }
626 
627         if (aaMatch && fLocalToDevice == other.fLocalToDevice) {
628             if (!fShape.rect().intersect(other.fShape.rect())) {
629                 // By floating point, it turns out the combination should be empty
630                 this->fShape.reset();
631                 this->markInvalid(current);
632                 return true;
633             }
634             shapeUpdated = true;
635         }
636     } else if ((fShape.isRect() || fShape.isRRect()) &&
637                (other.fShape.isRect() || other.fShape.isRRect())) {
638         // No such pixel-aligned disregard for AA for round rects
639         if (fAA == other.fAA && fLocalToDevice == other.fLocalToDevice) {
640             // Treat rrect+rect intersections as rrect+rrect
641             SkRRect a = fShape.isRect() ? SkRRect::MakeRect(fShape.rect()) : fShape.rrect();
642             SkRRect b = other.fShape.isRect() ? SkRRect::MakeRect(other.fShape.rect())
643                                               : other.fShape.rrect();
644 
645             SkRRect joined = SkRRectPriv::ConservativeIntersect(a, b);
646             if (!joined.isEmpty()) {
647                 // Can reduce to a single element
648                 if (joined.isRect()) {
649                     // And with a simplified type
650                     fShape.setRect(joined.rect());
651                 } else {
652                     fShape.setRRect(joined);
653                 }
654                 shapeUpdated = true;
655             } else if (!a.getBounds().intersects(b.getBounds())) {
656                 // Like the rect+rect combination, the intersection is actually empty
657                 fShape.reset();
658                 this->markInvalid(current);
659                 return true;
660             }
661         }
662     }
663 
664     if (shapeUpdated) {
665         // This logic works under the assumption that both combined elements were intersect, so we
666         // don't do the full bounds computations like in simplify().
667         SkASSERT(fOp == SkClipOp::kIntersect && other.fOp == SkClipOp::kIntersect);
668         SkAssertResult(fOuterBounds.intersect(other.fOuterBounds));
669         if (!fInnerBounds.intersect(other.fInnerBounds)) {
670             fInnerBounds = SkIRect::MakeEmpty();
671         }
672         return true;
673     } else {
674         return false;
675     }
676 }
677 
updateForElement(RawElement * added,const SaveRecord & current)678 void ClipStack::RawElement::updateForElement(RawElement* added, const SaveRecord& current) {
679     if (this->isInvalid()) {
680         // Already doesn't do anything, so skip this element
681         return;
682     }
683 
684     // 'A' refers to this element, 'B' refers to 'added'.
685     switch (get_clip_geometry(*this, *added)) {
686         case ClipGeometry::kEmpty:
687             // Mark both elements as invalid to signal that the clip is fully empty
688             this->markInvalid(current);
689             added->markInvalid(current);
690             break;
691 
692         case ClipGeometry::kAOnly:
693             // This element already clips more than 'added', so mark 'added' is invalid to skip it
694             added->markInvalid(current);
695             break;
696 
697         case ClipGeometry::kBOnly:
698             // 'added' clips more than this element, so mark this as invalid
699             this->markInvalid(current);
700             break;
701 
702         case ClipGeometry::kBoth:
703             // Else the bounds checks think we need to keep both, but depending on the combination
704             // of the ops and shape kinds, we may be able to do better.
705             if (added->combine(*this, current)) {
706                 // 'added' now fully represents the combination of the two elements
707                 this->markInvalid(current);
708             }
709             break;
710     }
711 }
712 
clipType() const713 ClipStack::ClipState ClipStack::RawElement::clipType() const {
714     // Map from the internal shape kind to the clip state enum
715     switch (fShape.type()) {
716         case GrShape::Type::kEmpty:
717             return ClipState::kEmpty;
718 
719         case GrShape::Type::kRect:
720             return fOp == SkClipOp::kIntersect && fLocalToDevice.isIdentity()
721                     ? ClipState::kDeviceRect : ClipState::kComplex;
722 
723         case GrShape::Type::kRRect:
724             return fOp == SkClipOp::kIntersect && fLocalToDevice.isIdentity()
725                     ? ClipState::kDeviceRRect : ClipState::kComplex;
726 
727         case GrShape::Type::kArc:
728         case GrShape::Type::kLine:
729         case GrShape::Type::kPoint:
730             // These types should never become RawElements
731             SkASSERT(false);
732             [[fallthrough]];
733 
734         case GrShape::Type::kPath:
735             return ClipState::kComplex;
736     }
737     SkUNREACHABLE;
738 }
739 
740 ///////////////////////////////////////////////////////////////////////////////
741 // ClipStack::Mask
742 
Mask(const SaveRecord & current,const SkIRect & drawBounds)743 ClipStack::Mask::Mask(const SaveRecord& current, const SkIRect& drawBounds)
744         : fBounds(drawBounds)
745         , fGenID(current.genID()) {
746     static const UniqueKey::Domain kDomain = UniqueKey::GenerateDomain();
747 
748     // The gen ID should not be invalid, empty, or wide open, since those do not require masks
749     SkASSERT(fGenID != kInvalidGenID && fGenID != kEmptyGenID && fGenID != kWideOpenGenID);
750 
751     UniqueKey::Builder builder(&fKey, kDomain, 5, "clip_mask");
752     builder[0] = fGenID;
753     builder[1] = drawBounds.fLeft;
754     builder[2] = drawBounds.fRight;
755     builder[3] = drawBounds.fTop;
756     builder[4] = drawBounds.fBottom;
757     SkASSERT(fKey.isValid());
758 
759     SkDEBUGCODE(fOwner = &current;)
760 }
761 
appliesToDraw(const SaveRecord & current,const SkIRect & drawBounds) const762 bool ClipStack::Mask::appliesToDraw(const SaveRecord& current, const SkIRect& drawBounds) const {
763     // For the same save record, a larger mask will have the same or more elements
764     // baked into it, so it can be reused to clip the smaller draw.
765     SkASSERT(fGenID != current.genID() || &current == fOwner);
766     return fGenID == current.genID() && fBounds.contains(drawBounds);
767 }
768 
invalidate(GrProxyProvider * proxyProvider)769 void ClipStack::Mask::invalidate(GrProxyProvider* proxyProvider) {
770     SkASSERT(proxyProvider);
771     SkASSERT(fKey.isValid()); // Should only be invalidated once
772     proxyProvider->processInvalidUniqueKey(
773             fKey, nullptr, GrProxyProvider::InvalidateGPUResource::kYes);
774     fKey.reset();
775 }
776 
777 ///////////////////////////////////////////////////////////////////////////////
778 // ClipStack::SaveRecord
779 
SaveRecord(const SkIRect & deviceBounds)780 ClipStack::SaveRecord::SaveRecord(const SkIRect& deviceBounds)
781         : fInnerBounds(deviceBounds)
782         , fOuterBounds(deviceBounds)
783         , fShader(nullptr)
784         , fStartingMaskIndex(0)
785         , fStartingElementIndex(0)
786         , fOldestValidIndex(0)
787         , fDeferredSaveCount(0)
788         , fStackOp(SkClipOp::kIntersect)
789         , fState(ClipState::kWideOpen)
790         , fGenID(kInvalidGenID) {}
791 
SaveRecord(const SaveRecord & prior,int startingMaskIndex,int startingElementIndex)792 ClipStack::SaveRecord::SaveRecord(const SaveRecord& prior,
793                                   int startingMaskIndex,
794                                   int startingElementIndex)
795         : fInnerBounds(prior.fInnerBounds)
796         , fOuterBounds(prior.fOuterBounds)
797         , fShader(prior.fShader)
798         , fStartingMaskIndex(startingMaskIndex)
799         , fStartingElementIndex(startingElementIndex)
800         , fOldestValidIndex(prior.fOldestValidIndex)
801         , fDeferredSaveCount(0)
802         , fStackOp(prior.fStackOp)
803         , fState(prior.fState)
804         , fGenID(kInvalidGenID) {
805     // If the prior record never needed a mask, this one will insert into the same index
806     // (that's okay since we'll remove it when this record is popped off the stack).
807     SkASSERT(startingMaskIndex >= prior.fStartingMaskIndex);
808     // The same goes for elements (the prior could have been wide open).
809     SkASSERT(startingElementIndex >= prior.fStartingElementIndex);
810 }
811 
genID() const812 uint32_t ClipStack::SaveRecord::genID() const {
813     if (fState == ClipState::kEmpty) {
814         return kEmptyGenID;
815     } else if (fState == ClipState::kWideOpen) {
816         return kWideOpenGenID;
817     } else {
818         // The gen ID shouldn't be empty or wide open, since they are reserved for the above
819         // if-cases. It may be kInvalid if the record hasn't had any elements added to it yet.
820         SkASSERT(fGenID != kEmptyGenID && fGenID != kWideOpenGenID);
821         return fGenID;
822     }
823 }
824 
state() const825 ClipStack::ClipState ClipStack::SaveRecord::state() const {
826     if (fShader && fState != ClipState::kEmpty) {
827         return ClipState::kComplex;
828     } else {
829         return fState;
830     }
831 }
832 
contains(const ClipStack::Draw & draw) const833 bool ClipStack::SaveRecord::contains(const ClipStack::Draw& draw) const {
834     return fInnerBounds.contains(draw.outerBounds());
835 }
836 
contains(const ClipStack::RawElement & element) const837 bool ClipStack::SaveRecord::contains(const ClipStack::RawElement& element) const {
838     return fInnerBounds.contains(element.outerBounds());
839 }
840 
removeElements(RawElement::Stack * elements)841 void ClipStack::SaveRecord::removeElements(RawElement::Stack* elements) {
842     while (elements->count() > fStartingElementIndex) {
843         elements->pop_back();
844     }
845 }
846 
restoreElements(RawElement::Stack * elements)847 void ClipStack::SaveRecord::restoreElements(RawElement::Stack* elements) {
848     // Presumably this SaveRecord is the new top of the stack, and so it owns the elements
849     // from its starting index to restoreCount - 1. Elements from the old save record have
850     // been destroyed already, so their indices would have been >= restoreCount, and any
851     // still-present element can be un-invalidated based on that.
852     int i = elements->count() - 1;
853     for (RawElement& e : elements->ritems()) {
854         if (i < fOldestValidIndex) {
855             break;
856         }
857         e.restoreValid(*this);
858         --i;
859     }
860 }
861 
invalidateMasks(GrProxyProvider * proxyProvider,Mask::Stack * masks)862 void ClipStack::SaveRecord::invalidateMasks(GrProxyProvider* proxyProvider,
863                                             Mask::Stack* masks) {
864     // Must explicitly invalidate the key before removing the mask object from the stack
865     while (masks->count() > fStartingMaskIndex) {
866         SkASSERT(masks->back().owner() == this && proxyProvider);
867         masks->back().invalidate(proxyProvider);
868         masks->pop_back();
869     }
870     SkASSERT(masks->empty() || masks->back().genID() != fGenID);
871 }
872 
reset(const SkIRect & bounds)873 void ClipStack::SaveRecord::reset(const SkIRect& bounds) {
874     SkASSERT(this->canBeUpdated());
875     fOldestValidIndex = fStartingElementIndex;
876     fOuterBounds = bounds;
877     fInnerBounds = bounds;
878     fStackOp = SkClipOp::kIntersect;
879     fState = ClipState::kWideOpen;
880     fShader = nullptr;
881 }
882 
addShader(sk_sp<SkShader> shader)883 void ClipStack::SaveRecord::addShader(sk_sp<SkShader> shader) {
884     SkASSERT(shader);
885     SkASSERT(this->canBeUpdated());
886     if (!fShader) {
887         fShader = std::move(shader);
888     } else {
889         // The total coverage is computed by multiplying the coverage from each element (shape or
890         // shader), but since multiplication is associative, we can use kSrcIn blending to make
891         // a new shader that represents 'shader' * 'fShader'
892         fShader = SkShaders::Blend(SkBlendMode::kSrcIn, std::move(shader), fShader);
893     }
894 }
895 
addElement(RawElement && toAdd,RawElement::Stack * elements)896 bool ClipStack::SaveRecord::addElement(RawElement&& toAdd, RawElement::Stack* elements) {
897     // Validity check the element's state first; if the shape class isn't empty, the outer bounds
898     // shouldn't be empty; if the inner bounds are not empty, they must be contained in outer.
899     SkASSERT((toAdd.shape().isEmpty() || !toAdd.outerBounds().isEmpty()) &&
900              (toAdd.innerBounds().isEmpty() || toAdd.outerBounds().contains(toAdd.innerBounds())));
901     // And we shouldn't be adding an element if we have a deferred save
902     SkASSERT(this->canBeUpdated());
903 
904     if (fState == ClipState::kEmpty) {
905         // The clip is already empty, and we only shrink, so there's no need to record this element.
906         return false;
907     } else if (toAdd.shape().isEmpty()) {
908         // An empty difference op should have been detected earlier, since it's a no-op
909         SkASSERT(toAdd.op() == SkClipOp::kIntersect);
910         fState = ClipState::kEmpty;
911         return true;
912     }
913 
914     // In this invocation, 'A' refers to the existing stack's bounds and 'B' refers to the new
915     // element.
916     switch (get_clip_geometry(*this, toAdd)) {
917         case ClipGeometry::kEmpty:
918             // The combination results in an empty clip
919             fState = ClipState::kEmpty;
920             return true;
921 
922         case ClipGeometry::kAOnly:
923             // The combination would not be any different than the existing clip
924             return false;
925 
926         case ClipGeometry::kBOnly:
927             // The combination would invalidate the entire existing stack and can be replaced with
928             // just the new element.
929             this->replaceWithElement(std::move(toAdd), elements);
930             return true;
931 
932         case ClipGeometry::kBoth:
933             // The new element combines in a complex manner, so update the stack's bounds based on
934             // the combination of its and the new element's ops (handled below)
935             break;
936     }
937 
938     if (fState == ClipState::kWideOpen) {
939         // When the stack was wide open and the clip effect was kBoth, the "complex" manner is
940         // simply to keep the element and update the stack bounds to be the element's intersected
941         // with the device.
942         this->replaceWithElement(std::move(toAdd), elements);
943         return true;
944     }
945 
946     // Some form of actual clip element(s) to combine with.
947     if (fStackOp == SkClipOp::kIntersect) {
948         if (toAdd.op() == SkClipOp::kIntersect) {
949             // Intersect (stack) + Intersect (toAdd)
950             //  - Bounds updates is simply the paired intersections of outer and inner.
951             SkAssertResult(fOuterBounds.intersect(toAdd.outerBounds()));
952             if (!fInnerBounds.intersect(toAdd.innerBounds())) {
953                 // NOTE: this does the right thing if either rect is empty, since we set the
954                 // inner bounds to empty here
955                 fInnerBounds = SkIRect::MakeEmpty();
956             }
957         } else {
958             // Intersect (stack) + Difference (toAdd)
959             //  - Shrink the stack's outer bounds if the difference op's inner bounds completely
960             //    cuts off an edge.
961             //  - Shrink the stack's inner bounds to completely exclude the op's outer bounds.
962             fOuterBounds = subtract(fOuterBounds, toAdd.innerBounds(), /* exact */ true);
963             fInnerBounds = subtract(fInnerBounds, toAdd.outerBounds(), /* exact */ false);
964         }
965     } else {
966         if (toAdd.op() == SkClipOp::kIntersect) {
967             // Difference (stack) + Intersect (toAdd)
968             //  - Bounds updates are just the mirror of Intersect(stack) + Difference(toAdd)
969             SkIRect oldOuter = fOuterBounds;
970             fOuterBounds = subtract(toAdd.outerBounds(), fInnerBounds, /* exact */ true);
971             fInnerBounds = subtract(toAdd.innerBounds(), oldOuter,     /* exact */ false);
972         } else {
973             // Difference (stack) + Difference (toAdd)
974             //  - The updated outer bounds is the union of outer bounds and the inner becomes the
975             //    largest of the two possible inner bounds
976             fOuterBounds.join(toAdd.outerBounds());
977             if (toAdd.innerBounds().width() * toAdd.innerBounds().height() >
978                 fInnerBounds.width() * fInnerBounds.height()) {
979                 fInnerBounds = toAdd.innerBounds();
980             }
981         }
982     }
983 
984     // If we get here, we're keeping the new element and the stack's bounds have been updated.
985     // We ought to have caught the cases where the stack bounds resemble an empty or wide open
986     // clip, so assert that's the case.
987     SkASSERT(!fOuterBounds.isEmpty() &&
988              (fInnerBounds.isEmpty() || fOuterBounds.contains(fInnerBounds)));
989 
990     return this->appendElement(std::move(toAdd), elements);
991 }
992 
appendElement(RawElement && toAdd,RawElement::Stack * elements)993 bool ClipStack::SaveRecord::appendElement(RawElement&& toAdd, RawElement::Stack* elements) {
994     // Update past elements to account for the new element
995     int i = elements->count() - 1;
996 
997     // After the loop, elements between [max(youngestValid, startingIndex)+1, count-1] can be
998     // removed from the stack (these are the active elements that have been invalidated by the
999     // newest element; since it's the active part of the stack, no restore() can bring them back).
1000     int youngestValid = fStartingElementIndex - 1;
1001     // After the loop, elements between [0, oldestValid-1] are all invalid. The value of oldestValid
1002     // becomes the save record's new fLastValidIndex value.
1003     int oldestValid = elements->count();
1004     // After the loop, this is the earliest active element that was invalidated. It may be
1005     // older in the stack than earliestValid, so cannot be popped off, but can be used to store
1006     // the new element instead of allocating more.
1007     RawElement* oldestActiveInvalid = nullptr;
1008     int oldestActiveInvalidIndex = elements->count();
1009 
1010     for (RawElement& existing : elements->ritems()) {
1011         if (i < fOldestValidIndex) {
1012             break;
1013         }
1014         // We don't need to pass the actual index that toAdd will be saved to; just the minimum
1015         // index of this save record, since that will result in the same restoration behavior later.
1016         existing.updateForElement(&toAdd, *this);
1017 
1018         if (toAdd.isInvalid()) {
1019             if (existing.isInvalid()) {
1020                 // Both new and old invalid implies the entire clip becomes empty
1021                 fState = ClipState::kEmpty;
1022                 return true;
1023             } else {
1024                 // The new element doesn't change the clip beyond what the old element already does
1025                 return false;
1026             }
1027         } else if (existing.isInvalid()) {
1028             // The new element cancels out the old element. The new element may have been modified
1029             // to account for the old element's geometry.
1030             if (i >= fStartingElementIndex) {
1031                 // Still active, so the invalidated index could be used to store the new element
1032                 oldestActiveInvalid = &existing;
1033                 oldestActiveInvalidIndex = i;
1034             }
1035         } else {
1036             // Keep both new and old elements
1037             oldestValid = i;
1038             if (i > youngestValid) {
1039                 youngestValid = i;
1040             }
1041         }
1042 
1043         --i;
1044     }
1045 
1046     // Post-iteration validity check
1047     SkASSERT(oldestValid == elements->count() ||
1048              (oldestValid >= fOldestValidIndex && oldestValid < elements->count()));
1049     SkASSERT(youngestValid == fStartingElementIndex - 1 ||
1050              (youngestValid >= fStartingElementIndex && youngestValid < elements->count()));
1051     SkASSERT((oldestActiveInvalid && oldestActiveInvalidIndex >= fStartingElementIndex &&
1052               oldestActiveInvalidIndex < elements->count()) || !oldestActiveInvalid);
1053 
1054     // Update final state
1055     SkASSERT(oldestValid >= fOldestValidIndex);
1056     fOldestValidIndex = std::min(oldestValid, oldestActiveInvalidIndex);
1057     fState = oldestValid == elements->count() ? toAdd.clipType() : ClipState::kComplex;
1058     if (fStackOp == SkClipOp::kDifference && toAdd.op() == SkClipOp::kIntersect) {
1059         // The stack remains in difference mode only as long as all elements are difference
1060         fStackOp = SkClipOp::kIntersect;
1061     }
1062 
1063     int targetCount = youngestValid + 1;
1064     if (!oldestActiveInvalid || oldestActiveInvalidIndex >= targetCount) {
1065         // toAdd will be stored right after youngestValid
1066         targetCount++;
1067         oldestActiveInvalid = nullptr;
1068     }
1069     while (elements->count() > targetCount) {
1070         SkASSERT(oldestActiveInvalid != &elements->back()); // shouldn't delete what we'll reuse
1071         elements->pop_back();
1072     }
1073     if (oldestActiveInvalid) {
1074         *oldestActiveInvalid = std::move(toAdd);
1075     } else if (elements->count() < targetCount) {
1076         elements->push_back(std::move(toAdd));
1077     } else {
1078         elements->back() = std::move(toAdd);
1079     }
1080 
1081     // Changing this will prompt ClipStack to invalidate any masks associated with this record.
1082     fGenID = next_gen_id();
1083     return true;
1084 }
1085 
replaceWithElement(RawElement && toAdd,RawElement::Stack * elements)1086 void ClipStack::SaveRecord::replaceWithElement(RawElement&& toAdd, RawElement::Stack* elements) {
1087     // The aggregate state of the save record mirrors the element
1088     fInnerBounds = toAdd.innerBounds();
1089     fOuterBounds = toAdd.outerBounds();
1090     fStackOp = toAdd.op();
1091     fState = toAdd.clipType();
1092 
1093     // All prior active element can be removed from the stack: [startingIndex, count - 1]
1094     int targetCount = fStartingElementIndex + 1;
1095     while (elements->count() > targetCount) {
1096         elements->pop_back();
1097     }
1098     if (elements->count() < targetCount) {
1099         elements->push_back(std::move(toAdd));
1100     } else {
1101         elements->back() = std::move(toAdd);
1102     }
1103 
1104     SkASSERT(elements->count() == fStartingElementIndex + 1);
1105 
1106     // This invalidates all older elements that are owned by save records lower in the clip stack.
1107     fOldestValidIndex = fStartingElementIndex;
1108     fGenID = next_gen_id();
1109 }
1110 
1111 ///////////////////////////////////////////////////////////////////////////////
1112 // ClipStack
1113 
1114 // NOTE: Based on draw calls in all GMs, SKPs, and SVGs as of 08/20, 98% use a clip stack with
1115 // one Element and up to two SaveRecords, thus the inline size for RawElement::Stack and
1116 // SaveRecord::Stack (this conveniently keeps the size of ClipStack manageable). The max
1117 // encountered element stack depth was 5 and the max save depth was 6. Using an increment of 8 for
1118 // these stacks means that clip management will incur a single allocation for the remaining 2%
1119 // of the draws, with extra head room for more complex clips encountered in the wild.
1120 //
1121 // The mask stack increment size was chosen to be smaller since only 0.2% of the evaluated draw call
1122 // set ever used a mask (which includes stencil masks), or up to 0.3% when the atlas is disabled.
1123 static constexpr int kElementStackIncrement = 8;
1124 static constexpr int kSaveStackIncrement = 8;
1125 static constexpr int kMaskStackIncrement = 4;
1126 
1127 // And from this same draw call set, the most complex clip could only use 5 analytic coverage FPs.
1128 // Historically we limited it to 4 based on Blink's call pattern, so we keep the limit as-is since
1129 // it's so close to the empirically encountered max.
1130 static constexpr int kMaxAnalyticFPs = 4;
1131 // The number of stack-allocated mask pointers to store before extending the arrays.
1132 // Stack size determined empirically, the maximum number of elements put in a SW mask was 4
1133 // across our set of GMs, SKPs, and SVGs used for testing.
1134 static constexpr int kNumStackMasks = 4;
1135 
ClipStack(const SkIRect & deviceBounds,const SkMatrixProvider * matrixProvider,bool forceAA)1136 ClipStack::ClipStack(const SkIRect& deviceBounds, const SkMatrixProvider* matrixProvider,
1137                      bool forceAA)
1138         : fElements(kElementStackIncrement)
1139         , fSaves(kSaveStackIncrement)
1140         , fMasks(kMaskStackIncrement)
1141         , fProxyProvider(nullptr)
1142         , fDeviceBounds(deviceBounds)
1143         , fMatrixProvider(matrixProvider)
1144         , fForceAA(forceAA) {
1145     // Start with a save record that is wide open
1146     fSaves.emplace_back(deviceBounds);
1147 }
1148 
~ClipStack()1149 ClipStack::~ClipStack() {
1150     // Invalidate all mask keys that remain. Since we're tearing the clip stack down, we don't need
1151     // to go through SaveRecord.
1152     SkASSERT(fProxyProvider || fMasks.empty());
1153     if (fProxyProvider) {
1154         for (Mask& m : fMasks.ritems()) {
1155             m.invalidate(fProxyProvider);
1156         }
1157     }
1158 }
1159 
save()1160 void ClipStack::save() {
1161     SkASSERT(!fSaves.empty());
1162     fSaves.back().pushSave();
1163 }
1164 
restore()1165 void ClipStack::restore() {
1166     SkASSERT(!fSaves.empty());
1167     SaveRecord& current = fSaves.back();
1168     if (current.popSave()) {
1169         // This was just a deferred save being undone, so the record doesn't need to be removed yet
1170         return;
1171     }
1172 
1173     // When we remove a save record, we delete all elements >= its starting index and any masks
1174     // that were rasterized for it.
1175     current.removeElements(&fElements);
1176     SkASSERT(fProxyProvider || fMasks.empty());
1177     if (fProxyProvider) {
1178         current.invalidateMasks(fProxyProvider, &fMasks);
1179     }
1180     fSaves.pop_back();
1181     // Restore any remaining elements that were only invalidated by the now-removed save record.
1182     fSaves.back().restoreElements(&fElements);
1183 }
1184 
getConservativeBounds() const1185 SkIRect ClipStack::getConservativeBounds() const {
1186     const SaveRecord& current = this->currentSaveRecord();
1187     if (current.state() == ClipState::kEmpty) {
1188         return SkIRect::MakeEmpty();
1189     } else if (current.state() == ClipState::kWideOpen) {
1190         return fDeviceBounds;
1191     } else {
1192         if (current.op() == SkClipOp::kDifference) {
1193             // The outer/inner bounds represent what's cut out, so full bounds remains the device
1194             // bounds, minus any fully clipped content that spans the device edge.
1195             return subtract(fDeviceBounds, current.innerBounds(), /* exact */ true);
1196         } else {
1197             SkASSERT(fDeviceBounds.contains(current.outerBounds()));
1198             return current.outerBounds();
1199         }
1200     }
1201 }
1202 
preApply(const SkRect & bounds,GrAA aa) const1203 GrClip::PreClipResult ClipStack::preApply(const SkRect& bounds, GrAA aa) const {
1204     Draw draw(bounds, fForceAA ? GrAA::kYes : aa);
1205     if (!draw.applyDeviceBounds(fDeviceBounds)) {
1206         return GrClip::Effect::kClippedOut;
1207     }
1208 
1209     const SaveRecord& cs = this->currentSaveRecord();
1210     // Early out if we know a priori that the clip is full 0s or full 1s.
1211     if (cs.state() == ClipState::kEmpty) {
1212         return GrClip::Effect::kClippedOut;
1213     } else if (cs.state() == ClipState::kWideOpen) {
1214         SkASSERT(!cs.shader());
1215         return GrClip::Effect::kUnclipped;
1216     }
1217 
1218     // Given argument order, 'A' == current clip, 'B' == draw
1219     switch (get_clip_geometry(cs, draw)) {
1220         case ClipGeometry::kEmpty:
1221             // Can ignore the shader since the geometry removed everything already
1222             return GrClip::Effect::kClippedOut;
1223 
1224         case ClipGeometry::kBOnly:
1225             // Geometrically, the draw is unclipped, but can't ignore a shader
1226             return cs.shader() ? GrClip::Effect::kClipped : GrClip::Effect::kUnclipped;
1227 
1228         case ClipGeometry::kAOnly:
1229             // Shouldn't happen since the inner bounds of a draw are unknown
1230             SkASSERT(false);
1231             // But if it did, it technically means the draw covered the clip and should be
1232             // considered kClipped or similar, which is what the next case handles.
1233             [[fallthrough]];
1234 
1235         case ClipGeometry::kBoth: {
1236             SkASSERT(fElements.count() > 0);
1237             const RawElement& back = fElements.back();
1238             if (cs.state() == ClipState::kDeviceRect) {
1239                 SkASSERT(back.clipType() == ClipState::kDeviceRect);
1240                 return {back.shape().rect(), back.aa()};
1241             } else if (cs.state() == ClipState::kDeviceRRect) {
1242                 SkASSERT(back.clipType() == ClipState::kDeviceRRect);
1243                 return {back.shape().rrect(), back.aa()};
1244             } else {
1245                 // The clip stack has complex shapes, multiple elements, or a shader; we could
1246                 // iterate per element like we would in apply(), but preApply() is meant to be
1247                 // conservative and efficient.
1248                 SkASSERT(cs.state() == ClipState::kComplex);
1249                 return GrClip::Effect::kClipped;
1250             }
1251         }
1252     }
1253 
1254     SkUNREACHABLE;
1255 }
1256 
apply(GrRecordingContext * rContext,SurfaceDrawContext * sdc,GrDrawOp * op,GrAAType aa,GrAppliedClip * out,SkRect * bounds) const1257 GrClip::Effect ClipStack::apply(GrRecordingContext* rContext,
1258                                 SurfaceDrawContext* sdc,
1259                                 GrDrawOp* op,
1260                                 GrAAType aa,
1261                                 GrAppliedClip* out,
1262                                 SkRect* bounds) const {
1263     // TODO: Once we no longer store SW masks, we don't need to sneak the provider in like this
1264     if (!fProxyProvider) {
1265         fProxyProvider = rContext->priv().proxyProvider();
1266     }
1267     SkASSERT(fProxyProvider == rContext->priv().proxyProvider());
1268     const GrCaps* caps = rContext->priv().caps();
1269 
1270     // Convert the bounds to a Draw and apply device bounds clipping, making our query as tight
1271     // as possible.
1272     Draw draw(*bounds, GrAA(fForceAA || aa != GrAAType::kNone));
1273     if (!draw.applyDeviceBounds(fDeviceBounds)) {
1274         return Effect::kClippedOut;
1275     }
1276     SkAssertResult(bounds->intersect(SkRect::Make(fDeviceBounds)));
1277 
1278     const SaveRecord& cs = this->currentSaveRecord();
1279     // Early out if we know a priori that the clip is full 0s or full 1s.
1280     if (cs.state() == ClipState::kEmpty) {
1281         return Effect::kClippedOut;
1282     } else if (cs.state() == ClipState::kWideOpen) {
1283         SkASSERT(!cs.shader());
1284         return Effect::kUnclipped;
1285     }
1286 
1287     // Convert any clip shader first, since it's not geometrically related to the draw bounds
1288     std::unique_ptr<GrFragmentProcessor> clipFP = nullptr;
1289     if (cs.shader()) {
1290         static const GrColorInfo kCoverageColorInfo{GrColorType::kUnknown, kPremul_SkAlphaType,
1291                                                     nullptr};
1292         GrFPArgs args(rContext, *fMatrixProvider, &kCoverageColorInfo);
1293         clipFP = as_SB(cs.shader())->asFragmentProcessor(args);
1294         if (clipFP) {
1295             // The initial input is the coverage from the geometry processor, so this ensures it
1296             // is multiplied properly with the alpha of the clip shader.
1297             clipFP = GrFragmentProcessor::MulInputByChildAlpha(std::move(clipFP));
1298         }
1299     }
1300 
1301     // A refers to the entire clip stack, B refers to the draw
1302     switch (get_clip_geometry(cs, draw)) {
1303         case ClipGeometry::kEmpty:
1304             return Effect::kClippedOut;
1305 
1306         case ClipGeometry::kBOnly:
1307             // Geometrically unclipped, but may need to add the shader as a coverage FP
1308             if (clipFP) {
1309                 out->addCoverageFP(std::move(clipFP));
1310                 return Effect::kClipped;
1311             } else {
1312                 return Effect::kUnclipped;
1313             }
1314 
1315         case ClipGeometry::kAOnly:
1316             // Shouldn't happen since draws don't report inner bounds
1317             SkASSERT(false);
1318             [[fallthrough]];
1319 
1320         case ClipGeometry::kBoth:
1321             // The draw is combined with the saved clip elements; the below logic tries to skip
1322             // as many elements as possible.
1323             SkASSERT(cs.state() == ClipState::kDeviceRect ||
1324                      cs.state() == ClipState::kDeviceRRect ||
1325                      cs.state() == ClipState::kComplex);
1326             break;
1327     }
1328 
1329     // We can determine a scissor based on the draw and the overall stack bounds.
1330     SkIRect scissorBounds;
1331     if (cs.op() == SkClipOp::kIntersect) {
1332         // Initially we keep this as large as possible; if the clip is applied solely with coverage
1333         // FPs then using a loose scissor increases the chance we can batch the draws.
1334         // We tighten it later if any form of mask or atlas element is needed.
1335         scissorBounds = cs.outerBounds();
1336     } else {
1337         scissorBounds = subtract(draw.outerBounds(), cs.innerBounds(), /* exact */ true);
1338     }
1339 
1340     // We mark this true once we have a coverage FP (since complex clipping is occurring), or we
1341     // have an element that wouldn't affect the scissored draw bounds, but does affect the regular
1342     // draw bounds. In that case, the scissor is sufficient for clipping and we can skip the
1343     // element but definitely cannot then drop the scissor.
1344     bool scissorIsNeeded = SkToBool(cs.shader());
1345     SkDEBUGCODE(bool opClippedInternally = false;)
1346 
1347     int remainingAnalyticFPs = kMaxAnalyticFPs;
1348 
1349     // If window rectangles are supported, we can use them to exclude inner bounds of difference ops
1350     int maxWindowRectangles = sdc->maxWindowRectangles();
1351     GrWindowRectangles windowRects;
1352 
1353     // Elements not represented as an analytic FP or skipped will be collected here and later
1354     // applied by using the stencil buffer or a cached SW mask.
1355     SkSTArray<kNumStackMasks, const Element*> elementsForMask;
1356 
1357     bool maskRequiresAA = false;
1358     auto atlasPathRenderer = rContext->priv().drawingManager()->getAtlasPathRenderer();
1359 
1360     int i = fElements.count();
1361     for (const RawElement& e : fElements.ritems()) {
1362         --i;
1363         if (i < cs.oldestElementIndex()) {
1364             // All earlier elements have been invalidated by elements already processed
1365             break;
1366         } else if (e.isInvalid()) {
1367             continue;
1368         }
1369 
1370         switch (get_clip_geometry(e, draw)) {
1371             case ClipGeometry::kEmpty:
1372                 // This can happen for difference op elements that have a larger fInnerBounds than
1373                 // can be preserved at the next level.
1374                 return Effect::kClippedOut;
1375 
1376             case ClipGeometry::kBOnly:
1377                 // We don't need to produce a coverage FP or mask for the element
1378                 break;
1379 
1380             case ClipGeometry::kAOnly:
1381                 // Shouldn't happen for draws, fall through to regular element processing
1382                 SkASSERT(false);
1383                 [[fallthrough]];
1384 
1385             case ClipGeometry::kBoth: {
1386                 // The element must apply coverage to the draw, enable the scissor to limit overdraw
1387                 scissorIsNeeded = true;
1388 
1389                 // First apply using HW methods (scissor and window rects). When the inner and outer
1390                 // bounds match, nothing else needs to be done.
1391                 bool fullyApplied = false;
1392 
1393                 // First check if the op knows how to apply this clip internally.
1394                 SkASSERT(!e.shape().inverted());
1395                 auto result = op->clipToShape(sdc, e.op(), e.localToDevice(), e.shape(),
1396                                               GrAA(e.aa() == GrAA::kYes || fForceAA));
1397                 if (result != GrDrawOp::ClipResult::kFail) {
1398                     if (result == GrDrawOp::ClipResult::kClippedOut) {
1399                         return Effect::kClippedOut;
1400                     }
1401                     if (result == GrDrawOp::ClipResult::kClippedGeometrically) {
1402                         // The op clipped its own geometry. Tighten the draw bounds.
1403                         bounds->intersect(SkRect::Make(e.outerBounds()));
1404                     }
1405                     fullyApplied = true;
1406                     SkDEBUGCODE(opClippedInternally = true;)
1407                 }
1408 
1409                 if (!fullyApplied) {
1410                     if (e.op() == SkClipOp::kIntersect) {
1411                         // The second test allows clipped draws that are scissored by multiple
1412                         // elements to remain scissor-only.
1413                         fullyApplied = e.innerBounds() == e.outerBounds() ||
1414                                        e.innerBounds().contains(scissorBounds);
1415                     } else {
1416                         if (!e.innerBounds().isEmpty() &&
1417                             windowRects.count() < maxWindowRectangles) {
1418                             // TODO: If we have more difference ops than available window rects, we
1419                             // should prioritize those with the largest inner bounds.
1420                             windowRects.addWindow(e.innerBounds());
1421                             fullyApplied = e.innerBounds() == e.outerBounds();
1422                         }
1423                     }
1424                 }
1425 
1426                 if (!fullyApplied && remainingAnalyticFPs > 0) {
1427                     std::tie(fullyApplied, clipFP) = analytic_clip_fp(e.asElement(),
1428                                                                       *caps->shaderCaps(),
1429                                                                       std::move(clipFP));
1430                     if (!fullyApplied && atlasPathRenderer) {
1431                         std::tie(fullyApplied, clipFP) = clip_atlas_fp(sdc, op,
1432                                                                        atlasPathRenderer,
1433                                                                        scissorBounds, e.asElement(),
1434                                                                        std::move(clipFP));
1435                     }
1436                     if (fullyApplied) {
1437                         remainingAnalyticFPs--;
1438                     }
1439                 }
1440 
1441                 if (!fullyApplied) {
1442                     elementsForMask.push_back(&e.asElement());
1443                     maskRequiresAA |= (e.aa() == GrAA::kYes);
1444                 }
1445 
1446                 break;
1447             }
1448         }
1449     }
1450 
1451     if (!scissorIsNeeded) {
1452         // More detailed analysis of the element shapes determined no clip is needed
1453         SkASSERT(elementsForMask.empty() && !clipFP);
1454         return Effect::kUnclipped;
1455     }
1456 
1457     // Fill out the GrAppliedClip with what we know so far, possibly with a tightened scissor
1458     if (cs.op() == SkClipOp::kIntersect && !elementsForMask.empty()) {
1459         SkAssertResult(scissorBounds.intersect(draw.outerBounds()));
1460     }
1461     if (!GrClip::IsInsideClip(scissorBounds, *bounds, draw.aa())) {
1462         out->hardClip().addScissor(scissorBounds, bounds);
1463     }
1464     if (!windowRects.empty()) {
1465         out->hardClip().addWindowRectangles(windowRects, GrWindowRectsState::Mode::kExclusive);
1466     }
1467 
1468     // Now rasterize any remaining elements, either to the stencil or a SW mask. All elements are
1469     // flattened into a single mask.
1470     if (!elementsForMask.empty()) {
1471         bool stencilUnavailable =
1472                 !sdc->asRenderTargetProxy()->canUseStencil(*rContext->priv().caps());
1473 
1474         bool hasSWMask = false;
1475         if ((sdc->numSamples() <= 1 && !sdc->canUseDynamicMSAA() && maskRequiresAA) ||
1476             stencilUnavailable) {
1477             // Must use a texture mask to represent the combined clip elements since the stencil
1478             // cannot be used, or cannot handle smooth clips.
1479             std::tie(hasSWMask, clipFP) = GetSWMaskFP(
1480                      rContext, &fMasks, cs, scissorBounds, elementsForMask.begin(),
1481                      elementsForMask.count(), std::move(clipFP));
1482         }
1483 
1484         if (!hasSWMask) {
1485             if (stencilUnavailable) {
1486                 SkDebugf("WARNING: Clip mask requires stencil, but stencil unavailable. "
1487                             "Draw will be ignored.\n");
1488                 return Effect::kClippedOut;
1489             } else {
1490                 // Rasterize the remaining elements to the stencil buffer
1491                 render_stencil_mask(rContext, sdc, cs.genID(), scissorBounds,
1492                                     elementsForMask.begin(), elementsForMask.count(), out);
1493             }
1494         }
1495     }
1496 
1497     if (clipFP) {
1498         // This will include all analytic FPs, all atlas FPs, and a SW mask FP.
1499         out->addCoverageFP(std::move(clipFP));
1500     }
1501 
1502     SkASSERT(out->doesClip() || opClippedInternally);
1503     return Effect::kClipped;
1504 }
1505 
writableSaveRecord(bool * wasDeferred)1506 ClipStack::SaveRecord& ClipStack::writableSaveRecord(bool* wasDeferred) {
1507     SaveRecord& current = fSaves.back();
1508     if (current.canBeUpdated()) {
1509         // Current record is still open, so it can be modified directly
1510         *wasDeferred = false;
1511         return current;
1512     } else {
1513         // Must undefer the save to get a new record.
1514         SkAssertResult(current.popSave());
1515         *wasDeferred = true;
1516         return fSaves.emplace_back(current, fMasks.count(), fElements.count());
1517     }
1518 }
1519 
clipShader(sk_sp<SkShader> shader)1520 void ClipStack::clipShader(sk_sp<SkShader> shader) {
1521     // Shaders can't bring additional coverage
1522     if (this->currentSaveRecord().state() == ClipState::kEmpty) {
1523         return;
1524     }
1525 
1526     bool wasDeferred;
1527     this->writableSaveRecord(&wasDeferred).addShader(std::move(shader));
1528     // Masks and geometry elements are not invalidated by updating the clip shader
1529 }
1530 
replaceClip(const SkIRect & rect)1531 void ClipStack::replaceClip(const SkIRect& rect) {
1532     bool wasDeferred;
1533     SaveRecord& save = this->writableSaveRecord(&wasDeferred);
1534 
1535     if (!wasDeferred) {
1536         save.removeElements(&fElements);
1537         save.invalidateMasks(fProxyProvider, &fMasks);
1538     }
1539 
1540     save.reset(fDeviceBounds);
1541     if (rect != fDeviceBounds) {
1542         this->clipRect(SkMatrix::I(), SkRect::Make(rect), GrAA::kNo, SkClipOp::kIntersect);
1543     }
1544 }
1545 
clip(RawElement && element)1546 void ClipStack::clip(RawElement&& element) {
1547     if (this->currentSaveRecord().state() == ClipState::kEmpty) {
1548         return;
1549     }
1550 
1551     // Reduce the path to anything simpler, will apply the transform if it's a scale+translate
1552     // and ensures the element's bounds are clipped to the device (NOT the conservative clip bounds,
1553     // since those are based on the net effect of all elements while device bounds clipping happens
1554     // implicitly. During addElement, we may still be able to invalidate some older elements).
1555     element.simplify(fDeviceBounds, fForceAA);
1556     SkASSERT(!element.shape().inverted());
1557 
1558     // An empty op means do nothing (for difference), or close the save record, so we try and detect
1559     // that early before doing additional unnecessary save record allocation.
1560     if (element.shape().isEmpty()) {
1561         if (element.op() == SkClipOp::kDifference) {
1562             // If the shape is empty and we're subtracting, this has no effect on the clip
1563             return;
1564         }
1565         // else we will make the clip empty, but we need a new save record to record that change
1566         // in the clip state; fall through to below and updateForElement() will handle it.
1567     }
1568 
1569     bool wasDeferred;
1570     SaveRecord& save = this->writableSaveRecord(&wasDeferred);
1571     SkDEBUGCODE(uint32_t oldGenID = save.genID();)
1572     SkDEBUGCODE(int elementCount = fElements.count();)
1573     if (!save.addElement(std::move(element), &fElements)) {
1574         if (wasDeferred) {
1575             // We made a new save record, but ended up not adding an element to the stack.
1576             // So instead of keeping an empty save record around, pop it off and restore the counter
1577             SkASSERT(elementCount == fElements.count());
1578             fSaves.pop_back();
1579             fSaves.back().pushSave();
1580         } else {
1581             // Should not have changed gen ID if the element and save were not modified
1582             SkASSERT(oldGenID == save.genID());
1583         }
1584     } else {
1585         // The gen ID should be new, and should not be invalid
1586         SkASSERT(oldGenID != save.genID() && save.genID() != kInvalidGenID);
1587         if (fProxyProvider && !wasDeferred) {
1588             // We modified an active save record so any old masks it had can be invalidated
1589             save.invalidateMasks(fProxyProvider, &fMasks);
1590         }
1591     }
1592 }
1593 
GetSWMaskFP(GrRecordingContext * context,Mask::Stack * masks,const SaveRecord & current,const SkIRect & bounds,const Element ** elements,int count,std::unique_ptr<GrFragmentProcessor> clipFP)1594 GrFPResult ClipStack::GetSWMaskFP(GrRecordingContext* context, Mask::Stack* masks,
1595                                   const SaveRecord& current, const SkIRect& bounds,
1596                                   const Element** elements, int count,
1597                                   std::unique_ptr<GrFragmentProcessor> clipFP) {
1598     GrProxyProvider* proxyProvider = context->priv().proxyProvider();
1599     GrSurfaceProxyView maskProxy;
1600 
1601     SkIRect maskBounds; // may not be 'bounds' if we reuse a large clip mask
1602     // Check the existing masks from this save record for compatibility
1603     for (const Mask& m : masks->ritems()) {
1604         if (m.genID() != current.genID()) {
1605             break;
1606         }
1607         if (m.appliesToDraw(current, bounds)) {
1608             maskProxy = proxyProvider->findCachedProxyWithColorTypeFallback(
1609                     m.key(), kMaskOrigin, GrColorType::kAlpha_8, 1);
1610             if (maskProxy) {
1611                 maskBounds = m.bounds();
1612                 break;
1613             }
1614         }
1615     }
1616 
1617     if (!maskProxy) {
1618         // No existing mask was found, so need to render a new one
1619         maskProxy = render_sw_mask(context, bounds, elements, count);
1620         if (!maskProxy) {
1621             // If we still don't have one, there's nothing we can do
1622             return GrFPFailure(std::move(clipFP));
1623         }
1624 
1625         // Register the mask for later invalidation
1626         Mask& mask = masks->emplace_back(current, bounds);
1627         proxyProvider->assignUniqueKeyToProxy(mask.key(), maskProxy.asTextureProxy());
1628         maskBounds = bounds;
1629     }
1630 
1631     // Wrap the mask in an FP that samples it for coverage
1632     SkASSERT(maskProxy && maskProxy.origin() == kMaskOrigin);
1633 
1634     GrSamplerState samplerState(GrSamplerState::WrapMode::kClampToBorder,
1635                                 GrSamplerState::Filter::kNearest);
1636     // Maps the device coords passed to the texture effect to the top-left corner of the mask, and
1637     // make sure that the draw bounds are pre-mapped into the mask's space as well.
1638     auto m = SkMatrix::Translate(-maskBounds.fLeft, -maskBounds.fTop);
1639     auto subset = SkRect::Make(bounds);
1640     subset.offset(-maskBounds.fLeft, -maskBounds.fTop);
1641     // We scissor to bounds. The mask's texel centers are aligned to device space
1642     // pixel centers. Hence this domain of texture coordinates.
1643     auto domain = subset.makeInset(0.5, 0.5);
1644     auto fp = GrTextureEffect::MakeSubset(std::move(maskProxy), kPremul_SkAlphaType, m,
1645                                           samplerState, subset, domain, *context->priv().caps());
1646     fp = GrFragmentProcessor::DeviceSpace(std::move(fp));
1647 
1648     // Must combine the coverage sampled from the texture effect with the previous coverage
1649     fp = GrBlendFragmentProcessor::Make(std::move(fp), std::move(clipFP), SkBlendMode::kDstIn);
1650     return GrFPSuccess(std::move(fp));
1651 }
1652 
1653 } // namespace skgpu::v1
1654