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