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