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