3 * Copyright 2020 Google LLC
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
9 #include "src/gpu/ganesh/v1/ClipStack.h"
10 #include "tests/Test.h"
12 #include "include/core/SkColorSpace.h"
13 #include "include/core/SkPath.h"
14 #include "include/core/SkRRect.h"
15 #include "include/core/SkRect.h"
16 #include "include/core/SkRegion.h"
17 #include "include/core/SkShader.h"
18 #include "include/gpu/GrDirectContext.h"
19 #include "src/core/SkMatrixProvider.h"
20 #include "src/core/SkRRectPriv.h"
21 #include "src/core/SkRectPriv.h"
22 #include "src/gpu/ganesh/GrDirectContextPriv.h"
23 #include "src/gpu/ganesh/GrProxyProvider.h"
24 #include "src/gpu/ganesh/ops/GrDrawOp.h"
25 #include "src/gpu/ganesh/v1/SurfaceDrawContext_v1.h"
29 class TestCaseBuilder;
30 class ElementsBuilder;
32 enum class SavePolicy {
38 // TODO: We could add a RestorePolicy enum that tests different places to restore, but that would
39 // make defining the test expectations and order independence more cumbersome.
43 using ClipStack = skgpu::v1::ClipStack;
45 // Provides fluent API to describe actual clip commands and expected clip elements:
46 // TestCase test = TestCase::Build("example", deviceBounds)
47 // .actual().rect(r, GrAA::kYes, SkClipOp::kIntersect)
48 // .localToDevice(matrix)
54 // .expectedState(kDeviceRect)
55 // .expectedBounds(r.roundOut())
56 // .expect().rect(r, GrAA::kYes, SkClipOp::kIntersect)
59 static TestCaseBuilder Build(const char* name, const SkIRect& deviceBounds);
61 void run(const std::vector<int>& order, SavePolicy policy, skiatest::Reporter* reporter) const;
63 const SkIRect& deviceBounds() const { return fDeviceBounds; }
64 ClipStack::ClipState expectedState() const { return fExpectedState; }
65 const std::vector<ClipStack::Element>& initialElements() const { return fElements; }
66 const std::vector<ClipStack::Element>& expectedElements() const { return fExpectedElements; }
69 friend class TestCaseBuilder;
71 TestCase(SkString name,
72 const SkIRect& deviceBounds,
73 ClipStack::ClipState expectedState,
74 std::vector<ClipStack::Element> actual,
75 std::vector<ClipStack::Element> expected)
77 , fElements(std::move(actual))
78 , fDeviceBounds(deviceBounds)
79 , fExpectedElements(std::move(expected))
80 , fExpectedState(expectedState) {}
82 SkString getTestName(const std::vector<int>& order, SavePolicy policy) const;
84 // This may be tighter than ClipStack::getConservativeBounds() because this always accounts
85 // for difference ops, whereas ClipStack only sometimes can subtract the inner bounds for a
87 std::pair<SkIRect, bool> getOptimalBounds() const;
91 // The input shapes+state to ClipStack
92 std::vector<ClipStack::Element> fElements;
93 SkIRect fDeviceBounds;
95 // The expected output of iterating over the ClipStack after all fElements are added, although
96 // order is not important
97 std::vector<ClipStack::Element> fExpectedElements;
98 ClipStack::ClipState fExpectedState;
101 class ElementsBuilder {
103 using ClipStack = skgpu::v1::ClipStack;
105 // Update the default matrix, aa, and op state for elements that are added.
106 ElementsBuilder& localToDevice(const SkMatrix& m) { fLocalToDevice = m; return *this; }
107 ElementsBuilder& aa() { fAA = GrAA::kYes; return *this; }
108 ElementsBuilder& nonAA() { fAA = GrAA::kNo; return *this; }
109 ElementsBuilder& intersect() { fOp = SkClipOp::kIntersect; return *this; }
110 ElementsBuilder& difference() { fOp = SkClipOp::kDifference; return *this; }
112 // Add rect, rrect, or paths to the list of elements, possibly overriding the last set
113 // matrix, aa, and op state.
114 ElementsBuilder& rect(const SkRect& rect) {
115 return this->rect(rect, fLocalToDevice, fAA, fOp);
117 ElementsBuilder& rect(const SkRect& rect, GrAA aa, SkClipOp op) {
118 return this->rect(rect, fLocalToDevice, aa, op);
120 ElementsBuilder& rect(const SkRect& rect, const SkMatrix& m, GrAA aa, SkClipOp op) {
121 fElements->push_back({GrShape(rect), m, op, aa});
125 ElementsBuilder& rrect(const SkRRect& rrect) {
126 return this->rrect(rrect, fLocalToDevice, fAA, fOp);
128 ElementsBuilder& rrect(const SkRRect& rrect, GrAA aa, SkClipOp op) {
129 return this->rrect(rrect, fLocalToDevice, aa, op);
131 ElementsBuilder& rrect(const SkRRect& rrect, const SkMatrix& m, GrAA aa, SkClipOp op) {
132 fElements->push_back({GrShape(rrect), m, op, aa});
136 ElementsBuilder& path(const SkPath& path) {
137 return this->path(path, fLocalToDevice, fAA, fOp);
139 ElementsBuilder& path(const SkPath& path, GrAA aa, SkClipOp op) {
140 return this->path(path, fLocalToDevice, aa, op);
142 ElementsBuilder& path(const SkPath& path, const SkMatrix& m, GrAA aa, SkClipOp op) {
143 fElements->push_back({GrShape(path), m, op, aa});
147 // Finish and return the original test case builder
148 TestCaseBuilder& finishElements() {
153 friend class TestCaseBuilder;
155 ElementsBuilder(TestCaseBuilder* builder, std::vector<ClipStack::Element>* elements)
157 , fElements(elements) {}
159 SkMatrix fLocalToDevice = SkMatrix::I();
160 GrAA fAA = GrAA::kNo;
161 SkClipOp fOp = SkClipOp::kIntersect;
163 TestCaseBuilder* fBuilder;
164 std::vector<ClipStack::Element>* fElements;
167 class TestCaseBuilder {
169 using ClipStack = skgpu::v1::ClipStack;
171 ElementsBuilder actual() { return ElementsBuilder(this, &fActualElements); }
172 ElementsBuilder expect() { return ElementsBuilder(this, &fExpectedElements); }
174 TestCaseBuilder& expectActual() {
175 fExpectedElements = fActualElements;
179 TestCaseBuilder& state(ClipStack::ClipState state) {
180 fExpectedState = state;
184 TestCase finishTest() {
185 TestCase test(fName, fDeviceBounds, fExpectedState,
186 std::move(fActualElements), std::move(fExpectedElements));
188 fExpectedState = ClipStack::ClipState::kWideOpen;
193 friend class TestCase;
195 explicit TestCaseBuilder(const char* name, const SkIRect& deviceBounds)
197 , fDeviceBounds(deviceBounds)
198 , fExpectedState(ClipStack::ClipState::kWideOpen) {}
201 SkIRect fDeviceBounds;
202 ClipStack::ClipState fExpectedState;
204 std::vector<ClipStack::Element> fActualElements;
205 std::vector<ClipStack::Element> fExpectedElements;
208 TestCaseBuilder TestCase::Build(const char* name, const SkIRect& deviceBounds) {
209 return TestCaseBuilder(name, deviceBounds);
212 SkString TestCase::getTestName(const std::vector<int>& order, SavePolicy policy) const {
213 SkString name = fName;
217 case SavePolicy::kNever:
218 policyName = "never";
220 case SavePolicy::kAtStart:
221 policyName = "start";
223 case SavePolicy::kAtEnd:
226 case SavePolicy::kBetweenEveryOp:
227 policyName = "between";
231 name.appendf("(save %s, order [", policyName.c_str());
232 for (size_t i = 0; i < order.size(); ++i) {
236 name.appendf("%d", order[i]);
242 std::pair<SkIRect, bool> TestCase::getOptimalBounds() const {
243 if (fExpectedState == ClipStack::ClipState::kEmpty) {
244 return {SkIRect::MakeEmpty(), true};
247 bool expectOptimal = true;
248 SkRegion region(fDeviceBounds);
249 for (const ClipStack::Element& e : fExpectedElements) {
250 bool intersect = (e.fOp == SkClipOp::kIntersect && !e.fShape.inverted()) ||
251 (e.fOp == SkClipOp::kDifference && e.fShape.inverted());
253 SkIRect elementBounds;
256 op = SkRegion::kIntersect_Op;
257 expectOptimal &= e.fLocalToDevice.isIdentity();
258 elementBounds = GrClip::GetPixelIBounds(e.fLocalToDevice.mapRect(e.fShape.bounds()),
259 e.fAA, GrClip::BoundsType::kExterior);
261 op = SkRegion::kDifference_Op;
262 expectOptimal = false;
263 if (e.fShape.isRect() && e.fLocalToDevice.isIdentity()) {
264 elementBounds = GrClip::GetPixelIBounds(e.fShape.rect(), e.fAA,
265 GrClip::BoundsType::kInterior);
266 } else if (e.fShape.isRRect() && e.fLocalToDevice.isIdentity()) {
267 elementBounds = GrClip::GetPixelIBounds(SkRRectPriv::InnerBounds(e.fShape.rrect()),
268 e.fAA, GrClip::BoundsType::kInterior);
270 elementBounds = SkIRect::MakeEmpty();
274 region.op(SkRegion(elementBounds), op);
276 return {region.getBounds(), expectOptimal};
279 static bool compare_elements(const skgpu::v1::ClipStack::Element& a,
280 const skgpu::v1::ClipStack::Element& b) {
281 if (a.fAA != b.fAA || a.fOp != b.fOp || a.fLocalToDevice != b.fLocalToDevice ||
282 a.fShape.type() != b.fShape.type()) {
285 switch(a.fShape.type()) {
286 case GrShape::Type::kRect:
287 return a.fShape.rect() == b.fShape.rect();
288 case GrShape::Type::kRRect:
289 return a.fShape.rrect() == b.fShape.rrect();
290 case GrShape::Type::kPath:
291 // A path's points are never transformed, the only modification is fill type which does
292 // not change the generation ID. For convex polygons, we check == so that more complex
293 // test cases can be evaluated.
294 return a.fShape.path().getGenerationID() == b.fShape.path().getGenerationID() ||
295 (a.fShape.convex() &&
296 a.fShape.segmentMask() == SkPathSegmentMask::kLine_SkPathSegmentMask &&
297 a.fShape.path() == b.fShape.path());
299 SkDEBUGFAIL("Shape type not handled by test case yet.");
304 void TestCase::run(const std::vector<int>& order,
306 skiatest::Reporter* reporter) const {
307 SkASSERT(fElements.size() == order.size());
309 SkMatrixProvider matrixProvider(SkMatrix::I());
310 ClipStack cs(fDeviceBounds, &matrixProvider, false);
312 if (policy == SavePolicy::kAtStart) {
316 for (int i : order) {
317 if (policy == SavePolicy::kBetweenEveryOp) {
320 const ClipStack::Element& e = fElements[i];
321 switch(e.fShape.type()) {
322 case GrShape::Type::kRect:
323 cs.clipRect(e.fLocalToDevice, e.fShape.rect(), e.fAA, e.fOp);
325 case GrShape::Type::kRRect:
326 cs.clipRRect(e.fLocalToDevice, e.fShape.rrect(), e.fAA, e.fOp);
328 case GrShape::Type::kPath:
329 cs.clipPath(e.fLocalToDevice, e.fShape.path(), e.fAA, e.fOp);
332 SkDEBUGFAIL("Shape type not handled by test case yet.");
336 if (policy == SavePolicy::kAtEnd) {
341 SkString name = this->getTestName(order, policy);
342 REPORTER_ASSERT(reporter, cs.clipState() == fExpectedState,
343 "%s, clip state expected %d, actual %d",
344 name.c_str(), (int) fExpectedState, (int) cs.clipState());
345 SkIRect actualBounds = cs.getConservativeBounds();
346 SkIRect optimalBounds;
348 std::tie(optimalBounds, expectOptimal) = this->getOptimalBounds();
351 REPORTER_ASSERT(reporter, actualBounds == optimalBounds,
352 "%s, bounds expected [%d %d %d %d], actual [%d %d %d %d]",
353 name.c_str(), optimalBounds.fLeft, optimalBounds.fTop,
354 optimalBounds.fRight, optimalBounds.fBottom,
355 actualBounds.fLeft, actualBounds.fTop,
356 actualBounds.fRight, actualBounds.fBottom);
358 REPORTER_ASSERT(reporter, actualBounds.contains(optimalBounds),
359 "%s, bounds are not conservative, optimal [%d %d %d %d], actual [%d %d %d %d]",
360 name.c_str(), optimalBounds.fLeft, optimalBounds.fTop,
361 optimalBounds.fRight, optimalBounds.fBottom,
362 actualBounds.fLeft, actualBounds.fTop,
363 actualBounds.fRight, actualBounds.fBottom);
366 size_t matchedElements = 0;
367 for (const ClipStack::Element& a : cs) {
369 for (const ClipStack::Element& e : fExpectedElements) {
370 if (compare_elements(a, e)) {
371 // shouldn't match multiple expected elements or it's a bad test case
377 REPORTER_ASSERT(reporter, found,
378 "%s, unexpected clip element in stack: shape %d, aa %d, op %d",
379 name.c_str(), (int) a.fShape.type(), (int) a.fAA, (int) a.fOp);
380 matchedElements += found ? 1 : 0;
382 REPORTER_ASSERT(reporter, matchedElements == fExpectedElements.size(),
383 "%s, did not match all expected elements: expected %zu but matched only %zu",
384 name.c_str(), fExpectedElements.size(), matchedElements);
386 // Validate restoration behavior
387 if (policy == SavePolicy::kAtEnd) {
388 ClipStack::ClipState oldState = cs.clipState();
390 REPORTER_ASSERT(reporter, cs.clipState() == oldState,
391 "%s, restoring an empty save record should not change clip state: "
392 "expected %d but got %d",
393 name.c_str(), (int) oldState, (int) cs.clipState());
394 } else if (policy != SavePolicy::kNever) {
395 int restoreCount = policy == SavePolicy::kAtStart ? 1 : (int) order.size();
396 for (int i = 0; i < restoreCount; ++i) {
399 // Should be wide open if everything is restored to base state
400 REPORTER_ASSERT(reporter, cs.clipState() == ClipStack::ClipState::kWideOpen,
401 "%s, restore should make stack become wide-open, not %d",
402 name.c_str(), (int) cs.clipState());
406 // All clip operations are commutative so applying actual elements in every possible order should
407 // always produce the same set of expected elements.
408 static void run_test_case(skiatest::Reporter* r, const TestCase& test) {
409 int n = (int) test.initialElements().size();
410 std::vector<int> order(n);
411 std::vector<int> stack(n);
413 // Initial order sequence and zeroed stack
414 for (int i = 0; i < n; ++i) {
419 auto runTest = [&]() {
420 static const SavePolicy kPolicies[] = { SavePolicy::kNever, SavePolicy::kAtStart,
421 SavePolicy::kAtEnd, SavePolicy::kBetweenEveryOp };
422 for (auto policy : kPolicies) {
423 test.run(order, policy, r);
427 // Heap's algorithm (non-recursive) to generate every permutation over the test case's elements
428 // https://en.wikipedia.org/wiki/Heap%27s_algorithm
431 static constexpr int kMaxRuns = 720; // Don't run more than 6! configurations, even if n > 6
435 while (i < n && testRuns < kMaxRuns) {
439 swap(order[0], order[i]);
441 swap(order[stack[i]], order[i]);
455 static SkPath make_octagon(const SkRect& r, SkScalar lr, SkScalar tb) {
457 p.moveTo(r.fLeft + lr, r.fTop);
458 p.lineTo(r.fRight - lr, r.fTop);
459 p.lineTo(r.fRight, r.fTop + tb);
460 p.lineTo(r.fRight, r.fBottom - tb);
461 p.lineTo(r.fRight - lr, r.fBottom);
462 p.lineTo(r.fLeft + lr, r.fBottom);
463 p.lineTo(r.fLeft, r.fBottom - tb);
464 p.lineTo(r.fLeft, r.fTop + tb);
469 static SkPath make_octagon(const SkRect& r) {
470 SkScalar lr = 0.3f * r.width();
471 SkScalar tb = 0.3f * r.height();
472 return make_octagon(r, lr, tb);
475 static constexpr SkIRect kDeviceBounds = {0, 0, 100, 100};
477 class NoOp : public GrDrawOp {
485 NoOp() : GrDrawOp(ClassID()) {}
486 const char* name() const override { return "NoOp"; }
487 GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, GrClampType) override {
488 return GrProcessorSet::EmptySetAnalysis();
490 void onPrePrepare(GrRecordingContext*, const GrSurfaceProxyView&, GrAppliedClip*, const
491 GrDstProxyView&, GrXferBarrierFlags, GrLoadOp) override {}
492 void onPrepare(GrOpFlushState*) override {}
493 void onExecute(GrOpFlushState*, const SkRect&) override {}
496 } // anonymous namespace
498 ///////////////////////////////////////////////////////////////////////////////
499 // These tests use the TestCase infrastructure to define clip stacks and
500 // associated expectations.
502 // Tests that the initialized state of the clip stack is wide-open
503 DEF_TEST(ClipStack_InitialState, r) {
504 run_test_case(r, TestCase::Build("initial-state", SkIRect::MakeWH(100, 100)).finishTest());
507 // Tests that intersection of rects combine to a single element when they have the same AA type,
508 // or are pixel-aligned.
509 DEF_TEST(ClipStack_RectRectAACombine, r) {
510 using ClipState = skgpu::v1::ClipStack::ClipState;
512 SkRect pixelAligned = {0, 0, 10, 10};
513 SkRect fracRect1 = pixelAligned.makeOffset(5.3f, 3.7f);
514 SkRect fracRect2 = {fracRect1.fLeft + 0.75f * fracRect1.width(),
515 fracRect1.fTop + 0.75f * fracRect1.height(),
516 fracRect1.fRight, fracRect1.fBottom};
518 SkRect fracIntersect;
519 SkAssertResult(fracIntersect.intersect(fracRect1, fracRect2));
520 SkRect alignedIntersect;
521 SkAssertResult(alignedIntersect.intersect(pixelAligned, fracRect1));
523 // Both AA combine to one element
524 run_test_case(r, TestCase::Build("aa", kDeviceBounds)
525 .actual().aa().intersect()
526 .rect(fracRect1).rect(fracRect2)
528 .expect().aa().intersect().rect(fracIntersect).finishElements()
529 .state(ClipState::kDeviceRect)
532 // Both non-AA combine to one element
533 run_test_case(r, TestCase::Build("nonaa", kDeviceBounds)
534 .actual().nonAA().intersect()
535 .rect(fracRect1).rect(fracRect2)
537 .expect().nonAA().intersect().rect(fracIntersect).finishElements()
538 .state(ClipState::kDeviceRect)
541 // Pixel-aligned AA and non-AA combine
542 run_test_case(r, TestCase::Build("aligned-aa+nonaa", kDeviceBounds)
543 .actual().intersect()
544 .aa().rect(pixelAligned).nonAA().rect(fracRect1)
546 .expect().nonAA().intersect().rect(alignedIntersect).finishElements()
547 .state(ClipState::kDeviceRect)
550 // AA and pixel-aligned non-AA combine
551 run_test_case(r, TestCase::Build("aa+aligned-nonaa", kDeviceBounds)
552 .actual().intersect()
553 .aa().rect(fracRect1).nonAA().rect(pixelAligned)
555 .expect().aa().intersect().rect(alignedIntersect).finishElements()
556 .state(ClipState::kDeviceRect)
559 // Other mixed AA modes do not combine
560 run_test_case(r, TestCase::Build("aa+nonaa", kDeviceBounds)
561 .actual().intersect()
562 .aa().rect(fracRect1).nonAA().rect(fracRect2)
565 .state(ClipState::kComplex)
569 // Tests that an intersection and a difference op do not combine, even if they would have if both
570 // were intersection ops.
571 DEF_TEST(ClipStack_DifferenceNoCombine, r) {
572 using ClipState = skgpu::v1::ClipStack::ClipState;
574 SkRect r1 = {15.f, 14.f, 23.22f, 58.2f};
575 SkRect r2 = r1.makeOffset(5.f, 8.f);
576 SkASSERT(r1.intersects(r2));
578 run_test_case(r, TestCase::Build("no-combine", kDeviceBounds)
579 .actual().aa().intersect().rect(r1)
580 .difference().rect(r2)
583 .state(ClipState::kComplex)
587 // Tests that intersection of rects in the same coordinate space can still be combined, but do not
588 // when the spaces differ.
589 DEF_TEST(ClipStack_RectRectNonAxisAligned, r) {
590 using ClipState = skgpu::v1::ClipStack::ClipState;
592 SkRect pixelAligned = {0, 0, 10, 10};
593 SkRect fracRect1 = pixelAligned.makeOffset(5.3f, 3.7f);
594 SkRect fracRect2 = {fracRect1.fLeft + 0.75f * fracRect1.width(),
595 fracRect1.fTop + 0.75f * fracRect1.height(),
596 fracRect1.fRight, fracRect1.fBottom};
598 SkRect fracIntersect;
599 SkAssertResult(fracIntersect.intersect(fracRect1, fracRect2));
601 SkMatrix lm = SkMatrix::RotateDeg(45.f);
604 run_test_case(r, TestCase::Build("aa", kDeviceBounds)
605 .actual().aa().intersect().localToDevice(lm)
606 .rect(fracRect1).rect(fracRect2)
608 .expect().aa().intersect().localToDevice(lm)
609 .rect(fracIntersect).finishElements()
610 .state(ClipState::kComplex)
613 // Both non-AA combine
614 run_test_case(r, TestCase::Build("nonaa", kDeviceBounds)
615 .actual().nonAA().intersect().localToDevice(lm)
616 .rect(fracRect1).rect(fracRect2)
618 .expect().nonAA().intersect().localToDevice(lm)
619 .rect(fracIntersect).finishElements()
620 .state(ClipState::kComplex)
623 // Integer-aligned coordinates under a local matrix with mixed AA don't combine, though
624 run_test_case(r, TestCase::Build("local-aa", kDeviceBounds)
625 .actual().intersect().localToDevice(lm)
626 .aa().rect(pixelAligned).nonAA().rect(fracRect1)
629 .state(ClipState::kComplex)
633 // Tests that intersection of two round rects can simplify to a single round rect when they have
635 DEF_TEST(ClipStack_RRectRRectAACombine, r) {
636 using ClipState = skgpu::v1::ClipStack::ClipState;
638 SkRRect r1 = SkRRect::MakeRectXY(SkRect::MakeWH(12, 12), 2.f, 2.f);
639 SkRRect r2 = r1.makeOffset(6.f, 6.f);
641 SkRRect intersect = SkRRectPriv::ConservativeIntersect(r1, r2);
642 SkASSERT(!intersect.isEmpty());
645 run_test_case(r, TestCase::Build("aa", kDeviceBounds)
646 .actual().aa().intersect()
649 .expect().aa().intersect().rrect(intersect).finishElements()
650 .state(ClipState::kDeviceRRect)
653 // Both non-AA combine
654 run_test_case(r, TestCase::Build("nonaa", kDeviceBounds)
655 .actual().nonAA().intersect()
658 .expect().nonAA().intersect().rrect(intersect).finishElements()
659 .state(ClipState::kDeviceRRect)
662 // Mixed do not combine
663 run_test_case(r, TestCase::Build("aa+nonaa", kDeviceBounds)
664 .actual().intersect()
665 .aa().rrect(r1).nonAA().rrect(r2)
668 .state(ClipState::kComplex)
671 // Same AA state can combine in the same local coordinate space
672 SkMatrix lm = SkMatrix::RotateDeg(45.f);
673 run_test_case(r, TestCase::Build("local-aa", kDeviceBounds)
674 .actual().aa().intersect().localToDevice(lm)
677 .expect().aa().intersect().localToDevice(lm)
678 .rrect(intersect).finishElements()
679 .state(ClipState::kComplex)
681 run_test_case(r, TestCase::Build("local-nonaa", kDeviceBounds)
682 .actual().nonAA().intersect().localToDevice(lm)
685 .expect().nonAA().intersect().localToDevice(lm)
686 .rrect(intersect).finishElements()
687 .state(ClipState::kComplex)
691 // Tests that intersection of a round rect and rect can simplify to a new round rect or even a rect.
692 DEF_TEST(ClipStack_RectRRectCombine, r) {
693 using ClipState = skgpu::v1::ClipStack::ClipState;
695 SkRRect rrect = SkRRect::MakeRectXY({0, 0, 10, 10}, 2.f, 2.f);
696 SkRect cutTop = {-10, -10, 10, 4};
697 SkRect cutMid = {-10, 3, 10, 7};
699 // Rect + RRect becomes a round rect with some square corners
700 SkVector cutCorners[4] = {{2.f, 2.f}, {2.f, 2.f}, {0, 0}, {0, 0}};
702 cutRRect.setRectRadii({0, 0, 10, 4}, cutCorners);
703 run_test_case(r, TestCase::Build("still-rrect", kDeviceBounds)
704 .actual().intersect().aa().rrect(rrect).rect(cutTop).finishElements()
705 .expect().intersect().aa().rrect(cutRRect).finishElements()
706 .state(ClipState::kDeviceRRect)
709 // Rect + RRect becomes a rect
710 SkRect cutRect = {0, 3, 10, 7};
711 run_test_case(r, TestCase::Build("to-rect", kDeviceBounds)
712 .actual().intersect().aa().rrect(rrect).rect(cutMid).finishElements()
713 .expect().intersect().aa().rect(cutRect).finishElements()
714 .state(ClipState::kDeviceRect)
717 // But they can only combine when the intersecting shape is representable as a [r]rect.
718 cutRect = {0, 0, 1.5f, 5.f};
719 run_test_case(r, TestCase::Build("no-combine", kDeviceBounds)
720 .actual().intersect().aa().rrect(rrect).rect(cutRect).finishElements()
722 .state(ClipState::kComplex)
726 // Tests that a rect shape is actually pre-clipped to the device bounds
727 DEF_TEST(ClipStack_RectDeviceClip, r) {
728 using ClipState = skgpu::v1::ClipStack::ClipState;
730 SkRect crossesDeviceEdge = {20.f, kDeviceBounds.fTop - 13.2f,
731 kDeviceBounds.fRight + 15.5f, 30.f};
732 SkRect insideDevice = {20.f, kDeviceBounds.fTop, kDeviceBounds.fRight, 30.f};
734 run_test_case(r, TestCase::Build("device-aa-rect", kDeviceBounds)
735 .actual().intersect().aa().rect(crossesDeviceEdge).finishElements()
736 .expect().intersect().aa().rect(insideDevice).finishElements()
737 .state(ClipState::kDeviceRect)
740 run_test_case(r, TestCase::Build("device-nonaa-rect", kDeviceBounds)
741 .actual().intersect().nonAA().rect(crossesDeviceEdge).finishElements()
742 .expect().intersect().nonAA().rect(insideDevice).finishElements()
743 .state(ClipState::kDeviceRect)
747 // Tests that other shapes' bounds are contained by the device bounds, even if their shape is not.
748 DEF_TEST(ClipStack_ShapeDeviceBoundsClip, r) {
749 using ClipState = skgpu::v1::ClipStack::ClipState;
751 SkRect crossesDeviceEdge = {20.f, kDeviceBounds.fTop - 13.2f,
752 kDeviceBounds.fRight + 15.5f, 30.f};
755 run_test_case(r, TestCase::Build("device-rrect", kDeviceBounds)
756 .actual().intersect().aa()
757 .rrect(SkRRect::MakeRectXY(crossesDeviceEdge, 4.f, 4.f))
760 .state(ClipState::kDeviceRRect)
764 run_test_case(r, TestCase::Build("device-path", kDeviceBounds)
765 .actual().intersect().aa()
766 .path(make_octagon(crossesDeviceEdge))
769 .state(ClipState::kComplex)
773 // Tests that a simplifiable path turns into a simpler element type
774 DEF_TEST(ClipStack_PathSimplify, r) {
775 using ClipState = skgpu::v1::ClipStack::ClipState;
777 // Empty, point, and line paths -> empty
779 run_test_case(r, TestCase::Build("empty", kDeviceBounds)
780 .actual().path(empty).finishElements()
781 .state(ClipState::kEmpty)
784 point.moveTo({0.f, 0.f});
785 run_test_case(r, TestCase::Build("point", kDeviceBounds)
786 .actual().path(point).finishElements()
787 .state(ClipState::kEmpty)
791 line.moveTo({0.f, 0.f});
792 line.lineTo({10.f, 5.f});
793 run_test_case(r, TestCase::Build("line", kDeviceBounds)
794 .actual().path(line).finishElements()
795 .state(ClipState::kEmpty)
798 // Rect path -> rect element
799 SkRect rect = {0.f, 2.f, 10.f, 15.4f};
801 rectPath.addRect(rect);
802 run_test_case(r, TestCase::Build("rect", kDeviceBounds)
803 .actual().path(rectPath).finishElements()
804 .expect().rect(rect).finishElements()
805 .state(ClipState::kDeviceRect)
808 // Oval path -> rrect element
810 ovalPath.addOval(rect);
811 run_test_case(r, TestCase::Build("oval", kDeviceBounds)
812 .actual().path(ovalPath).finishElements()
813 .expect().rrect(SkRRect::MakeOval(rect)).finishElements()
814 .state(ClipState::kDeviceRRect)
817 // RRect path -> rrect element
818 SkRRect rrect = SkRRect::MakeRectXY(rect, 2.f, 2.f);
820 rrectPath.addRRect(rrect);
821 run_test_case(r, TestCase::Build("rrect", kDeviceBounds)
822 .actual().path(rrectPath).finishElements()
823 .expect().rrect(rrect).finishElements()
824 .state(ClipState::kDeviceRRect)
828 // Tests that repeated identical clip operations are idempotent
829 DEF_TEST(ClipStack_RepeatElement, r) {
830 using ClipState = skgpu::v1::ClipStack::ClipState;
833 SkRect rect = {5.3f, 62.f, 20.f, 85.f};
834 run_test_case(r, TestCase::Build("same-rects", kDeviceBounds)
835 .actual().rect(rect).rect(rect).rect(rect).finishElements()
836 .expect().rect(rect).finishElements()
837 .state(ClipState::kDeviceRect)
840 lm.setRotate(30.f, rect.centerX(), rect.centerY());
841 run_test_case(r, TestCase::Build("same-local-rects", kDeviceBounds)
842 .actual().localToDevice(lm).rect(rect).rect(rect).rect(rect)
844 .expect().localToDevice(lm).rect(rect).finishElements()
845 .state(ClipState::kComplex)
849 SkRRect rrect = SkRRect::MakeRectXY(rect, 5.f, 2.5f);
850 run_test_case(r, TestCase::Build("same-rrects", kDeviceBounds)
851 .actual().rrect(rrect).rrect(rrect).rrect(rrect).finishElements()
852 .expect().rrect(rrect).finishElements()
853 .state(ClipState::kDeviceRRect)
855 run_test_case(r, TestCase::Build("same-local-rrects", kDeviceBounds)
856 .actual().localToDevice(lm).rrect(rrect).rrect(rrect).rrect(rrect)
858 .expect().localToDevice(lm).rrect(rrect).finishElements()
859 .state(ClipState::kComplex)
862 // Same convex path, by ==
863 run_test_case(r, TestCase::Build("same-convex", kDeviceBounds)
864 .actual().path(make_octagon(rect)).path(make_octagon(rect))
866 .expect().path(make_octagon(rect)).finishElements()
867 .state(ClipState::kComplex)
869 run_test_case(r, TestCase::Build("same-local-convex", kDeviceBounds)
870 .actual().localToDevice(lm)
871 .path(make_octagon(rect)).path(make_octagon(rect))
873 .expect().localToDevice(lm).path(make_octagon(rect))
875 .state(ClipState::kComplex)
878 // Same complicated path by gen-id but not ==
879 SkPath path; // an hour glass
880 path.moveTo({0.f, 0.f});
881 path.lineTo({20.f, 20.f});
882 path.lineTo({0.f, 20.f});
883 path.lineTo({20.f, 0.f});
886 run_test_case(r, TestCase::Build("same-path", kDeviceBounds)
887 .actual().path(path).path(path).path(path).finishElements()
888 .expect().path(path).finishElements()
889 .state(ClipState::kComplex)
891 run_test_case(r, TestCase::Build("same-local-path", kDeviceBounds)
892 .actual().localToDevice(lm)
893 .path(path).path(path).path(path).finishElements()
894 .expect().localToDevice(lm).path(path)
896 .state(ClipState::kComplex)
900 // Tests that inverse-filled paths are canonicalized to a regular fill and a swapped clip op
901 DEF_TEST(ClipStack_InverseFilledPath, r) {
902 using ClipState = skgpu::v1::ClipStack::ClipState;
904 SkRect rect = {0.f, 0.f, 16.f, 17.f};
906 rectPath.addRect(rect);
908 SkPath inverseRectPath = rectPath;
909 inverseRectPath.toggleInverseFillType();
911 SkPath complexPath = make_octagon(rect);
912 SkPath inverseComplexPath = complexPath;
913 inverseComplexPath.toggleInverseFillType();
915 // Inverse filled rect + intersect -> diff rect
916 run_test_case(r, TestCase::Build("inverse-rect-intersect", kDeviceBounds)
917 .actual().aa().intersect().path(inverseRectPath).finishElements()
918 .expect().aa().difference().rect(rect).finishElements()
919 .state(ClipState::kComplex)
922 // Inverse filled rect + difference -> int. rect
923 run_test_case(r, TestCase::Build("inverse-rect-difference", kDeviceBounds)
924 .actual().aa().difference().path(inverseRectPath).finishElements()
925 .expect().aa().intersect().rect(rect).finishElements()
926 .state(ClipState::kDeviceRect)
929 // Inverse filled path + intersect -> diff path
930 run_test_case(r, TestCase::Build("inverse-path-intersect", kDeviceBounds)
931 .actual().aa().intersect().path(inverseComplexPath).finishElements()
932 .expect().aa().difference().path(complexPath).finishElements()
933 .state(ClipState::kComplex)
936 // Inverse filled path + difference -> int. path
937 run_test_case(r, TestCase::Build("inverse-path-difference", kDeviceBounds)
938 .actual().aa().difference().path(inverseComplexPath).finishElements()
939 .expect().aa().intersect().path(complexPath).finishElements()
940 .state(ClipState::kComplex)
944 // Tests that clip operations that are offscreen either make the clip empty or stay wide open
945 DEF_TEST(ClipStack_Offscreen, r) {
946 using ClipState = skgpu::v1::ClipStack::ClipState;
948 SkRect offscreenRect = {kDeviceBounds.fRight + 10.f, kDeviceBounds.fTop + 20.f,
949 kDeviceBounds.fRight + 40.f, kDeviceBounds.fTop + 60.f};
950 SkASSERT(!offscreenRect.intersects(SkRect::Make(kDeviceBounds)));
952 SkRRect offscreenRRect = SkRRect::MakeRectXY(offscreenRect, 5.f, 5.f);
953 SkPath offscreenPath = make_octagon(offscreenRect);
955 // Intersect -> empty
956 run_test_case(r, TestCase::Build("intersect-combo", kDeviceBounds)
957 .actual().aa().intersect()
959 .rrect(offscreenRRect)
962 .state(ClipState::kEmpty)
964 run_test_case(r, TestCase::Build("intersect-rect", kDeviceBounds)
965 .actual().aa().intersect()
968 .state(ClipState::kEmpty)
970 run_test_case(r, TestCase::Build("intersect-rrect", kDeviceBounds)
971 .actual().aa().intersect()
972 .rrect(offscreenRRect)
974 .state(ClipState::kEmpty)
976 run_test_case(r, TestCase::Build("intersect-path", kDeviceBounds)
977 .actual().aa().intersect()
980 .state(ClipState::kEmpty)
983 // Difference -> wide open
984 run_test_case(r, TestCase::Build("difference-combo", kDeviceBounds)
985 .actual().aa().difference()
987 .rrect(offscreenRRect)
990 .state(ClipState::kWideOpen)
992 run_test_case(r, TestCase::Build("difference-rect", kDeviceBounds)
993 .actual().aa().difference()
996 .state(ClipState::kWideOpen)
998 run_test_case(r, TestCase::Build("difference-rrect", kDeviceBounds)
999 .actual().aa().difference()
1000 .rrect(offscreenRRect)
1002 .state(ClipState::kWideOpen)
1004 run_test_case(r, TestCase::Build("difference-path", kDeviceBounds)
1005 .actual().aa().difference()
1006 .path(offscreenPath)
1008 .state(ClipState::kWideOpen)
1012 // Tests that an empty shape updates the clip state directly without needing an element
1013 DEF_TEST(ClipStack_EmptyShape, r) {
1014 using ClipState = skgpu::v1::ClipStack::ClipState;
1016 // Intersect -> empty
1017 run_test_case(r, TestCase::Build("empty-intersect", kDeviceBounds)
1018 .actual().intersect().rect(SkRect::MakeEmpty()).finishElements()
1019 .state(ClipState::kEmpty)
1022 // Difference -> no-op
1023 run_test_case(r, TestCase::Build("empty-difference", kDeviceBounds)
1024 .actual().difference().rect(SkRect::MakeEmpty()).finishElements()
1025 .state(ClipState::kWideOpen)
1028 SkRRect rrect = SkRRect::MakeRectXY({4.f, 10.f, 16.f, 32.f}, 2.f, 2.f);
1029 run_test_case(r, TestCase::Build("noop-difference", kDeviceBounds)
1030 .actual().difference().rrect(rrect).rect(SkRect::MakeEmpty())
1032 .expect().difference().rrect(rrect).finishElements()
1033 .state(ClipState::kComplex)
1037 // Tests that sufficiently large difference operations can shrink the conservative bounds
1038 DEF_TEST(ClipStack_DifferenceBounds, r) {
1039 using ClipState = skgpu::v1::ClipStack::ClipState;
1041 SkRect rightSide = {50.f, -10.f, 2.f * kDeviceBounds.fRight, kDeviceBounds.fBottom + 10.f};
1042 SkRect clipped = rightSide;
1043 SkAssertResult(clipped.intersect(SkRect::Make(kDeviceBounds)));
1045 run_test_case(r, TestCase::Build("difference-cut", kDeviceBounds)
1046 .actual().nonAA().difference().rect(rightSide).finishElements()
1047 .expect().nonAA().difference().rect(clipped).finishElements()
1048 .state(ClipState::kComplex)
1052 // Tests that intersections can combine even if there's a difference operation in the middle
1053 DEF_TEST(ClipStack_NoDifferenceInterference, r) {
1054 using ClipState = skgpu::v1::ClipStack::ClipState;
1056 SkRect intR1 = {0.f, 0.f, 30.f, 30.f};
1057 SkRect intR2 = {15.f, 15.f, 45.f, 45.f};
1058 SkRect intCombo = {15.f, 15.f, 30.f, 30.f};
1059 SkRect diff = {20.f, 6.f, 50.f, 50.f};
1061 run_test_case(r, TestCase::Build("cross-diff-combine", kDeviceBounds)
1062 .actual().rect(intR1, GrAA::kYes, SkClipOp::kIntersect)
1063 .rect(diff, GrAA::kYes, SkClipOp::kDifference)
1064 .rect(intR2, GrAA::kYes, SkClipOp::kIntersect)
1066 .expect().rect(intCombo, GrAA::kYes, SkClipOp::kIntersect)
1067 .rect(diff, GrAA::kYes, SkClipOp::kDifference)
1069 .state(ClipState::kComplex)
1073 // Tests that multiple path operations are all recorded, but not otherwise consolidated
1074 DEF_TEST(ClipStack_MultiplePaths, r) {
1075 using ClipState = skgpu::v1::ClipStack::ClipState;
1077 // Chosen to be greater than the number of inline-allocated elements and save records of the
1078 // ClipStack so that we test heap allocation as well.
1079 static constexpr int kNumOps = 16;
1081 auto b = TestCase::Build("many-paths-difference", kDeviceBounds);
1082 SkRect d = {0.f, 0.f, 12.f, 12.f};
1083 for (int i = 0; i < kNumOps; ++i) {
1084 b.actual().path(make_octagon(d), GrAA::kNo, SkClipOp::kDifference);
1086 d.offset(15.f, 0.f);
1087 if (d.fRight > kDeviceBounds.fRight) {
1090 d.offset(0.f, 15.f);
1094 run_test_case(r, b.expectActual()
1095 .state(ClipState::kComplex)
1098 b = TestCase::Build("many-paths-intersect", kDeviceBounds);
1099 d = {0.f, 0.f, 12.f, 12.f};
1100 for (int i = 0; i < kNumOps; ++i) {
1101 b.actual().path(make_octagon(d), GrAA::kYes, SkClipOp::kIntersect);
1102 d.offset(0.01f, 0.01f);
1105 run_test_case(r, b.expectActual()
1106 .state(ClipState::kComplex)
1110 // Tests that a single rect is treated as kDeviceRect state when it's axis-aligned and intersect.
1111 DEF_TEST(ClipStack_DeviceRect, r) {
1112 using ClipState = skgpu::v1::ClipStack::ClipState;
1114 // Axis-aligned + intersect -> kDeviceRect
1115 SkRect rect = {0, 0, 20, 20};
1116 run_test_case(r, TestCase::Build("device-rect", kDeviceBounds)
1117 .actual().intersect().aa().rect(rect).finishElements()
1119 .state(ClipState::kDeviceRect)
1122 // Not axis-aligned -> kComplex
1123 SkMatrix lm = SkMatrix::RotateDeg(15.f);
1124 run_test_case(r, TestCase::Build("unaligned-rect", kDeviceBounds)
1125 .actual().localToDevice(lm).intersect().aa().rect(rect)
1128 .state(ClipState::kComplex)
1131 // Not intersect -> kComplex
1132 run_test_case(r, TestCase::Build("diff-rect", kDeviceBounds)
1133 .actual().difference().aa().rect(rect).finishElements()
1135 .state(ClipState::kComplex)
1139 // Tests that a single rrect is treated as kDeviceRRect state when it's axis-aligned and intersect.
1140 DEF_TEST(ClipStack_DeviceRRect, r) {
1141 using ClipState = skgpu::v1::ClipStack::ClipState;
1143 // Axis-aligned + intersect -> kDeviceRRect
1144 SkRect rect = {0, 0, 20, 20};
1145 SkRRect rrect = SkRRect::MakeRectXY(rect, 5.f, 5.f);
1146 run_test_case(r, TestCase::Build("device-rrect", kDeviceBounds)
1147 .actual().intersect().aa().rrect(rrect).finishElements()
1149 .state(ClipState::kDeviceRRect)
1152 // Not axis-aligned -> kComplex
1153 SkMatrix lm = SkMatrix::RotateDeg(15.f);
1154 run_test_case(r, TestCase::Build("unaligned-rrect", kDeviceBounds)
1155 .actual().localToDevice(lm).intersect().aa().rrect(rrect)
1158 .state(ClipState::kComplex)
1161 // Not intersect -> kComplex
1162 run_test_case(r, TestCase::Build("diff-rrect", kDeviceBounds)
1163 .actual().difference().aa().rrect(rrect).finishElements()
1165 .state(ClipState::kComplex)
1169 // Tests that scale+translate matrices are pre-applied to rects and rrects, which also then allows
1170 // elements with different scale+translate matrices to be consolidated as if they were in the same
1171 // coordinate space.
1172 DEF_TEST(ClipStack_ScaleTranslate, r) {
1173 using ClipState = skgpu::v1::ClipStack::ClipState;
1175 SkMatrix lm = SkMatrix::Scale(2.f, 4.f);
1176 lm.postTranslate(15.5f, 14.3f);
1177 SkASSERT(lm.preservesAxisAlignment() && lm.isScaleTranslate());
1179 // Rect -> matrix is applied up front
1180 SkRect rect = {0.f, 0.f, 10.f, 10.f};
1181 run_test_case(r, TestCase::Build("st+rect", kDeviceBounds)
1182 .actual().rect(rect, lm, GrAA::kYes, SkClipOp::kIntersect)
1184 .expect().rect(lm.mapRect(rect), GrAA::kYes, SkClipOp::kIntersect)
1186 .state(ClipState::kDeviceRect)
1189 // RRect -> matrix is applied up front
1190 SkRRect localRRect = SkRRect::MakeRectXY(rect, 2.f, 2.f);
1191 SkRRect deviceRRect;
1192 SkAssertResult(localRRect.transform(lm, &deviceRRect));
1193 run_test_case(r, TestCase::Build("st+rrect", kDeviceBounds)
1194 .actual().rrect(localRRect, lm, GrAA::kYes, SkClipOp::kIntersect)
1196 .expect().rrect(deviceRRect, GrAA::kYes, SkClipOp::kIntersect)
1198 .state(ClipState::kDeviceRRect)
1201 // Path -> matrix is NOT applied
1202 run_test_case(r, TestCase::Build("st+path", kDeviceBounds)
1203 .actual().intersect().localToDevice(lm).path(make_octagon(rect))
1206 .state(ClipState::kComplex)
1210 // Tests that rect-stays-rect matrices that are not scale+translate matrices are pre-applied.
1211 DEF_TEST(ClipStack_PreserveAxisAlignment, r) {
1212 using ClipState = skgpu::v1::ClipStack::ClipState;
1214 SkMatrix lm = SkMatrix::RotateDeg(90.f);
1215 lm.postTranslate(15.5f, 14.3f);
1216 SkASSERT(lm.preservesAxisAlignment() && !lm.isScaleTranslate());
1218 // Rect -> matrix is applied up front
1219 SkRect rect = {0.f, 0.f, 10.f, 10.f};
1220 run_test_case(r, TestCase::Build("r90+rect", kDeviceBounds)
1221 .actual().rect(rect, lm, GrAA::kYes, SkClipOp::kIntersect)
1223 .expect().rect(lm.mapRect(rect), GrAA::kYes, SkClipOp::kIntersect)
1225 .state(ClipState::kDeviceRect)
1228 // RRect -> matrix is applied up front
1229 SkRRect localRRect = SkRRect::MakeRectXY(rect, 2.f, 2.f);
1230 SkRRect deviceRRect;
1231 SkAssertResult(localRRect.transform(lm, &deviceRRect));
1232 run_test_case(r, TestCase::Build("r90+rrect", kDeviceBounds)
1233 .actual().rrect(localRRect, lm, GrAA::kYes, SkClipOp::kIntersect)
1235 .expect().rrect(deviceRRect, GrAA::kYes, SkClipOp::kIntersect)
1237 .state(ClipState::kDeviceRRect)
1240 // Path -> matrix is NOT applied
1241 run_test_case(r, TestCase::Build("r90+path", kDeviceBounds)
1242 .actual().intersect().localToDevice(lm).path(make_octagon(rect))
1245 .state(ClipState::kComplex)
1249 // Tests that a convex path element can contain a rect or round rect, allowing the stack to be
1251 DEF_TEST(ClipStack_ConvexPathContains, r) {
1252 using ClipState = skgpu::v1::ClipStack::ClipState;
1254 SkRect rect = {15.f, 15.f, 30.f, 30.f};
1255 SkRRect rrect = SkRRect::MakeRectXY(rect, 5.f, 5.f);
1256 SkPath bigPath = make_octagon(rect.makeOutset(10.f, 10.f), 5.f, 5.f);
1258 // Intersect -> path element isn't kept
1259 run_test_case(r, TestCase::Build("convex+rect-intersect", kDeviceBounds)
1260 .actual().aa().intersect().rect(rect).path(bigPath).finishElements()
1261 .expect().aa().intersect().rect(rect).finishElements()
1262 .state(ClipState::kDeviceRect)
1264 run_test_case(r, TestCase::Build("convex+rrect-intersect", kDeviceBounds)
1265 .actual().aa().intersect().rrect(rrect).path(bigPath).finishElements()
1266 .expect().aa().intersect().rrect(rrect).finishElements()
1267 .state(ClipState::kDeviceRRect)
1270 // Difference -> path element is the only one left
1271 run_test_case(r, TestCase::Build("convex+rect-difference", kDeviceBounds)
1272 .actual().aa().difference().rect(rect).path(bigPath).finishElements()
1273 .expect().aa().difference().path(bigPath).finishElements()
1274 .state(ClipState::kComplex)
1276 run_test_case(r, TestCase::Build("convex+rrect-difference", kDeviceBounds)
1277 .actual().aa().difference().rrect(rrect).path(bigPath)
1279 .expect().aa().difference().path(bigPath).finishElements()
1280 .state(ClipState::kComplex)
1283 // Intersect small shape + difference big path -> empty
1284 run_test_case(r, TestCase::Build("convex-diff+rect-int", kDeviceBounds)
1285 .actual().aa().intersect().rect(rect)
1286 .difference().path(bigPath).finishElements()
1287 .state(ClipState::kEmpty)
1289 run_test_case(r, TestCase::Build("convex-diff+rrect-int", kDeviceBounds)
1290 .actual().aa().intersect().rrect(rrect)
1291 .difference().path(bigPath).finishElements()
1292 .state(ClipState::kEmpty)
1295 // Diff small shape + intersect big path -> both
1296 run_test_case(r, TestCase::Build("convex-int+rect-diff", kDeviceBounds)
1297 .actual().aa().intersect().path(bigPath).difference().rect(rect)
1300 .state(ClipState::kComplex)
1302 run_test_case(r, TestCase::Build("convex-int+rrect-diff", kDeviceBounds)
1303 .actual().aa().intersect().path(bigPath).difference().rrect(rrect)
1306 .state(ClipState::kComplex)
1310 // Tests that rects/rrects in different coordinate spaces can be consolidated when one is fully
1311 // contained by the other.
1312 DEF_TEST(ClipStack_NonAxisAlignedContains, r) {
1313 using ClipState = skgpu::v1::ClipStack::ClipState;
1315 SkMatrix lm1 = SkMatrix::RotateDeg(45.f);
1316 SkRect bigR = {-20.f, -20.f, 20.f, 20.f};
1317 SkRRect bigRR = SkRRect::MakeRectXY(bigR, 1.f, 1.f);
1319 SkMatrix lm2 = SkMatrix::RotateDeg(-45.f);
1320 SkRect smR = {-10.f, -10.f, 10.f, 10.f};
1321 SkRRect smRR = SkRRect::MakeRectXY(smR, 1.f, 1.f);
1323 // I+I should select the smaller 2nd shape (r2 or rr2)
1324 run_test_case(r, TestCase::Build("rect-rect-ii", kDeviceBounds)
1325 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1326 .rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1328 .expect().rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1330 .state(ClipState::kComplex)
1332 run_test_case(r, TestCase::Build("rrect-rrect-ii", kDeviceBounds)
1333 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1334 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1336 .expect().rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1338 .state(ClipState::kComplex)
1340 run_test_case(r, TestCase::Build("rect-rrect-ii", kDeviceBounds)
1341 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1342 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1344 .expect().rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1346 .state(ClipState::kComplex)
1348 run_test_case(r, TestCase::Build("rrect-rect-ii", kDeviceBounds)
1349 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1350 .rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1352 .expect().rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1354 .state(ClipState::kComplex)
1357 // D+D should select the larger shape (r1 or rr1)
1358 run_test_case(r, TestCase::Build("rect-rect-dd", kDeviceBounds)
1359 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference)
1360 .rect(smR, lm2, GrAA::kYes, SkClipOp::kDifference)
1362 .expect().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference)
1364 .state(ClipState::kComplex)
1366 run_test_case(r, TestCase::Build("rrect-rrect-dd", kDeviceBounds)
1367 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference)
1368 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kDifference)
1370 .expect().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference)
1372 .state(ClipState::kComplex)
1374 run_test_case(r, TestCase::Build("rect-rrect-dd", kDeviceBounds)
1375 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference)
1376 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kDifference)
1378 .expect().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference)
1380 .state(ClipState::kComplex)
1382 run_test_case(r, TestCase::Build("rrect-rect-dd", kDeviceBounds)
1383 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference)
1384 .rect(smR, lm2, GrAA::kYes, SkClipOp::kDifference)
1386 .expect().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference)
1388 .state(ClipState::kComplex)
1391 // D(1)+I(2) should result in empty
1392 run_test_case(r, TestCase::Build("rectD-rectI", kDeviceBounds)
1393 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference)
1394 .rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1396 .state(ClipState::kEmpty)
1398 run_test_case(r, TestCase::Build("rrectD-rrectI", kDeviceBounds)
1399 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference)
1400 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1402 .state(ClipState::kEmpty)
1404 run_test_case(r, TestCase::Build("rectD-rrectI", kDeviceBounds)
1405 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference)
1406 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1408 .state(ClipState::kEmpty)
1410 run_test_case(r, TestCase::Build("rrectD-rectI", kDeviceBounds)
1411 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference)
1412 .rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1414 .state(ClipState::kEmpty)
1417 // I(1)+D(2) should result in both shapes
1418 run_test_case(r, TestCase::Build("rectI+rectD", kDeviceBounds)
1419 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1420 .rect(smR, lm2, GrAA::kYes, SkClipOp::kDifference)
1423 .state(ClipState::kComplex)
1425 run_test_case(r, TestCase::Build("rrectI+rrectD", kDeviceBounds)
1426 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1427 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kDifference)
1430 .state(ClipState::kComplex)
1432 run_test_case(r, TestCase::Build("rrectI+rectD", kDeviceBounds)
1433 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1434 .rect(smR, lm2, GrAA::kYes, SkClipOp::kDifference)
1437 .state(ClipState::kComplex)
1439 run_test_case(r, TestCase::Build("rectI+rrectD", kDeviceBounds)
1440 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1441 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kDifference)
1444 .state(ClipState::kComplex)
1448 // Tests that shapes with mixed AA state that contain each other can still be consolidated,
1449 // unless they are too close to the edge and non-AA snapping can't be predicted
1450 DEF_TEST(ClipStack_MixedAAContains, r) {
1451 using ClipState = skgpu::v1::ClipStack::ClipState;
1453 SkMatrix lm1 = SkMatrix::RotateDeg(45.f);
1454 SkRect r1 = {-20.f, -20.f, 20.f, 20.f};
1456 SkMatrix lm2 = SkMatrix::RotateDeg(-45.f);
1457 SkRect r2Safe = {-10.f, -10.f, 10.f, 10.f};
1458 SkRect r2Unsafe = {-19.5f, -19.5f, 19.5f, 19.5f};
1460 // Non-AA sufficiently inside AA element can discard the outer AA element
1461 run_test_case(r, TestCase::Build("mixed-outeraa-combine", kDeviceBounds)
1462 .actual().rect(r1, lm1, GrAA::kYes, SkClipOp::kIntersect)
1463 .rect(r2Safe, lm2, GrAA::kNo, SkClipOp::kIntersect)
1465 .expect().rect(r2Safe, lm2, GrAA::kNo, SkClipOp::kIntersect)
1467 .state(ClipState::kComplex)
1470 run_test_case(r, TestCase::Build("mixed-inneraa-combine", kDeviceBounds)
1471 .actual().rect(r1, lm1, GrAA::kNo, SkClipOp::kIntersect)
1472 .rect(r2Safe, lm2, GrAA::kYes, SkClipOp::kIntersect)
1474 .expect().rect(r2Safe, lm2, GrAA::kYes, SkClipOp::kIntersect)
1476 .state(ClipState::kComplex)
1479 // Non-AA too close to AA edges keeps both
1480 run_test_case(r, TestCase::Build("mixed-outeraa-nocombine", kDeviceBounds)
1481 .actual().rect(r1, lm1, GrAA::kYes, SkClipOp::kIntersect)
1482 .rect(r2Unsafe, lm2, GrAA::kNo, SkClipOp::kIntersect)
1485 .state(ClipState::kComplex)
1487 run_test_case(r, TestCase::Build("mixed-inneraa-nocombine", kDeviceBounds)
1488 .actual().rect(r1, lm1, GrAA::kNo, SkClipOp::kIntersect)
1489 .rect(r2Unsafe, lm2, GrAA::kYes, SkClipOp::kIntersect)
1492 .state(ClipState::kComplex)
1496 // Tests that a shape that contains the device bounds updates the clip state directly
1497 DEF_TEST(ClipStack_ShapeContainsDevice, r) {
1498 using ClipState = skgpu::v1::ClipStack::ClipState;
1500 SkRect rect = SkRect::Make(kDeviceBounds).makeOutset(10.f, 10.f);
1501 SkRRect rrect = SkRRect::MakeRectXY(rect, 10.f, 10.f);
1502 SkPath convex = make_octagon(rect, 10.f, 10.f);
1504 // Intersect -> no-op
1505 run_test_case(r, TestCase::Build("rect-intersect", kDeviceBounds)
1506 .actual().intersect().rect(rect).finishElements()
1507 .state(ClipState::kWideOpen)
1509 run_test_case(r, TestCase::Build("rrect-intersect", kDeviceBounds)
1510 .actual().intersect().rrect(rrect).finishElements()
1511 .state(ClipState::kWideOpen)
1513 run_test_case(r, TestCase::Build("convex-intersect", kDeviceBounds)
1514 .actual().intersect().path(convex).finishElements()
1515 .state(ClipState::kWideOpen)
1518 // Difference -> empty
1519 run_test_case(r, TestCase::Build("rect-difference", kDeviceBounds)
1520 .actual().difference().rect(rect).finishElements()
1521 .state(ClipState::kEmpty)
1523 run_test_case(r, TestCase::Build("rrect-difference", kDeviceBounds)
1524 .actual().difference().rrect(rrect).finishElements()
1525 .state(ClipState::kEmpty)
1527 run_test_case(r, TestCase::Build("convex-difference", kDeviceBounds)
1528 .actual().difference().path(convex).finishElements()
1529 .state(ClipState::kEmpty)
1533 // Tests that shapes that do not overlap make for an empty clip (when intersecting), pick just the
1534 // intersecting op (when mixed), or are all kept (when diff'ing).
1535 DEF_TEST(ClipStack_DisjointShapes, r) {
1536 using ClipState = skgpu::v1::ClipStack::ClipState;
1538 SkRect rt = {10.f, 10.f, 20.f, 20.f};
1539 SkRRect rr = SkRRect::MakeOval(rt.makeOffset({20.f, 0.f}));
1540 SkPath p = make_octagon(rt.makeOffset({0.f, 20.f}));
1543 run_test_case(r, TestCase::Build("iii", kDeviceBounds)
1544 .actual().aa().intersect().rect(rt).rrect(rr).path(p).finishElements()
1545 .state(ClipState::kEmpty)
1549 run_test_case(r, TestCase::Build("ddd", kDeviceBounds)
1550 .actual().nonAA().difference().rect(rt).rrect(rr).path(p)
1553 .state(ClipState::kComplex)
1557 run_test_case(r, TestCase::Build("idd", kDeviceBounds)
1558 .actual().aa().intersect().rect(rt)
1559 .nonAA().difference().rrect(rr).path(p)
1561 .expect().aa().intersect().rect(rt).finishElements()
1562 .state(ClipState::kDeviceRect)
1566 run_test_case(r, TestCase::Build("did", kDeviceBounds)
1567 .actual().aa().intersect().rrect(rr)
1568 .nonAA().difference().rect(rt).path(p)
1570 .expect().aa().intersect().rrect(rr).finishElements()
1571 .state(ClipState::kDeviceRRect)
1575 run_test_case(r, TestCase::Build("ddi", kDeviceBounds)
1576 .actual().aa().intersect().path(p)
1577 .nonAA().difference().rect(rt).rrect(rr)
1579 .expect().aa().intersect().path(p).finishElements()
1580 .state(ClipState::kComplex)
1584 DEF_TEST(ClipStack_ComplexClip, reporter) {
1585 using ClipStack = skgpu::v1::ClipStack;
1587 static constexpr float kN = 10.f;
1588 static constexpr float kR = kN / 3.f;
1590 // 4 rectangles that overlap by kN x 2kN (horiz), 2kN x kN (vert), or kN x kN (diagonal)
1591 static const SkRect kTL = {0.f, 0.f, 2.f * kN, 2.f * kN};
1592 static const SkRect kTR = {kN, 0.f, 3.f * kN, 2.f * kN};
1593 static const SkRect kBL = {0.f, kN, 2.f * kN, 3.f * kN};
1594 static const SkRect kBR = {kN, kN, 3.f * kN, 3.f * kN};
1596 enum ShapeType { kRect, kRRect, kConvex };
1598 SkRect rects[] = { kTL, kTR, kBL, kBR };
1599 for (ShapeType type : { kRect, kRRect, kConvex }) {
1600 for (int opBits = 6; opBits < 16; ++opBits) {
1602 name.appendf("complex-%d-%d", (int) type, opBits);
1604 SkRect expectedRectIntersection = SkRect::Make(kDeviceBounds);
1605 SkRRect expectedRRectIntersection = SkRRect::MakeRect(expectedRectIntersection);
1607 auto b = TestCase::Build(name.c_str(), kDeviceBounds);
1608 for (int i = 0; i < 4; ++i) {
1609 SkClipOp op = (opBits & (1 << i)) ? SkClipOp::kIntersect : SkClipOp::kDifference;
1612 SkRect r = rects[i];
1613 if (op == SkClipOp::kDifference) {
1614 // Shrink the rect for difference ops, otherwise in the rect testcase
1615 // any difference op would remove the intersection of the other ops
1616 // given how the rects are defined, and that's just not interesting.
1619 b.actual().rect(r, GrAA::kYes, op);
1620 if (op == SkClipOp::kIntersect) {
1621 SkAssertResult(expectedRectIntersection.intersect(r));
1623 b.expect().rect(r, GrAA::kYes, SkClipOp::kDifference);
1627 SkRRect rrect = SkRRect::MakeRectXY(rects[i], kR, kR);
1628 b.actual().rrect(rrect, GrAA::kYes, op);
1629 if (op == SkClipOp::kIntersect) {
1630 expectedRRectIntersection = SkRRectPriv::ConservativeIntersect(
1631 expectedRRectIntersection, rrect);
1632 SkASSERT(!expectedRRectIntersection.isEmpty());
1634 b.expect().rrect(rrect, GrAA::kYes, SkClipOp::kDifference);
1638 b.actual().path(make_octagon(rects[i], kR, kR), GrAA::kYes, op);
1639 // NOTE: We don't set any expectations here, since convex just calls
1640 // expectActual() at the end.
1645 // The expectations differ depending on the shape type
1646 ClipStack::ClipState state = ClipStack::ClipState::kComplex;
1647 if (type == kConvex) {
1648 // The simplest case is when the paths cannot be combined together, so we expect
1649 // the actual elements to be unmodified (both intersect and difference).
1651 } else if (opBits) {
1652 // All intersection ops were pre-computed into expectedR[R]ectIntersection
1653 // - difference ops already added in the for loop
1654 if (type == kRect) {
1655 SkASSERT(expectedRectIntersection != SkRect::Make(kDeviceBounds) &&
1656 !expectedRectIntersection.isEmpty());
1657 b.expect().rect(expectedRectIntersection, GrAA::kYes, SkClipOp::kIntersect);
1658 if (opBits == 0xf) {
1659 state = ClipStack::ClipState::kDeviceRect;
1662 SkASSERT(expectedRRectIntersection !=
1663 SkRRect::MakeRect(SkRect::Make(kDeviceBounds)) &&
1664 !expectedRRectIntersection.isEmpty());
1665 b.expect().rrect(expectedRRectIntersection, GrAA::kYes, SkClipOp::kIntersect);
1666 if (opBits == 0xf) {
1667 state = ClipStack::ClipState::kDeviceRRect;
1672 run_test_case(reporter, b.state(state).finishTest());
1677 // ///////////////////////////////////////////////////////////////////////////////
1678 // // These tests do not use the TestCase infrastructure and manipulate a
1679 // // ClipStack directly.
1681 // Tests that replaceClip() works as expected across save/restores
1682 DEF_TEST(ClipStack_ReplaceClip, r) {
1683 using ClipStack = skgpu::v1::ClipStack;
1685 ClipStack cs(kDeviceBounds, nullptr, false);
1687 SkRRect rrect = SkRRect::MakeRectXY({15.f, 12.25f, 40.3f, 23.5f}, 4.f, 6.f);
1688 cs.clipRRect(SkMatrix::I(), rrect, GrAA::kYes, SkClipOp::kIntersect);
1690 SkIRect replace = {50, 25, 75, 40}; // Is disjoint from the rrect element
1692 cs.replaceClip(replace);
1694 REPORTER_ASSERT(r, cs.clipState() == ClipStack::ClipState::kDeviceRect,
1695 "Clip did not become a device rect");
1696 REPORTER_ASSERT(r, cs.getConservativeBounds() == replace, "Unexpected replaced clip bounds");
1697 const ClipStack::Element& replaceElement = *cs.begin();
1698 REPORTER_ASSERT(r, replaceElement.fShape.rect() == SkRect::Make(replace) &&
1699 replaceElement.fAA == GrAA::kNo &&
1700 replaceElement.fOp == SkClipOp::kIntersect &&
1701 replaceElement.fLocalToDevice == SkMatrix::I(),
1702 "Unexpected replace element state");
1704 // Restore should undo the replaced clip and bring back the rrect
1706 REPORTER_ASSERT(r, cs.clipState() == ClipStack::ClipState::kDeviceRRect,
1707 "Unexpected state after restore, not kDeviceRRect");
1708 const ClipStack::Element& rrectElem = *cs.begin();
1709 REPORTER_ASSERT(r, rrectElem.fShape.rrect() == rrect &&
1710 rrectElem.fAA == GrAA::kYes &&
1711 rrectElem.fOp == SkClipOp::kIntersect &&
1712 rrectElem.fLocalToDevice == SkMatrix::I(),
1713 "RRect element state not restored properly after replace clip undone");
1716 // Try to overflow the number of allowed window rects (see skbug.com/10989)
1717 DEF_TEST(ClipStack_DiffRects, r) {
1718 using ClipStack = skgpu::v1::ClipStack;
1719 using SurfaceDrawContext = skgpu::v1::SurfaceDrawContext;
1721 GrMockOptions options;
1722 options.fMaxWindowRectangles = 8;
1724 SkMatrixProvider matrixProvider = SkMatrix::I();
1725 sk_sp<GrDirectContext> context = GrDirectContext::MakeMock(&options);
1726 std::unique_ptr<SurfaceDrawContext> sdc = SurfaceDrawContext::Make(
1727 context.get(), GrColorType::kRGBA_8888, SkColorSpace::MakeSRGB(),
1728 SkBackingFit::kExact, kDeviceBounds.size(), SkSurfaceProps());
1730 ClipStack cs(kDeviceBounds, &matrixProvider, false);
1733 for (int y = 0; y < 10; ++y) {
1734 for (int x = 0; x < 10; ++x) {
1735 cs.clipRect(SkMatrix::I(), SkRect::MakeXYWH(10*x+1, 10*y+1, 8, 8),
1736 GrAA::kNo, SkClipOp::kDifference);
1740 GrAppliedClip out(kDeviceBounds.size());
1741 SkRect drawBounds = SkRect::Make(kDeviceBounds);
1742 GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage,
1745 REPORTER_ASSERT(r, effect == GrClip::Effect::kClipped);
1746 REPORTER_ASSERT(r, out.windowRectsState().numWindows() == 8);
1751 // Tests that when a stack is forced to always be AA, non-AA elements become AA
1752 DEF_TEST(ClipStack_ForceAA, r) {
1753 using ClipStack = skgpu::v1::ClipStack;
1755 ClipStack cs(kDeviceBounds, nullptr, true);
1757 // AA will remain AA
1758 SkRect aaRect = {0.25f, 12.43f, 25.2f, 23.f};
1759 cs.clipRect(SkMatrix::I(), aaRect, GrAA::kYes, SkClipOp::kIntersect);
1761 // Non-AA will become AA
1762 SkPath nonAAPath = make_octagon({2.f, 10.f, 16.f, 20.f});
1763 cs.clipPath(SkMatrix::I(), nonAAPath, GrAA::kNo, SkClipOp::kIntersect);
1765 // Non-AA rects remain non-AA so they can be applied as a scissor
1766 SkRect nonAARect = {4.5f, 5.f, 17.25f, 18.23f};
1767 cs.clipRect(SkMatrix::I(), nonAARect, GrAA::kNo, SkClipOp::kIntersect);
1769 // The stack reports elements newest first, but the non-AA rect op was combined in place with
1770 // the first aa rect, so we should see nonAAPath as AA, and then the intersection of rects.
1771 auto elements = cs.begin();
1773 const ClipStack::Element& nonAARectElement = *elements;
1774 REPORTER_ASSERT(r, nonAARectElement.fShape.isRect(), "Expected rect element");
1775 REPORTER_ASSERT(r, nonAARectElement.fAA == GrAA::kNo,
1776 "Axis-aligned non-AA rect ignores forceAA");
1777 REPORTER_ASSERT(r, nonAARectElement.fShape.rect() == nonAARect,
1778 "Mixed AA rects should not combine");
1781 const ClipStack::Element& aaPathElement = *elements;
1782 REPORTER_ASSERT(r, aaPathElement.fShape.isPath(), "Expected path element");
1783 REPORTER_ASSERT(r, aaPathElement.fShape.path() == nonAAPath, "Wrong path element");
1784 REPORTER_ASSERT(r, aaPathElement.fAA == GrAA::kYes, "Path element not promoted to AA");
1787 const ClipStack::Element& aaRectElement = *elements;
1788 REPORTER_ASSERT(r, aaRectElement.fShape.isRect(), "Expected rect element");
1789 REPORTER_ASSERT(r, aaRectElement.fShape.rect() == aaRect,
1790 "Mixed AA rects should not combine");
1791 REPORTER_ASSERT(r, aaRectElement.fAA == GrAA::kYes, "Rect element stays AA");
1794 REPORTER_ASSERT(r, !(elements != cs.end()), "Expected only three clip elements");
1797 // Tests preApply works as expected for device rects, rrects, and reports clipped-out, etc. as
1799 DEF_TEST(ClipStack_PreApply, r) {
1800 using ClipStack = skgpu::v1::ClipStack;
1802 ClipStack cs(kDeviceBounds, nullptr, false);
1804 // Offscreen is kClippedOut
1805 GrClip::PreClipResult result = cs.preApply({-10.f, -10.f, -1.f, -1.f}, GrAA::kYes);
1806 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClippedOut,
1807 "Offscreen draw is kClippedOut");
1809 // Intersecting screen with wide-open clip is kUnclipped
1810 result = cs.preApply({-10.f, -10.f, 10.f, 10.f}, GrAA::kYes);
1811 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kUnclipped,
1812 "Wide open screen intersection is still kUnclipped");
1814 // Empty clip is clipped out
1816 cs.clipRect(SkMatrix::I(), SkRect::MakeEmpty(), GrAA::kNo, SkClipOp::kIntersect);
1817 result = cs.preApply({0.f, 0.f, 20.f, 20.f}, GrAA::kYes);
1818 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClippedOut,
1819 "Empty clip stack preApplies as kClippedOut");
1822 // Contained inside clip is kUnclipped (using rrect for the outer clip element since paths
1823 // don't support an inner bounds and anything complex is otherwise skipped in preApply).
1824 SkRect rect = {10.f, 10.f, 40.f, 40.f};
1825 SkRRect bigRRect = SkRRect::MakeRectXY(rect.makeOutset(5.f, 5.f), 5.f, 5.f);
1827 cs.clipRRect(SkMatrix::I(), bigRRect, GrAA::kYes, SkClipOp::kIntersect);
1828 result = cs.preApply(rect, GrAA::kYes);
1829 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kUnclipped,
1830 "Draw contained within clip is kUnclipped");
1832 // Disjoint from clip (but still on screen) is kClippedOut
1833 result = cs.preApply({50.f, 50.f, 60.f, 60.f}, GrAA::kYes);
1834 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClippedOut,
1835 "Draw not intersecting clip is kClippedOut");
1838 // Intersecting clip is kClipped for complex shape
1840 SkPath path = make_octagon(rect.makeOutset(5.f, 5.f), 5.f, 5.f);
1841 cs.clipPath(SkMatrix::I(), path, GrAA::kYes, SkClipOp::kIntersect);
1842 result = cs.preApply(path.getBounds(), GrAA::kNo);
1843 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClipped && !result.fIsRRect,
1844 "Draw with complex clip is kClipped, but is not an rrect");
1847 // Intersecting clip is kDeviceRect for axis-aligned rect clip
1849 cs.clipRect(SkMatrix::I(), rect, GrAA::kYes, SkClipOp::kIntersect);
1850 result = cs.preApply(rect.makeOffset(2.f, 2.f), GrAA::kNo);
1851 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClipped &&
1852 result.fAA == GrAA::kYes &&
1854 result.fRRect == SkRRect::MakeRect(rect),
1855 "kDeviceRect clip stack should be reported by preApply");
1858 // Intersecting clip is kDeviceRRect for axis-aligned rrect clip
1860 SkRRect clipRRect = SkRRect::MakeRectXY(rect, 5.f, 5.f);
1861 cs.clipRRect(SkMatrix::I(), clipRRect, GrAA::kYes, SkClipOp::kIntersect);
1862 result = cs.preApply(rect.makeOffset(2.f, 2.f), GrAA::kNo);
1863 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClipped &&
1864 result.fAA == GrAA::kYes &&
1866 result.fRRect == clipRRect,
1867 "kDeviceRRect clip stack should be reported by preApply");
1871 // Tests the clip shader entry point
1872 DEF_TEST(ClipStack_Shader, r) {
1873 using ClipStack = skgpu::v1::ClipStack;
1874 using SurfaceDrawContext = skgpu::v1::SurfaceDrawContext;
1876 sk_sp<SkShader> shader = SkShaders::Color({0.f, 0.f, 0.f, 0.5f}, nullptr);
1878 SkMatrixProvider matrixProvider = SkMatrix::I();
1879 sk_sp<GrDirectContext> context = GrDirectContext::MakeMock(nullptr);
1880 std::unique_ptr<SurfaceDrawContext> sdc = SurfaceDrawContext::Make(
1881 context.get(), GrColorType::kRGBA_8888, SkColorSpace::MakeSRGB(),
1882 SkBackingFit::kExact, kDeviceBounds.size(), SkSurfaceProps());
1884 ClipStack cs(kDeviceBounds, &matrixProvider, false);
1886 cs.clipShader(shader);
1888 REPORTER_ASSERT(r, cs.clipState() == ClipStack::ClipState::kComplex,
1889 "A clip shader should be reported as a complex clip");
1891 GrAppliedClip out(kDeviceBounds.size());
1892 SkRect drawBounds = {10.f, 11.f, 16.f, 32.f};
1893 GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage,
1896 REPORTER_ASSERT(r, effect == GrClip::Effect::kClipped,
1897 "apply() should return kClipped for a clip shader");
1898 REPORTER_ASSERT(r, out.hasCoverageFragmentProcessor(),
1899 "apply() should have converted clip shader to a coverage FP");
1901 GrAppliedClip out2(kDeviceBounds.size());
1902 drawBounds = {-15.f, -10.f, -1.f, 10.f}; // offscreen
1903 effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage, &out2,
1905 REPORTER_ASSERT(r, effect == GrClip::Effect::kClippedOut,
1906 "apply() should still discard offscreen draws with a clip shader");
1909 REPORTER_ASSERT(r, cs.clipState() == ClipStack::ClipState::kWideOpen,
1910 "restore() should get rid of the clip shader");
1913 // Adding a clip shader on top of a device rect clip should prevent preApply from reporting
1914 // it as a device rect
1915 cs.clipRect(SkMatrix::I(), {10, 15, 30, 30}, GrAA::kNo, SkClipOp::kIntersect);
1916 SkASSERT(cs.clipState() == ClipStack::ClipState::kDeviceRect); // test precondition
1917 cs.clipShader(shader);
1918 GrClip::PreClipResult result = cs.preApply(SkRect::Make(kDeviceBounds), GrAA::kYes);
1919 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClipped && !result.fIsRRect,
1920 "A clip shader should not produce a device rect from preApply");
1923 // Tests apply() under simple circumstances, that don't require actual rendering of masks, or
1924 // atlases. This lets us define the test regularly instead of a GPU-only test.
1925 // - This is not exhaustive and is challenging to unit test, so apply() is predominantly tested by
1927 DEF_TEST(ClipStack_SimpleApply, r) {
1928 using ClipStack = skgpu::v1::ClipStack;
1929 using SurfaceDrawContext = skgpu::v1::SurfaceDrawContext;
1931 SkMatrixProvider matrixProvider = SkMatrix::I();
1932 sk_sp<GrDirectContext> context = GrDirectContext::MakeMock(nullptr);
1933 std::unique_ptr<SurfaceDrawContext> sdc = SurfaceDrawContext::Make(
1934 context.get(), GrColorType::kRGBA_8888, SkColorSpace::MakeSRGB(),
1935 SkBackingFit::kExact, kDeviceBounds.size(), SkSurfaceProps());
1937 ClipStack cs(kDeviceBounds, &matrixProvider, false);
1939 // Offscreen draw is kClippedOut
1941 SkRect drawBounds = {-15.f, -15.f, -1.f, -1.f};
1943 GrAppliedClip out(kDeviceBounds.size());
1944 GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage,
1946 REPORTER_ASSERT(r, effect == GrClip::Effect::kClippedOut, "Offscreen draw is clipped out");
1949 // Draw contained in clip is kUnclipped
1951 SkRect drawBounds = {15.4f, 16.3f, 26.f, 32.f};
1953 cs.clipPath(SkMatrix::I(), make_octagon(drawBounds.makeOutset(5.f, 5.f), 5.f, 5.f),
1954 GrAA::kYes, SkClipOp::kIntersect);
1956 GrAppliedClip out(kDeviceBounds.size());
1957 GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage,
1959 REPORTER_ASSERT(r, effect == GrClip::Effect::kUnclipped, "Draw inside clip is unclipped");
1963 // Draw bounds are cropped to device space before checking contains
1965 SkRect clipRect = {kDeviceBounds.fRight - 20.f, 10.f, kDeviceBounds.fRight, 20.f};
1966 SkRect drawRect = clipRect.makeOffset(10.f, 0.f);
1969 cs.clipRect(SkMatrix::I(), clipRect, GrAA::kNo, SkClipOp::kIntersect);
1971 GrAppliedClip out(kDeviceBounds.size());
1972 GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage,
1974 REPORTER_ASSERT(r, SkRect::Make(kDeviceBounds).contains(drawRect),
1975 "Draw rect should be clipped to device rect");
1976 REPORTER_ASSERT(r, effect == GrClip::Effect::kUnclipped,
1977 "After device clipping, this should be detected as contained within clip");
1981 // Non-AA device rect intersect is just a scissor
1983 SkRect clipRect = {15.3f, 17.23f, 30.2f, 50.8f};
1984 SkRect drawRect = clipRect.makeOutset(10.f, 10.f);
1985 SkIRect expectedScissor = clipRect.round();
1988 cs.clipRect(SkMatrix::I(), clipRect, GrAA::kNo, SkClipOp::kIntersect);
1990 GrAppliedClip out(kDeviceBounds.size());
1991 GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage,
1993 REPORTER_ASSERT(r, effect == GrClip::Effect::kClipped, "Draw should be clipped by rect");
1994 REPORTER_ASSERT(r, !out.hasCoverageFragmentProcessor(), "Clip should not use coverage FPs");
1995 REPORTER_ASSERT(r, !out.hardClip().hasStencilClip(), "Clip should not need stencil");
1996 REPORTER_ASSERT(r, !out.hardClip().windowRectsState().enabled(),
1997 "Clip should not need window rects");
1998 REPORTER_ASSERT(r, out.scissorState().enabled() &&
1999 out.scissorState().rect() == expectedScissor,
2000 "Clip has unexpected scissor rectangle");
2004 // Analytic coverage FPs
2005 auto testHasCoverageFP = [&](SkRect drawBounds) {
2006 GrAppliedClip out(kDeviceBounds.size());
2007 GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage,
2009 REPORTER_ASSERT(r, effect == GrClip::Effect::kClipped, "Draw should be clipped");
2010 REPORTER_ASSERT(r, out.scissorState().enabled(), "Coverage FPs should still set scissor");
2011 REPORTER_ASSERT(r, out.hasCoverageFragmentProcessor(), "Clip should use coverage FP");
2014 // Axis-aligned rect can be an analytic FP
2017 cs.clipRect(SkMatrix::I(), {10.2f, 8.342f, 63.f, 23.3f}, GrAA::kYes,
2018 SkClipOp::kDifference);
2019 testHasCoverageFP({9.f, 10.f, 30.f, 18.f});
2023 // Axis-aligned round rect can be an analytic FP
2025 SkRect rect = {4.f, 8.f, 20.f, 20.f};
2027 cs.clipRRect(SkMatrix::I(), SkRRect::MakeRectXY(rect, 3.f, 3.f), GrAA::kYes,
2028 SkClipOp::kIntersect);
2029 testHasCoverageFP(rect.makeOffset(2.f, 2.f));
2033 // Transformed rect can be an analytic FP
2035 SkRect rect = {14.f, 8.f, 30.f, 22.34f};
2036 SkMatrix rot = SkMatrix::RotateDeg(34.f);
2038 cs.clipRect(rot, rect, GrAA::kNo, SkClipOp::kIntersect);
2039 testHasCoverageFP(rot.mapRect(rect));
2043 // Convex polygons can be an analytic FP
2045 SkRect rect = {15.f, 15.f, 45.f, 45.f};
2047 cs.clipPath(SkMatrix::I(), make_octagon(rect), GrAA::kYes, SkClipOp::kIntersect);
2048 testHasCoverageFP(rect.makeOutset(2.f, 2.f));
2053 // Must disable tessellation in order to trigger SW mask generation when the clip stack is applied.
2054 static void disable_tessellation_atlas(GrContextOptions* options) {
2055 options->fGpuPathRenderers = GpuPathRenderers::kNone;
2056 options->fAvoidStencilBuffers = true;
2059 DEF_GPUTEST_FOR_CONTEXTS(ClipStack_SWMask,
2060 sk_gpu_test::GrContextFactory::IsRenderingContext,
2061 r, ctxInfo, disable_tessellation_atlas) {
2062 using ClipStack = skgpu::v1::ClipStack;
2063 using SurfaceDrawContext = skgpu::v1::SurfaceDrawContext;
2065 GrDirectContext* context = ctxInfo.directContext();
2066 std::unique_ptr<SurfaceDrawContext> sdc = SurfaceDrawContext::Make(
2067 context, GrColorType::kRGBA_8888, nullptr, SkBackingFit::kExact, kDeviceBounds.size(),
2070 SkMatrixProvider matrixProvider = SkMatrix::I();
2071 std::unique_ptr<ClipStack> cs(new ClipStack(kDeviceBounds, &matrixProvider, false));
2073 auto addMaskRequiringClip = [&](SkScalar x, SkScalar y, SkScalar radius) {
2075 path.addCircle(x, y, radius);
2076 path.addCircle(x + radius / 2.f, y + radius / 2.f, radius);
2077 path.setFillType(SkPathFillType::kEvenOdd);
2079 // Use AA so that clip application does not route through the stencil buffer
2080 cs->clipPath(SkMatrix::I(), path, GrAA::kYes, SkClipOp::kIntersect);
2083 auto drawRect = [&](SkRect drawBounds) {
2085 paint.setColor4f({1.f, 1.f, 1.f, 1.f});
2086 sdc->drawRect(cs.get(), std::move(paint), GrAA::kYes, SkMatrix::I(), drawBounds);
2089 auto generateMask = [&](SkRect drawBounds) {
2090 skgpu::UniqueKey priorKey = cs->testingOnly_getLastSWMaskKey();
2091 drawRect(drawBounds);
2092 skgpu::UniqueKey newKey = cs->testingOnly_getLastSWMaskKey();
2093 REPORTER_ASSERT(r, priorKey != newKey, "Did not generate a new SW mask key as expected");
2097 auto verifyKeys = [&](const std::vector<skgpu::UniqueKey>& expectedKeys,
2098 const std::vector<skgpu::UniqueKey>& releasedKeys) {
2100 GrProxyProvider* proxyProvider = context->priv().proxyProvider();
2103 // The proxy providers key count fluctuates based on proxy lifetime, but we want to
2104 // verify the resource count, and that requires using key tags that are debug-only.
2105 SkASSERT(expectedKeys.size() > 0 || releasedKeys.size() > 0);
2106 const char* tag = expectedKeys.size() > 0 ? expectedKeys[0].tag() : releasedKeys[0].tag();
2107 GrResourceCache* cache = context->priv().getResourceCache();
2108 int numProxies = cache->countUniqueKeysWithTag(tag);
2109 REPORTER_ASSERT(r, (int) expectedKeys.size() == numProxies,
2110 "Unexpected proxy count, got %d, not %d",
2111 numProxies, (int) expectedKeys.size());
2114 for (const auto& key : expectedKeys) {
2115 auto proxy = proxyProvider->findOrCreateProxyByUniqueKey(key);
2116 REPORTER_ASSERT(r, SkToBool(proxy), "Unable to find resource for expected mask key");
2118 for (const auto& key : releasedKeys) {
2119 auto proxy = proxyProvider->findOrCreateProxyByUniqueKey(key);
2120 REPORTER_ASSERT(r, !SkToBool(proxy), "SW mask not released as expected");
2124 // Creates a mask for a complex clip
2126 addMaskRequiringClip(5.f, 5.f, 20.f);
2127 skgpu::UniqueKey keyADepth1 = generateMask({0.f, 0.f, 20.f, 20.f});
2128 skgpu::UniqueKey keyBDepth1 = generateMask({10.f, 10.f, 30.f, 30.f});
2129 verifyKeys({keyADepth1, keyBDepth1}, {});
2131 // Creates a new mask for a new save record, but doesn't delete the old records
2133 addMaskRequiringClip(6.f, 6.f, 15.f);
2134 skgpu::UniqueKey keyADepth2 = generateMask({0.f, 0.f, 20.f, 20.f});
2135 skgpu::UniqueKey keyBDepth2 = generateMask({10.f, 10.f, 30.f, 30.f});
2136 verifyKeys({keyADepth1, keyBDepth1, keyADepth2, keyBDepth2}, {});
2138 // Release after modifying the current record (even if we don't draw anything)
2139 addMaskRequiringClip(4.f, 4.f, 15.f);
2140 skgpu::UniqueKey keyCDepth2 = generateMask({4.f, 4.f, 16.f, 20.f});
2141 verifyKeys({keyADepth1, keyBDepth1, keyCDepth2}, {keyADepth2, keyBDepth2});
2143 // Release after restoring an older record
2145 verifyKeys({keyADepth1, keyBDepth1}, {keyCDepth2});
2147 // Drawing finds the old masks at depth 1 still w/o making new ones
2148 drawRect({0.f, 0.f, 20.f, 20.f});
2149 drawRect({10.f, 10.f, 30.f, 30.f});
2150 verifyKeys({keyADepth1, keyBDepth1}, {});
2152 // Drawing something contained within a previous mask also does not make a new one
2153 drawRect({5.f, 5.f, 15.f, 15.f});
2154 verifyKeys({keyADepth1, keyBDepth1}, {});
2156 // Release on destruction
2158 verifyKeys({}, {keyADepth1, keyBDepth1});