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