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