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