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