blob: 952754e33dcfaf64940aced313b474c8ebd83455 [file] [log] [blame]
Michael Ludwiga195d102020-09-15 14:51:52 -04001
2/*
3 * Copyright 2020 Google LLC
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9#include "src/gpu/GrClipStack.h"
10#include "tests/Test.h"
11
12#include "include/core/SkPath.h"
13#include "include/core/SkRRect.h"
14#include "include/core/SkRect.h"
15#include "include/core/SkRegion.h"
16#include "include/core/SkShader.h"
17#include "include/gpu/GrDirectContext.h"
18#include "src/core/SkMatrixProvider.h"
19#include "src/core/SkRRectPriv.h"
20#include "src/core/SkRectPriv.h"
Adlai Hollera0693042020-10-14 11:23:11 -040021#include "src/gpu/GrDirectContextPriv.h"
Michael Ludwiga195d102020-09-15 14:51:52 -040022#include "src/gpu/GrProxyProvider.h"
Brian Salomoneebe7352020-12-09 16:37:04 -050023#include "src/gpu/GrSurfaceDrawContext.h"
Michael Ludwiga195d102020-09-15 14:51:52 -040024
25namespace {
26
27class TestCaseBuilder;
28class ElementsBuilder;
29
30enum class SavePolicy {
31 kNever,
32 kAtStart,
33 kAtEnd,
34 kBetweenEveryOp
35};
36// TODO: We could add a RestorePolicy enum that tests different places to restore, but that would
37// make defining the test expectations and order independence more cumbersome.
38
39class TestCase {
40public:
41 // Provides fluent API to describe actual clip commands and expected clip elements:
42 // TestCase test = TestCase::Build("example", deviceBounds)
43 // .actual().rect(r, GrAA::kYes, SkClipOp::kIntersect)
44 // .localToDevice(matrix)
45 // .nonAA()
46 // .difference()
47 // .path(p1)
48 // .path(p2)
49 // .finishElements()
50 // .expectedState(kDeviceRect)
51 // .expectedBounds(r.roundOut())
52 // .expect().rect(r, GrAA::kYes, SkClipOp::kIntersect)
53 // .finishElements()
54 // .finishTest();
55 static TestCaseBuilder Build(const char* name, const SkIRect& deviceBounds);
56
57 void run(const std::vector<int>& order, SavePolicy policy, skiatest::Reporter* reporter) const;
58
59 const SkIRect& deviceBounds() const { return fDeviceBounds; }
60 GrClipStack::ClipState expectedState() const { return fExpectedState; }
61 const std::vector<GrClipStack::Element>& initialElements() const { return fElements; }
62 const std::vector<GrClipStack::Element>& expectedElements() const { return fExpectedElements; }
63
64private:
65 friend class TestCaseBuilder;
66
67 TestCase(SkString name,
68 const SkIRect& deviceBounds,
69 GrClipStack::ClipState expectedState,
70 std::vector<GrClipStack::Element> actual,
71 std::vector<GrClipStack::Element> expected)
72 : fName(name)
73 , fElements(std::move(actual))
74 , fDeviceBounds(deviceBounds)
75 , fExpectedElements(std::move(expected))
76 , fExpectedState(expectedState) {}
77
78 SkString getTestName(const std::vector<int>& order, SavePolicy policy) const;
79
80 // This may be tighter than GrClipStack::getConservativeBounds() because this always accounts
81 // for difference ops, whereas GrClipStack only sometimes can subtract the inner bounds for a
82 // difference op.
83 std::pair<SkIRect, bool> getOptimalBounds() const;
84
85 SkString fName;
86
87 // The input shapes+state to GrClipStack
88 std::vector<GrClipStack::Element> fElements;
89 SkIRect fDeviceBounds;
90
91 // The expected output of iterating over the GrClipStack after all fElements are added, although
92 // order is not important
93 std::vector<GrClipStack::Element> fExpectedElements;
94 GrClipStack::ClipState fExpectedState;
95};
96
97class ElementsBuilder {
98public:
99 // Update the default matrix, aa, and op state for elements that are added.
100 ElementsBuilder& localToDevice(const SkMatrix& m) { fLocalToDevice = m; return *this; }
101 ElementsBuilder& aa() { fAA = GrAA::kYes; return *this; }
102 ElementsBuilder& nonAA() { fAA = GrAA::kNo; return *this; }
103 ElementsBuilder& intersect() { fOp = SkClipOp::kIntersect; return *this; }
104 ElementsBuilder& difference() { fOp = SkClipOp::kDifference; return *this; }
105
106 // Add rect, rrect, or paths to the list of elements, possibly overriding the last set
107 // matrix, aa, and op state.
108 ElementsBuilder& rect(const SkRect& rect) {
109 return this->rect(rect, fLocalToDevice, fAA, fOp);
110 }
111 ElementsBuilder& rect(const SkRect& rect, GrAA aa, SkClipOp op) {
112 return this->rect(rect, fLocalToDevice, aa, op);
113 }
114 ElementsBuilder& rect(const SkRect& rect, const SkMatrix& m, GrAA aa, SkClipOp op) {
115 fElements->push_back({GrShape(rect), m, op, aa});
116 return *this;
117 }
118
119 ElementsBuilder& rrect(const SkRRect& rrect) {
120 return this->rrect(rrect, fLocalToDevice, fAA, fOp);
121 }
122 ElementsBuilder& rrect(const SkRRect& rrect, GrAA aa, SkClipOp op) {
123 return this->rrect(rrect, fLocalToDevice, aa, op);
124 }
125 ElementsBuilder& rrect(const SkRRect& rrect, const SkMatrix& m, GrAA aa, SkClipOp op) {
126 fElements->push_back({GrShape(rrect), m, op, aa});
127 return *this;
128 }
129
130 ElementsBuilder& path(const SkPath& path) {
131 return this->path(path, fLocalToDevice, fAA, fOp);
132 }
133 ElementsBuilder& path(const SkPath& path, GrAA aa, SkClipOp op) {
134 return this->path(path, fLocalToDevice, aa, op);
135 }
136 ElementsBuilder& path(const SkPath& path, const SkMatrix& m, GrAA aa, SkClipOp op) {
137 fElements->push_back({GrShape(path), m, op, aa});
138 return *this;
139 }
140
141 // Finish and return the original test case builder
142 TestCaseBuilder& finishElements() {
143 return *fBuilder;
144 }
145
146private:
147 friend class TestCaseBuilder;
148
149 ElementsBuilder(TestCaseBuilder* builder, std::vector<GrClipStack::Element>* elements)
150 : fBuilder(builder)
151 , fElements(elements) {}
152
153 SkMatrix fLocalToDevice = SkMatrix::I();
154 GrAA fAA = GrAA::kNo;
155 SkClipOp fOp = SkClipOp::kIntersect;
156
157 TestCaseBuilder* fBuilder;
158 std::vector<GrClipStack::Element>* fElements;
159};
160
161class TestCaseBuilder {
162public:
163 ElementsBuilder actual() { return ElementsBuilder(this, &fActualElements); }
164 ElementsBuilder expect() { return ElementsBuilder(this, &fExpectedElements); }
165
166 TestCaseBuilder& expectActual() {
167 fExpectedElements = fActualElements;
168 return *this;
169 }
170
171 TestCaseBuilder& state(GrClipStack::ClipState state) {
172 fExpectedState = state;
173 return *this;
174 }
175
176 TestCase finishTest() {
177 TestCase test(fName, fDeviceBounds, fExpectedState,
178 std::move(fActualElements), std::move(fExpectedElements));
179
180 fExpectedState = GrClipStack::ClipState::kWideOpen;
181 return test;
182 }
183
184private:
185 friend class TestCase;
186
187 explicit TestCaseBuilder(const char* name, const SkIRect& deviceBounds)
188 : fName(name)
189 , fDeviceBounds(deviceBounds)
190 , fExpectedState(GrClipStack::ClipState::kWideOpen) {}
191
192 SkString fName;
193 SkIRect fDeviceBounds;
194 GrClipStack::ClipState fExpectedState;
195
196 std::vector<GrClipStack::Element> fActualElements;
197 std::vector<GrClipStack::Element> fExpectedElements;
198};
199
200TestCaseBuilder TestCase::Build(const char* name, const SkIRect& deviceBounds) {
201 return TestCaseBuilder(name, deviceBounds);
202}
203
204SkString TestCase::getTestName(const std::vector<int>& order, SavePolicy policy) const {
205 SkString name = fName;
206
207 SkString policyName;
208 switch(policy) {
209 case SavePolicy::kNever:
210 policyName = "never";
211 break;
212 case SavePolicy::kAtStart:
213 policyName = "start";
214 break;
215 case SavePolicy::kAtEnd:
216 policyName = "end";
217 break;
218 case SavePolicy::kBetweenEveryOp:
219 policyName = "between";
220 break;
221 }
222
223 name.appendf("(save %s, order [", policyName.c_str());
224 for (size_t i = 0; i < order.size(); ++i) {
225 if (i > 0) {
226 name.append(",");
227 }
228 name.appendf("%d", order[i]);
229 }
230 name.append("])");
231 return name;
232}
233
234std::pair<SkIRect, bool> TestCase::getOptimalBounds() const {
235 if (fExpectedState == GrClipStack::ClipState::kEmpty) {
236 return {SkIRect::MakeEmpty(), true};
237 }
238
239 bool expectOptimal = true;
240 SkRegion region(fDeviceBounds);
241 for (const GrClipStack::Element& e : fExpectedElements) {
242 bool intersect = (e.fOp == SkClipOp::kIntersect && !e.fShape.inverted()) ||
243 (e.fOp == SkClipOp::kDifference && e.fShape.inverted());
244
245 SkIRect elementBounds;
246 SkRegion::Op op;
247 if (intersect) {
248 op = SkRegion::kIntersect_Op;
249 expectOptimal &= e.fLocalToDevice.isIdentity();
250 elementBounds = GrClip::GetPixelIBounds(e.fLocalToDevice.mapRect(e.fShape.bounds()),
251 e.fAA, GrClip::BoundsType::kExterior);
252 } else {
253 op = SkRegion::kDifference_Op;
254 expectOptimal = false;
255 if (e.fShape.isRect() && e.fLocalToDevice.isIdentity()) {
256 elementBounds = GrClip::GetPixelIBounds(e.fShape.rect(), e.fAA,
257 GrClip::BoundsType::kInterior);
258 } else if (e.fShape.isRRect() && e.fLocalToDevice.isIdentity()) {
259 elementBounds = GrClip::GetPixelIBounds(SkRRectPriv::InnerBounds(e.fShape.rrect()),
260 e.fAA, GrClip::BoundsType::kInterior);
261 } else {
262 elementBounds = SkIRect::MakeEmpty();
263 }
264 }
265
266 region.op(SkRegion(elementBounds), op);
267 }
268 return {region.getBounds(), expectOptimal};
269}
270
271static bool compare_elements(const GrClipStack::Element& a, const GrClipStack::Element& b) {
272 if (a.fAA != b.fAA || a.fOp != b.fOp || a.fLocalToDevice != b.fLocalToDevice ||
273 a.fShape.type() != b.fShape.type()) {
274 return false;
275 }
276 switch(a.fShape.type()) {
277 case GrShape::Type::kRect:
278 return a.fShape.rect() == b.fShape.rect();
279 case GrShape::Type::kRRect:
280 return a.fShape.rrect() == b.fShape.rrect();
281 case GrShape::Type::kPath:
282 // A path's points are never transformed, the only modification is fill type which does
283 // not change the generation ID. For convex polygons, we check == so that more complex
284 // test cases can be evaluated.
285 return a.fShape.path().getGenerationID() == b.fShape.path().getGenerationID() ||
286 (a.fShape.convex() &&
287 a.fShape.segmentMask() == SkPathSegmentMask::kLine_SkPathSegmentMask &&
288 a.fShape.path() == b.fShape.path());
289 default:
290 SkDEBUGFAIL("Shape type not handled by test case yet.");
291 return false;
292 }
293}
294
295void TestCase::run(const std::vector<int>& order, SavePolicy policy,
296 skiatest::Reporter* reporter) const {
297 SkASSERT(fElements.size() == order.size());
298
299 SkSimpleMatrixProvider matrixProvider(SkMatrix::I());
300 GrClipStack cs(fDeviceBounds, &matrixProvider, false);
301
302 if (policy == SavePolicy::kAtStart) {
303 cs.save();
304 }
305
306 for (int i : order) {
307 if (policy == SavePolicy::kBetweenEveryOp) {
308 cs.save();
309 }
310 const GrClipStack::Element& e = fElements[i];
311 switch(e.fShape.type()) {
312 case GrShape::Type::kRect:
313 cs.clipRect(e.fLocalToDevice, e.fShape.rect(), e.fAA, e.fOp);
314 break;
315 case GrShape::Type::kRRect:
316 cs.clipRRect(e.fLocalToDevice, e.fShape.rrect(), e.fAA, e.fOp);
317 break;
318 case GrShape::Type::kPath:
319 cs.clipPath(e.fLocalToDevice, e.fShape.path(), e.fAA, e.fOp);
320 break;
321 default:
322 SkDEBUGFAIL("Shape type not handled by test case yet.");
323 }
324 }
325
326 if (policy == SavePolicy::kAtEnd) {
327 cs.save();
328 }
329
330 // Now validate
331 SkString name = this->getTestName(order, policy);
332 REPORTER_ASSERT(reporter, cs.clipState() == fExpectedState,
333 "%s, clip state expected %d, actual %d",
334 name.c_str(), (int) fExpectedState, (int) cs.clipState());
335 SkIRect actualBounds = cs.getConservativeBounds();
336 SkIRect optimalBounds;
337 bool expectOptimal;
338 std::tie(optimalBounds, expectOptimal) = this->getOptimalBounds();
339
340 if (expectOptimal) {
341 REPORTER_ASSERT(reporter, actualBounds == optimalBounds,
342 "%s, bounds expected [%d %d %d %d], actual [%d %d %d %d]",
343 name.c_str(), optimalBounds.fLeft, optimalBounds.fTop,
344 optimalBounds.fRight, optimalBounds.fBottom,
345 actualBounds.fLeft, actualBounds.fTop,
346 actualBounds.fRight, actualBounds.fBottom);
347 } else {
348 REPORTER_ASSERT(reporter, actualBounds.contains(optimalBounds),
349 "%s, bounds are not conservative, optimal [%d %d %d %d], actual [%d %d %d %d]",
350 name.c_str(), optimalBounds.fLeft, optimalBounds.fTop,
351 optimalBounds.fRight, optimalBounds.fBottom,
352 actualBounds.fLeft, actualBounds.fTop,
353 actualBounds.fRight, actualBounds.fBottom);
354 }
355
356 size_t matchedElements = 0;
357 for (const GrClipStack::Element& a : cs) {
358 bool found = false;
359 for (const GrClipStack::Element& e : fExpectedElements) {
360 if (compare_elements(a, e)) {
361 // shouldn't match multiple expected elements or it's a bad test case
362 SkASSERT(!found);
363 found = true;
364 }
365 }
366
367 REPORTER_ASSERT(reporter, found,
368 "%s, unexpected clip element in stack: shape %d, aa %d, op %d",
369 name.c_str(), (int) a.fShape.type(), (int) a.fAA, (int) a.fOp);
370 matchedElements += found ? 1 : 0;
371 }
372 REPORTER_ASSERT(reporter, matchedElements == fExpectedElements.size(),
373 "%s, did not match all expected elements: expected %d but matched only %d",
374 name.c_str(), fExpectedElements.size(), matchedElements);
375
376 // Validate restoration behavior
377 if (policy == SavePolicy::kAtEnd) {
378 GrClipStack::ClipState oldState = cs.clipState();
379 cs.restore();
380 REPORTER_ASSERT(reporter, cs.clipState() == oldState,
381 "%s, restoring an empty save record should not change clip state: "
382 "expected %d but got %d", (int) oldState, (int) cs.clipState());
383 } else if (policy != SavePolicy::kNever) {
384 int restoreCount = policy == SavePolicy::kAtStart ? 1 : (int) order.size();
385 for (int i = 0; i < restoreCount; ++i) {
386 cs.restore();
387 }
388 // Should be wide open if everything is restored to base state
389 REPORTER_ASSERT(reporter, cs.clipState() == GrClipStack::ClipState::kWideOpen,
390 "%s, restore should make stack become wide-open, not %d",
391 (int) cs.clipState());
392 }
393}
394
395// All clip operations are commutative so applying actual elements in every possible order should
396// always produce the same set of expected elements.
397static void run_test_case(skiatest::Reporter* r, const TestCase& test) {
398 int n = (int) test.initialElements().size();
399 std::vector<int> order(n);
400 std::vector<int> stack(n);
401
402 // Initial order sequence and zeroed stack
403 for (int i = 0; i < n; ++i) {
404 order[i] = i;
405 stack[i] = 0;
406 }
407
408 auto runTest = [&]() {
409 static const SavePolicy kPolicies[] = { SavePolicy::kNever, SavePolicy::kAtStart,
410 SavePolicy::kAtEnd, SavePolicy::kBetweenEveryOp };
411 for (auto policy : kPolicies) {
412 test.run(order, policy, r);
413 }
414 };
415
416 // Heap's algorithm (non-recursive) to generate every permutation over the test case's elements
417 // https://en.wikipedia.org/wiki/Heap%27s_algorithm
418 runTest();
419
420 static constexpr int kMaxRuns = 720; // Don't run more than 6! configurations, even if n > 6
421 int testRuns = 1;
422
423 int i = 0;
424 while (i < n && testRuns < kMaxRuns) {
425 if (stack[i] < i) {
426 using std::swap;
427 if (i % 2 == 0) {
428 swap(order[0], order[i]);
429 } else {
430 swap(order[stack[i]], order[i]);
431 }
432
433 runTest();
434 stack[i]++;
435 i = 0;
436 testRuns++;
437 } else {
438 stack[i] = 0;
439 ++i;
440 }
441 }
442}
443
444static SkPath make_octagon(const SkRect& r, SkScalar lr, SkScalar tb) {
445 SkPath p;
446 p.moveTo(r.fLeft + lr, r.fTop);
447 p.lineTo(r.fRight - lr, r.fTop);
448 p.lineTo(r.fRight, r.fTop + tb);
449 p.lineTo(r.fRight, r.fBottom - tb);
450 p.lineTo(r.fRight - lr, r.fBottom);
451 p.lineTo(r.fLeft + lr, r.fBottom);
452 p.lineTo(r.fLeft, r.fBottom - tb);
453 p.lineTo(r.fLeft, r.fTop + tb);
454 p.close();
455 return p;
456}
457
458static SkPath make_octagon(const SkRect& r) {
459 SkScalar lr = 0.3f * r.width();
460 SkScalar tb = 0.3f * r.height();
461 return make_octagon(r, lr, tb);
462}
463
464static constexpr SkIRect kDeviceBounds = {0, 0, 100, 100};
465
466} // anonymous namespace
467
468///////////////////////////////////////////////////////////////////////////////
469// These tests use the TestCase infrastructure to define clip stacks and
470// associated expectations.
471
472// Tests that the initialized state of the clip stack is wide-open
473DEF_TEST(GrClipStack_InitialState, r) {
474 run_test_case(r, TestCase::Build("initial-state", SkIRect::MakeWH(100, 100)).finishTest());
475}
476
477// Tests that intersection of rects combine to a single element when they have the same AA type,
478// or are pixel-aligned.
479DEF_TEST(GrClipStack_RectRectAACombine, r) {
480 SkRect pixelAligned = {0, 0, 10, 10};
481 SkRect fracRect1 = pixelAligned.makeOffset(5.3f, 3.7f);
482 SkRect fracRect2 = {fracRect1.fLeft + 0.75f * fracRect1.width(),
483 fracRect1.fTop + 0.75f * fracRect1.height(),
484 fracRect1.fRight, fracRect1.fBottom};
485
486 SkRect fracIntersect;
487 SkAssertResult(fracIntersect.intersect(fracRect1, fracRect2));
488 SkRect alignedIntersect;
489 SkAssertResult(alignedIntersect.intersect(pixelAligned, fracRect1));
490
491 // Both AA combine to one element
492 run_test_case(r, TestCase::Build("aa", kDeviceBounds)
493 .actual().aa().intersect()
494 .rect(fracRect1).rect(fracRect2)
495 .finishElements()
496 .expect().aa().intersect().rect(fracIntersect).finishElements()
497 .state(GrClipStack::ClipState::kDeviceRect)
498 .finishTest());
499
500 // Both non-AA combine to one element
501 run_test_case(r, TestCase::Build("nonaa", kDeviceBounds)
502 .actual().nonAA().intersect()
503 .rect(fracRect1).rect(fracRect2)
504 .finishElements()
505 .expect().nonAA().intersect().rect(fracIntersect).finishElements()
506 .state(GrClipStack::ClipState::kDeviceRect)
507 .finishTest());
508
509 // Pixel-aligned AA and non-AA combine
510 run_test_case(r, TestCase::Build("aligned-aa+nonaa", kDeviceBounds)
511 .actual().intersect()
512 .aa().rect(pixelAligned).nonAA().rect(fracRect1)
513 .finishElements()
514 .expect().nonAA().intersect().rect(alignedIntersect).finishElements()
515 .state(GrClipStack::ClipState::kDeviceRect)
516 .finishTest());
517
518 // AA and pixel-aligned non-AA combine
519 run_test_case(r, TestCase::Build("aa+aligned-nonaa", kDeviceBounds)
520 .actual().intersect()
521 .aa().rect(fracRect1).nonAA().rect(pixelAligned)
522 .finishElements()
523 .expect().aa().intersect().rect(alignedIntersect).finishElements()
524 .state(GrClipStack::ClipState::kDeviceRect)
525 .finishTest());
526
527 // Other mixed AA modes do not combine
528 run_test_case(r, TestCase::Build("aa+nonaa", kDeviceBounds)
529 .actual().intersect()
530 .aa().rect(fracRect1).nonAA().rect(fracRect2)
531 .finishElements()
532 .expectActual()
533 .state(GrClipStack::ClipState::kComplex)
534 .finishTest());
535}
536
537// Tests that an intersection and a difference op do not combine, even if they would have if both
538// were intersection ops.
539DEF_TEST(GrClipStack_DifferenceNoCombine, r) {
540 SkRect r1 = {15.f, 14.f, 23.22f, 58.2f};
541 SkRect r2 = r1.makeOffset(5.f, 8.f);
542 SkASSERT(r1.intersects(r2));
543
544 run_test_case(r, TestCase::Build("no-combine", kDeviceBounds)
545 .actual().aa().intersect().rect(r1)
546 .difference().rect(r2)
547 .finishElements()
548 .expectActual()
549 .state(GrClipStack::ClipState::kComplex)
550 .finishTest());
551}
552
553// Tests that intersection of rects in the same coordinate space can still be combined, but do not
554// when the spaces differ.
555DEF_TEST(GrClipStack_RectRectNonAxisAligned, r) {
556 SkRect pixelAligned = {0, 0, 10, 10};
557 SkRect fracRect1 = pixelAligned.makeOffset(5.3f, 3.7f);
558 SkRect fracRect2 = {fracRect1.fLeft + 0.75f * fracRect1.width(),
559 fracRect1.fTop + 0.75f * fracRect1.height(),
560 fracRect1.fRight, fracRect1.fBottom};
561
562 SkRect fracIntersect;
563 SkAssertResult(fracIntersect.intersect(fracRect1, fracRect2));
564
565 SkMatrix lm = SkMatrix::RotateDeg(45.f);
566
567 // Both AA combine
568 run_test_case(r, TestCase::Build("aa", kDeviceBounds)
569 .actual().aa().intersect().localToDevice(lm)
570 .rect(fracRect1).rect(fracRect2)
571 .finishElements()
572 .expect().aa().intersect().localToDevice(lm)
573 .rect(fracIntersect).finishElements()
574 .state(GrClipStack::ClipState::kComplex)
575 .finishTest());
576
577 // Both non-AA combine
578 run_test_case(r, TestCase::Build("nonaa", kDeviceBounds)
579 .actual().nonAA().intersect().localToDevice(lm)
580 .rect(fracRect1).rect(fracRect2)
581 .finishElements()
582 .expect().nonAA().intersect().localToDevice(lm)
583 .rect(fracIntersect).finishElements()
584 .state(GrClipStack::ClipState::kComplex)
585 .finishTest());
586
587 // Integer-aligned coordinates under a local matrix with mixed AA don't combine, though
588 run_test_case(r, TestCase::Build("local-aa", kDeviceBounds)
589 .actual().intersect().localToDevice(lm)
590 .aa().rect(pixelAligned).nonAA().rect(fracRect1)
591 .finishElements()
592 .expectActual()
593 .state(GrClipStack::ClipState::kComplex)
594 .finishTest());
595}
596
597// Tests that intersection of two round rects can simplify to a single round rect when they have
598// the same AA type.
599DEF_TEST(GrClipStack_RRectRRectAACombine, r) {
600 SkRRect r1 = SkRRect::MakeRectXY(SkRect::MakeWH(12, 12), 2.f, 2.f);
601 SkRRect r2 = r1.makeOffset(6.f, 6.f);
602
603 SkRRect intersect = SkRRectPriv::ConservativeIntersect(r1, r2);
604 SkASSERT(!intersect.isEmpty());
605
606 // Both AA combine
607 run_test_case(r, TestCase::Build("aa", kDeviceBounds)
608 .actual().aa().intersect()
609 .rrect(r1).rrect(r2)
610 .finishElements()
611 .expect().aa().intersect().rrect(intersect).finishElements()
612 .state(GrClipStack::ClipState::kDeviceRRect)
613 .finishTest());
614
615 // Both non-AA combine
616 run_test_case(r, TestCase::Build("nonaa", kDeviceBounds)
617 .actual().nonAA().intersect()
618 .rrect(r1).rrect(r2)
619 .finishElements()
620 .expect().nonAA().intersect().rrect(intersect).finishElements()
621 .state(GrClipStack::ClipState::kDeviceRRect)
622 .finishTest());
623
624 // Mixed do not combine
625 run_test_case(r, TestCase::Build("aa+nonaa", kDeviceBounds)
626 .actual().intersect()
627 .aa().rrect(r1).nonAA().rrect(r2)
628 .finishElements()
629 .expectActual()
630 .state(GrClipStack::ClipState::kComplex)
631 .finishTest());
632
633 // Same AA state can combine in the same local coordinate space
634 SkMatrix lm = SkMatrix::RotateDeg(45.f);
635 run_test_case(r, TestCase::Build("local-aa", kDeviceBounds)
636 .actual().aa().intersect().localToDevice(lm)
637 .rrect(r1).rrect(r2)
638 .finishElements()
639 .expect().aa().intersect().localToDevice(lm)
640 .rrect(intersect).finishElements()
641 .state(GrClipStack::ClipState::kComplex)
642 .finishTest());
643 run_test_case(r, TestCase::Build("local-nonaa", kDeviceBounds)
644 .actual().nonAA().intersect().localToDevice(lm)
645 .rrect(r1).rrect(r2)
646 .finishElements()
647 .expect().nonAA().intersect().localToDevice(lm)
648 .rrect(intersect).finishElements()
649 .state(GrClipStack::ClipState::kComplex)
650 .finishTest());
651}
652
653// Tests that intersection of a round rect and rect can simplify to a new round rect or even a rect.
654DEF_TEST(GrClipStack_RectRRectCombine, r) {
655 SkRRect rrect = SkRRect::MakeRectXY({0, 0, 10, 10}, 2.f, 2.f);
656 SkRect cutTop = {-10, -10, 10, 4};
657 SkRect cutMid = {-10, 3, 10, 7};
658
659 // Rect + RRect becomes a round rect with some square corners
660 SkVector cutCorners[4] = {{2.f, 2.f}, {2.f, 2.f}, {0, 0}, {0, 0}};
661 SkRRect cutRRect;
662 cutRRect.setRectRadii({0, 0, 10, 4}, cutCorners);
663 run_test_case(r, TestCase::Build("still-rrect", kDeviceBounds)
664 .actual().intersect().aa().rrect(rrect).rect(cutTop).finishElements()
665 .expect().intersect().aa().rrect(cutRRect).finishElements()
666 .state(GrClipStack::ClipState::kDeviceRRect)
667 .finishTest());
668
669 // Rect + RRect becomes a rect
670 SkRect cutRect = {0, 3, 10, 7};
671 run_test_case(r, TestCase::Build("to-rect", kDeviceBounds)
672 .actual().intersect().aa().rrect(rrect).rect(cutMid).finishElements()
673 .expect().intersect().aa().rect(cutRect).finishElements()
674 .state(GrClipStack::ClipState::kDeviceRect)
675 .finishTest());
676
677 // But they can only combine when the intersecting shape is representable as a [r]rect.
678 cutRect = {0, 0, 1.5f, 5.f};
679 run_test_case(r, TestCase::Build("no-combine", kDeviceBounds)
680 .actual().intersect().aa().rrect(rrect).rect(cutRect).finishElements()
681 .expectActual()
682 .state(GrClipStack::ClipState::kComplex)
683 .finishTest());
684}
685
686// Tests that a rect shape is actually pre-clipped to the device bounds
687DEF_TEST(GrClipStack_RectDeviceClip, r) {
688 SkRect crossesDeviceEdge = {20.f, kDeviceBounds.fTop - 13.2f,
689 kDeviceBounds.fRight + 15.5f, 30.f};
690 SkRect insideDevice = {20.f, kDeviceBounds.fTop, kDeviceBounds.fRight, 30.f};
691
692 run_test_case(r, TestCase::Build("device-aa-rect", kDeviceBounds)
693 .actual().intersect().aa().rect(crossesDeviceEdge).finishElements()
694 .expect().intersect().aa().rect(insideDevice).finishElements()
695 .state(GrClipStack::ClipState::kDeviceRect)
696 .finishTest());
697
698 run_test_case(r, TestCase::Build("device-nonaa-rect", kDeviceBounds)
699 .actual().intersect().nonAA().rect(crossesDeviceEdge).finishElements()
700 .expect().intersect().nonAA().rect(insideDevice).finishElements()
701 .state(GrClipStack::ClipState::kDeviceRect)
702 .finishTest());
703}
704
705// Tests that other shapes' bounds are contained by the device bounds, even if their shape is not.
706DEF_TEST(GrClipStack_ShapeDeviceBoundsClip, r) {
707 SkRect crossesDeviceEdge = {20.f, kDeviceBounds.fTop - 13.2f,
708 kDeviceBounds.fRight + 15.5f, 30.f};
709
710 // RRect
711 run_test_case(r, TestCase::Build("device-rrect", kDeviceBounds)
712 .actual().intersect().aa()
713 .rrect(SkRRect::MakeRectXY(crossesDeviceEdge, 4.f, 4.f))
714 .finishElements()
715 .expectActual()
716 .state(GrClipStack::ClipState::kDeviceRRect)
717 .finishTest());
718
719 // Path
720 run_test_case(r, TestCase::Build("device-path", kDeviceBounds)
721 .actual().intersect().aa()
722 .path(make_octagon(crossesDeviceEdge))
723 .finishElements()
724 .expectActual()
725 .state(GrClipStack::ClipState::kComplex)
726 .finishTest());
727}
728
729// Tests that a simplifiable path turns into a simpler element type
730DEF_TEST(GrClipStack_PathSimplify, r) {
731 // Empty, point, and line paths -> empty
732 SkPath empty;
733 run_test_case(r, TestCase::Build("empty", kDeviceBounds)
734 .actual().path(empty).finishElements()
735 .state(GrClipStack::ClipState::kEmpty)
736 .finishTest());
737 SkPath point;
738 point.moveTo({0.f, 0.f});
739 run_test_case(r, TestCase::Build("point", kDeviceBounds)
740 .actual().path(point).finishElements()
741 .state(GrClipStack::ClipState::kEmpty)
742 .finishTest());
743
744 SkPath line;
745 line.moveTo({0.f, 0.f});
746 line.lineTo({10.f, 5.f});
747 run_test_case(r, TestCase::Build("line", kDeviceBounds)
748 .actual().path(line).finishElements()
749 .state(GrClipStack::ClipState::kEmpty)
750 .finishTest());
751
752 // Rect path -> rect element
753 SkRect rect = {0.f, 2.f, 10.f, 15.4f};
754 SkPath rectPath;
755 rectPath.addRect(rect);
756 run_test_case(r, TestCase::Build("rect", kDeviceBounds)
757 .actual().path(rectPath).finishElements()
758 .expect().rect(rect).finishElements()
759 .state(GrClipStack::ClipState::kDeviceRect)
760 .finishTest());
761
762 // Oval path -> rrect element
763 SkPath ovalPath;
764 ovalPath.addOval(rect);
765 run_test_case(r, TestCase::Build("oval", kDeviceBounds)
766 .actual().path(ovalPath).finishElements()
767 .expect().rrect(SkRRect::MakeOval(rect)).finishElements()
768 .state(GrClipStack::ClipState::kDeviceRRect)
769 .finishTest());
770
771 // RRect path -> rrect element
772 SkRRect rrect = SkRRect::MakeRectXY(rect, 2.f, 2.f);
773 SkPath rrectPath;
774 rrectPath.addRRect(rrect);
775 run_test_case(r, TestCase::Build("rrect", kDeviceBounds)
776 .actual().path(rrectPath).finishElements()
777 .expect().rrect(rrect).finishElements()
778 .state(GrClipStack::ClipState::kDeviceRRect)
779 .finishTest());
780}
781
782// Tests that repeated identical clip operations are idempotent
783DEF_TEST(GrClipStack_RepeatElement, r) {
784 // Same rect
785 SkRect rect = {5.3f, 62.f, 20.f, 85.f};
786 run_test_case(r, TestCase::Build("same-rects", kDeviceBounds)
787 .actual().rect(rect).rect(rect).rect(rect).finishElements()
788 .expect().rect(rect).finishElements()
789 .state(GrClipStack::ClipState::kDeviceRect)
790 .finishTest());
791 SkMatrix lm;
792 lm.setRotate(30.f, rect.centerX(), rect.centerY());
793 run_test_case(r, TestCase::Build("same-local-rects", kDeviceBounds)
794 .actual().localToDevice(lm).rect(rect).rect(rect).rect(rect)
795 .finishElements()
796 .expect().localToDevice(lm).rect(rect).finishElements()
797 .state(GrClipStack::ClipState::kComplex)
798 .finishTest());
799
800 // Same rrect
801 SkRRect rrect = SkRRect::MakeRectXY(rect, 5.f, 2.5f);
802 run_test_case(r, TestCase::Build("same-rrects", kDeviceBounds)
803 .actual().rrect(rrect).rrect(rrect).rrect(rrect).finishElements()
804 .expect().rrect(rrect).finishElements()
805 .state(GrClipStack::ClipState::kDeviceRRect)
806 .finishTest());
807 run_test_case(r, TestCase::Build("same-local-rrects", kDeviceBounds)
808 .actual().localToDevice(lm).rrect(rrect).rrect(rrect).rrect(rrect)
809 .finishElements()
810 .expect().localToDevice(lm).rrect(rrect).finishElements()
811 .state(GrClipStack::ClipState::kComplex)
812 .finishTest());
813
814 // Same convex path, by ==
815 run_test_case(r, TestCase::Build("same-convex", kDeviceBounds)
816 .actual().path(make_octagon(rect)).path(make_octagon(rect))
817 .finishElements()
818 .expect().path(make_octagon(rect)).finishElements()
819 .state(GrClipStack::ClipState::kComplex)
820 .finishTest());
821 run_test_case(r, TestCase::Build("same-local-convex", kDeviceBounds)
822 .actual().localToDevice(lm)
823 .path(make_octagon(rect)).path(make_octagon(rect))
824 .finishElements()
825 .expect().localToDevice(lm).path(make_octagon(rect))
826 .finishElements()
827 .state(GrClipStack::ClipState::kComplex)
828 .finishTest());
829
830 // Same complicated path by gen-id but not ==
831 SkPath path; // an hour glass
832 path.moveTo({0.f, 0.f});
833 path.lineTo({20.f, 20.f});
834 path.lineTo({0.f, 20.f});
835 path.lineTo({20.f, 0.f});
836 path.close();
837
838 run_test_case(r, TestCase::Build("same-path", kDeviceBounds)
839 .actual().path(path).path(path).path(path).finishElements()
840 .expect().path(path).finishElements()
841 .state(GrClipStack::ClipState::kComplex)
842 .finishTest());
843 run_test_case(r, TestCase::Build("same-local-path", kDeviceBounds)
844 .actual().localToDevice(lm)
845 .path(path).path(path).path(path).finishElements()
846 .expect().localToDevice(lm).path(path)
847 .finishElements()
848 .state(GrClipStack::ClipState::kComplex)
849 .finishTest());
850}
851
852// Tests that inverse-filled paths are canonicalized to a regular fill and a swapped clip op
853DEF_TEST(GrClipStack_InverseFilledPath, r) {
854 SkRect rect = {0.f, 0.f, 16.f, 17.f};
855 SkPath rectPath;
856 rectPath.addRect(rect);
857
858 SkPath inverseRectPath = rectPath;
859 inverseRectPath.toggleInverseFillType();
860
861 SkPath complexPath = make_octagon(rect);
862 SkPath inverseComplexPath = complexPath;
863 inverseComplexPath.toggleInverseFillType();
864
865 // Inverse filled rect + intersect -> diff rect
866 run_test_case(r, TestCase::Build("inverse-rect-intersect", kDeviceBounds)
867 .actual().aa().intersect().path(inverseRectPath).finishElements()
868 .expect().aa().difference().rect(rect).finishElements()
869 .state(GrClipStack::ClipState::kComplex)
870 .finishTest());
871
872 // Inverse filled rect + difference -> int. rect
873 run_test_case(r, TestCase::Build("inverse-rect-difference", kDeviceBounds)
874 .actual().aa().difference().path(inverseRectPath).finishElements()
875 .expect().aa().intersect().rect(rect).finishElements()
876 .state(GrClipStack::ClipState::kDeviceRect)
877 .finishTest());
878
879 // Inverse filled path + intersect -> diff path
880 run_test_case(r, TestCase::Build("inverse-path-intersect", kDeviceBounds)
881 .actual().aa().intersect().path(inverseComplexPath).finishElements()
882 .expect().aa().difference().path(complexPath).finishElements()
883 .state(GrClipStack::ClipState::kComplex)
884 .finishTest());
885
886 // Inverse filled path + difference -> int. path
887 run_test_case(r, TestCase::Build("inverse-path-difference", kDeviceBounds)
888 .actual().aa().difference().path(inverseComplexPath).finishElements()
889 .expect().aa().intersect().path(complexPath).finishElements()
890 .state(GrClipStack::ClipState::kComplex)
891 .finishTest());
892}
893
894// Tests that clip operations that are offscreen either make the clip empty or stay wide open
895DEF_TEST(GrClipStack_Offscreen, r) {
896 SkRect offscreenRect = {kDeviceBounds.fRight + 10.f, kDeviceBounds.fTop + 20.f,
897 kDeviceBounds.fRight + 40.f, kDeviceBounds.fTop + 60.f};
898 SkASSERT(!offscreenRect.intersects(SkRect::Make(kDeviceBounds)));
899
900 SkRRect offscreenRRect = SkRRect::MakeRectXY(offscreenRect, 5.f, 5.f);
901 SkPath offscreenPath = make_octagon(offscreenRect);
902
903 // Intersect -> empty
904 run_test_case(r, TestCase::Build("intersect-combo", kDeviceBounds)
905 .actual().aa().intersect()
906 .rect(offscreenRect)
907 .rrect(offscreenRRect)
908 .path(offscreenPath)
909 .finishElements()
910 .state(GrClipStack::ClipState::kEmpty)
911 .finishTest());
912 run_test_case(r, TestCase::Build("intersect-rect", kDeviceBounds)
913 .actual().aa().intersect()
914 .rect(offscreenRect)
915 .finishElements()
916 .state(GrClipStack::ClipState::kEmpty)
917 .finishTest());
918 run_test_case(r, TestCase::Build("intersect-rrect", kDeviceBounds)
919 .actual().aa().intersect()
920 .rrect(offscreenRRect)
921 .finishElements()
922 .state(GrClipStack::ClipState::kEmpty)
923 .finishTest());
924 run_test_case(r, TestCase::Build("intersect-path", kDeviceBounds)
925 .actual().aa().intersect()
926 .path(offscreenPath)
927 .finishElements()
928 .state(GrClipStack::ClipState::kEmpty)
929 .finishTest());
930
931 // Difference -> wide open
932 run_test_case(r, TestCase::Build("difference-combo", kDeviceBounds)
933 .actual().aa().difference()
934 .rect(offscreenRect)
935 .rrect(offscreenRRect)
936 .path(offscreenPath)
937 .finishElements()
938 .state(GrClipStack::ClipState::kWideOpen)
939 .finishTest());
940 run_test_case(r, TestCase::Build("difference-rect", kDeviceBounds)
941 .actual().aa().difference()
942 .rect(offscreenRect)
943 .finishElements()
944 .state(GrClipStack::ClipState::kWideOpen)
945 .finishTest());
946 run_test_case(r, TestCase::Build("difference-rrect", kDeviceBounds)
947 .actual().aa().difference()
948 .rrect(offscreenRRect)
949 .finishElements()
950 .state(GrClipStack::ClipState::kWideOpen)
951 .finishTest());
952 run_test_case(r, TestCase::Build("difference-path", kDeviceBounds)
953 .actual().aa().difference()
954 .path(offscreenPath)
955 .finishElements()
956 .state(GrClipStack::ClipState::kWideOpen)
957 .finishTest());
958}
959
960// Tests that an empty shape updates the clip state directly without needing an element
961DEF_TEST(GrClipStack_EmptyShape, r) {
962 // Intersect -> empty
963 run_test_case(r, TestCase::Build("empty-intersect", kDeviceBounds)
964 .actual().intersect().rect(SkRect::MakeEmpty()).finishElements()
965 .state(GrClipStack::ClipState::kEmpty)
966 .finishTest());
967
968 // Difference -> no-op
969 run_test_case(r, TestCase::Build("empty-difference", kDeviceBounds)
970 .actual().difference().rect(SkRect::MakeEmpty()).finishElements()
971 .state(GrClipStack::ClipState::kWideOpen)
972 .finishTest());
973
974 SkRRect rrect = SkRRect::MakeRectXY({4.f, 10.f, 16.f, 32.f}, 2.f, 2.f);
975 run_test_case(r, TestCase::Build("noop-difference", kDeviceBounds)
976 .actual().difference().rrect(rrect).rect(SkRect::MakeEmpty())
977 .finishElements()
978 .expect().difference().rrect(rrect).finishElements()
979 .state(GrClipStack::ClipState::kComplex)
980 .finishTest());
981}
982
983// Tests that sufficiently large difference operations can shrink the conservative bounds
984DEF_TEST(GrClipStack_DifferenceBounds, r) {
985 SkRect rightSide = {50.f, -10.f, 2.f * kDeviceBounds.fRight, kDeviceBounds.fBottom + 10.f};
986 SkRect clipped = rightSide;
987 SkAssertResult(clipped.intersect(SkRect::Make(kDeviceBounds)));
988
989 run_test_case(r, TestCase::Build("difference-cut", kDeviceBounds)
990 .actual().nonAA().difference().rect(rightSide).finishElements()
991 .expect().nonAA().difference().rect(clipped).finishElements()
992 .state(GrClipStack::ClipState::kComplex)
993 .finishTest());
994}
995
996// Tests that intersections can combine even if there's a difference operation in the middle
997DEF_TEST(GrClipStack_NoDifferenceInterference, r) {
998 SkRect intR1 = {0.f, 0.f, 30.f, 30.f};
999 SkRect intR2 = {15.f, 15.f, 45.f, 45.f};
1000 SkRect intCombo = {15.f, 15.f, 30.f, 30.f};
1001 SkRect diff = {20.f, 6.f, 50.f, 50.f};
1002
1003 run_test_case(r, TestCase::Build("cross-diff-combine", kDeviceBounds)
1004 .actual().rect(intR1, GrAA::kYes, SkClipOp::kIntersect)
1005 .rect(diff, GrAA::kYes, SkClipOp::kDifference)
1006 .rect(intR2, GrAA::kYes, SkClipOp::kIntersect)
1007 .finishElements()
1008 .expect().rect(intCombo, GrAA::kYes, SkClipOp::kIntersect)
1009 .rect(diff, GrAA::kYes, SkClipOp::kDifference)
1010 .finishElements()
1011 .state(GrClipStack::ClipState::kComplex)
1012 .finishTest());
1013}
1014
1015// Tests that multiple path operations are all recorded, but not otherwise consolidated
1016DEF_TEST(GrClipStack_MultiplePaths, r) {
1017 // Chosen to be greater than the number of inline-allocated elements and save records of the
1018 // GrClipStack so that we test heap allocation as well.
1019 static constexpr int kNumOps = 16;
1020
1021 auto b = TestCase::Build("many-paths-difference", kDeviceBounds);
1022 SkRect d = {0.f, 0.f, 12.f, 12.f};
1023 for (int i = 0; i < kNumOps; ++i) {
1024 b.actual().path(make_octagon(d), GrAA::kNo, SkClipOp::kDifference);
1025
1026 d.offset(15.f, 0.f);
1027 if (d.fRight > kDeviceBounds.fRight) {
1028 d.fLeft = 0.f;
1029 d.fRight = 12.f;
1030 d.offset(0.f, 15.f);
1031 }
1032 }
1033
1034 run_test_case(r, b.expectActual()
1035 .state(GrClipStack::ClipState::kComplex)
1036 .finishTest());
1037
1038 b = TestCase::Build("many-paths-intersect", kDeviceBounds);
1039 d = {0.f, 0.f, 12.f, 12.f};
1040 for (int i = 0; i < kNumOps; ++i) {
1041 b.actual().path(make_octagon(d), GrAA::kYes, SkClipOp::kIntersect);
1042 d.offset(0.01f, 0.01f);
1043 }
1044
1045 run_test_case(r, b.expectActual()
1046 .state(GrClipStack::ClipState::kComplex)
1047 .finishTest());
1048}
1049
1050// Tests that a single rect is treated as kDeviceRect state when it's axis-aligned and intersect.
1051DEF_TEST(GrClipStack_DeviceRect, r) {
1052 // Axis-aligned + intersect -> kDeviceRect
1053 SkRect rect = {0, 0, 20, 20};
1054 run_test_case(r, TestCase::Build("device-rect", kDeviceBounds)
1055 .actual().intersect().aa().rect(rect).finishElements()
1056 .expectActual()
1057 .state(GrClipStack::ClipState::kDeviceRect)
1058 .finishTest());
1059
1060 // Not axis-aligned -> kComplex
1061 SkMatrix lm = SkMatrix::RotateDeg(15.f);
1062 run_test_case(r, TestCase::Build("unaligned-rect", kDeviceBounds)
1063 .actual().localToDevice(lm).intersect().aa().rect(rect)
1064 .finishElements()
1065 .expectActual()
1066 .state(GrClipStack::ClipState::kComplex)
1067 .finishTest());
1068
1069 // Not intersect -> kComplex
1070 run_test_case(r, TestCase::Build("diff-rect", kDeviceBounds)
1071 .actual().difference().aa().rect(rect).finishElements()
1072 .expectActual()
1073 .state(GrClipStack::ClipState::kComplex)
1074 .finishTest());
1075}
1076
1077// Tests that a single rrect is treated as kDeviceRRect state when it's axis-aligned and intersect.
1078DEF_TEST(GrClipStack_DeviceRRect, r) {
1079 // Axis-aligned + intersect -> kDeviceRRect
1080 SkRect rect = {0, 0, 20, 20};
1081 SkRRect rrect = SkRRect::MakeRectXY(rect, 5.f, 5.f);
1082 run_test_case(r, TestCase::Build("device-rrect", kDeviceBounds)
1083 .actual().intersect().aa().rrect(rrect).finishElements()
1084 .expectActual()
1085 .state(GrClipStack::ClipState::kDeviceRRect)
1086 .finishTest());
1087
1088 // Not axis-aligned -> kComplex
1089 SkMatrix lm = SkMatrix::RotateDeg(15.f);
1090 run_test_case(r, TestCase::Build("unaligned-rrect", kDeviceBounds)
1091 .actual().localToDevice(lm).intersect().aa().rrect(rrect)
1092 .finishElements()
1093 .expectActual()
1094 .state(GrClipStack::ClipState::kComplex)
1095 .finishTest());
1096
1097 // Not intersect -> kComplex
1098 run_test_case(r, TestCase::Build("diff-rrect", kDeviceBounds)
1099 .actual().difference().aa().rrect(rrect).finishElements()
1100 .expectActual()
1101 .state(GrClipStack::ClipState::kComplex)
1102 .finishTest());
1103}
1104
1105// Tests that scale+translate matrices are pre-applied to rects and rrects, which also then allows
1106// elements with different scale+translate matrices to be consolidated as if they were in the same
1107// coordinate space.
1108DEF_TEST(GrClipStack_ScaleTranslate, r) {
1109 SkMatrix lm = SkMatrix::Scale(2.f, 4.f);
1110 lm.postTranslate(15.5f, 14.3f);
Michael Ludwigd30e9ef2020-09-28 12:03:01 -04001111 SkASSERT(lm.preservesAxisAlignment() && lm.isScaleTranslate());
Michael Ludwiga195d102020-09-15 14:51:52 -04001112
1113 // Rect -> matrix is applied up front
1114 SkRect rect = {0.f, 0.f, 10.f, 10.f};
1115 run_test_case(r, TestCase::Build("st+rect", kDeviceBounds)
1116 .actual().rect(rect, lm, GrAA::kYes, SkClipOp::kIntersect)
1117 .finishElements()
1118 .expect().rect(lm.mapRect(rect), GrAA::kYes, SkClipOp::kIntersect)
1119 .finishElements()
1120 .state(GrClipStack::ClipState::kDeviceRect)
1121 .finishTest());
1122
1123 // RRect -> matrix is applied up front
1124 SkRRect localRRect = SkRRect::MakeRectXY(rect, 2.f, 2.f);
1125 SkRRect deviceRRect;
1126 SkAssertResult(localRRect.transform(lm, &deviceRRect));
1127 run_test_case(r, TestCase::Build("st+rrect", kDeviceBounds)
1128 .actual().rrect(localRRect, lm, GrAA::kYes, SkClipOp::kIntersect)
1129 .finishElements()
1130 .expect().rrect(deviceRRect, GrAA::kYes, SkClipOp::kIntersect)
1131 .finishElements()
1132 .state(GrClipStack::ClipState::kDeviceRRect)
1133 .finishTest());
1134
1135 // Path -> matrix is NOT applied
1136 run_test_case(r, TestCase::Build("st+path", kDeviceBounds)
1137 .actual().intersect().localToDevice(lm).path(make_octagon(rect))
1138 .finishElements()
1139 .expectActual()
1140 .state(GrClipStack::ClipState::kComplex)
1141 .finishTest());
1142}
1143
Michael Ludwigd30e9ef2020-09-28 12:03:01 -04001144// Tests that rect-stays-rect matrices that are not scale+translate matrices are pre-applied.
1145DEF_TEST(GrClipStack_PreserveAxisAlignment, r) {
1146 SkMatrix lm = SkMatrix::RotateDeg(90.f);
1147 lm.postTranslate(15.5f, 14.3f);
1148 SkASSERT(lm.preservesAxisAlignment() && !lm.isScaleTranslate());
1149
1150 // Rect -> matrix is applied up front
1151 SkRect rect = {0.f, 0.f, 10.f, 10.f};
1152 run_test_case(r, TestCase::Build("r90+rect", kDeviceBounds)
1153 .actual().rect(rect, lm, GrAA::kYes, SkClipOp::kIntersect)
1154 .finishElements()
1155 .expect().rect(lm.mapRect(rect), GrAA::kYes, SkClipOp::kIntersect)
1156 .finishElements()
1157 .state(GrClipStack::ClipState::kDeviceRect)
1158 .finishTest());
1159
1160 // RRect -> matrix is applied up front
1161 SkRRect localRRect = SkRRect::MakeRectXY(rect, 2.f, 2.f);
1162 SkRRect deviceRRect;
1163 SkAssertResult(localRRect.transform(lm, &deviceRRect));
1164 run_test_case(r, TestCase::Build("r90+rrect", kDeviceBounds)
1165 .actual().rrect(localRRect, lm, GrAA::kYes, SkClipOp::kIntersect)
1166 .finishElements()
1167 .expect().rrect(deviceRRect, GrAA::kYes, SkClipOp::kIntersect)
1168 .finishElements()
1169 .state(GrClipStack::ClipState::kDeviceRRect)
1170 .finishTest());
1171
1172 // Path -> matrix is NOT applied
1173 run_test_case(r, TestCase::Build("r90+path", kDeviceBounds)
1174 .actual().intersect().localToDevice(lm).path(make_octagon(rect))
1175 .finishElements()
1176 .expectActual()
1177 .state(GrClipStack::ClipState::kComplex)
1178 .finishTest());
1179}
1180
Michael Ludwiga195d102020-09-15 14:51:52 -04001181// Tests that a convex path element can contain a rect or round rect, allowing the stack to be
1182// simplified
1183DEF_TEST(GrClipStack_ConvexPathContains, r) {
1184 SkRect rect = {15.f, 15.f, 30.f, 30.f};
1185 SkRRect rrect = SkRRect::MakeRectXY(rect, 5.f, 5.f);
1186 SkPath bigPath = make_octagon(rect.makeOutset(10.f, 10.f), 5.f, 5.f);
1187
1188 // Intersect -> path element isn't kept
1189 run_test_case(r, TestCase::Build("convex+rect-intersect", kDeviceBounds)
1190 .actual().aa().intersect().rect(rect).path(bigPath).finishElements()
1191 .expect().aa().intersect().rect(rect).finishElements()
1192 .state(GrClipStack::ClipState::kDeviceRect)
1193 .finishTest());
1194 run_test_case(r, TestCase::Build("convex+rrect-intersect", kDeviceBounds)
1195 .actual().aa().intersect().rrect(rrect).path(bigPath).finishElements()
1196 .expect().aa().intersect().rrect(rrect).finishElements()
1197 .state(GrClipStack::ClipState::kDeviceRRect)
1198 .finishTest());
1199
1200 // Difference -> path element is the only one left
1201 run_test_case(r, TestCase::Build("convex+rect-difference", kDeviceBounds)
1202 .actual().aa().difference().rect(rect).path(bigPath).finishElements()
1203 .expect().aa().difference().path(bigPath).finishElements()
1204 .state(GrClipStack::ClipState::kComplex)
1205 .finishTest());
1206 run_test_case(r, TestCase::Build("convex+rrect-difference", kDeviceBounds)
1207 .actual().aa().difference().rrect(rrect).path(bigPath)
1208 .finishElements()
1209 .expect().aa().difference().path(bigPath).finishElements()
1210 .state(GrClipStack::ClipState::kComplex)
1211 .finishTest());
1212
1213 // Intersect small shape + difference big path -> empty
1214 run_test_case(r, TestCase::Build("convex-diff+rect-int", kDeviceBounds)
1215 .actual().aa().intersect().rect(rect)
1216 .difference().path(bigPath).finishElements()
1217 .state(GrClipStack::ClipState::kEmpty)
1218 .finishTest());
1219 run_test_case(r, TestCase::Build("convex-diff+rrect-int", kDeviceBounds)
1220 .actual().aa().intersect().rrect(rrect)
1221 .difference().path(bigPath).finishElements()
1222 .state(GrClipStack::ClipState::kEmpty)
1223 .finishTest());
1224
1225 // Diff small shape + intersect big path -> both
1226 run_test_case(r, TestCase::Build("convex-int+rect-diff", kDeviceBounds)
1227 .actual().aa().intersect().path(bigPath).difference().rect(rect)
1228 .finishElements()
1229 .expectActual()
1230 .state(GrClipStack::ClipState::kComplex)
1231 .finishTest());
1232 run_test_case(r, TestCase::Build("convex-int+rrect-diff", kDeviceBounds)
1233 .actual().aa().intersect().path(bigPath).difference().rrect(rrect)
1234 .finishElements()
1235 .expectActual()
1236 .state(GrClipStack::ClipState::kComplex)
1237 .finishTest());
1238}
1239
1240// Tests that rects/rrects in different coordinate spaces can be consolidated when one is fully
1241// contained by the other.
1242DEF_TEST(GrClipStack_NonAxisAlignedContains, r) {
1243 SkMatrix lm1 = SkMatrix::RotateDeg(45.f);
1244 SkRect bigR = {-20.f, -20.f, 20.f, 20.f};
1245 SkRRect bigRR = SkRRect::MakeRectXY(bigR, 1.f, 1.f);
1246
1247 SkMatrix lm2 = SkMatrix::RotateDeg(-45.f);
1248 SkRect smR = {-10.f, -10.f, 10.f, 10.f};
1249 SkRRect smRR = SkRRect::MakeRectXY(smR, 1.f, 1.f);
1250
1251 // I+I should select the smaller 2nd shape (r2 or rr2)
1252 run_test_case(r, TestCase::Build("rect-rect-ii", kDeviceBounds)
1253 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1254 .rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1255 .finishElements()
1256 .expect().rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1257 .finishElements()
1258 .state(GrClipStack::ClipState::kComplex)
1259 .finishTest());
1260 run_test_case(r, TestCase::Build("rrect-rrect-ii", kDeviceBounds)
1261 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1262 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1263 .finishElements()
1264 .expect().rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1265 .finishElements()
1266 .state(GrClipStack::ClipState::kComplex)
1267 .finishTest());
1268 run_test_case(r, TestCase::Build("rect-rrect-ii", kDeviceBounds)
1269 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1270 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1271 .finishElements()
1272 .expect().rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1273 .finishElements()
1274 .state(GrClipStack::ClipState::kComplex)
1275 .finishTest());
1276 run_test_case(r, TestCase::Build("rrect-rect-ii", kDeviceBounds)
1277 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1278 .rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1279 .finishElements()
1280 .expect().rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1281 .finishElements()
1282 .state(GrClipStack::ClipState::kComplex)
1283 .finishTest());
1284
1285 // D+D should select the larger shape (r1 or rr1)
1286 run_test_case(r, TestCase::Build("rect-rect-dd", kDeviceBounds)
1287 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference)
1288 .rect(smR, lm2, GrAA::kYes, SkClipOp::kDifference)
1289 .finishElements()
1290 .expect().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference)
1291 .finishElements()
1292 .state(GrClipStack::ClipState::kComplex)
1293 .finishTest());
1294 run_test_case(r, TestCase::Build("rrect-rrect-dd", kDeviceBounds)
1295 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference)
1296 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kDifference)
1297 .finishElements()
1298 .expect().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference)
1299 .finishElements()
1300 .state(GrClipStack::ClipState::kComplex)
1301 .finishTest());
1302 run_test_case(r, TestCase::Build("rect-rrect-dd", kDeviceBounds)
1303 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference)
1304 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kDifference)
1305 .finishElements()
1306 .expect().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference)
1307 .finishElements()
1308 .state(GrClipStack::ClipState::kComplex)
1309 .finishTest());
1310 run_test_case(r, TestCase::Build("rrect-rect-dd", kDeviceBounds)
1311 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference)
1312 .rect(smR, lm2, GrAA::kYes, SkClipOp::kDifference)
1313 .finishElements()
1314 .expect().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference)
1315 .finishElements()
1316 .state(GrClipStack::ClipState::kComplex)
1317 .finishTest());
1318
1319 // D(1)+I(2) should result in empty
1320 run_test_case(r, TestCase::Build("rectD-rectI", kDeviceBounds)
1321 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference)
1322 .rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1323 .finishElements()
1324 .state(GrClipStack::ClipState::kEmpty)
1325 .finishTest());
1326 run_test_case(r, TestCase::Build("rrectD-rrectI", kDeviceBounds)
1327 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference)
1328 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1329 .finishElements()
1330 .state(GrClipStack::ClipState::kEmpty)
1331 .finishTest());
1332 run_test_case(r, TestCase::Build("rectD-rrectI", kDeviceBounds)
1333 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference)
1334 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1335 .finishElements()
1336 .state(GrClipStack::ClipState::kEmpty)
1337 .finishTest());
1338 run_test_case(r, TestCase::Build("rrectD-rectI", kDeviceBounds)
1339 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference)
1340 .rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1341 .finishElements()
1342 .state(GrClipStack::ClipState::kEmpty)
1343 .finishTest());
1344
1345 // I(1)+D(2) should result in both shapes
1346 run_test_case(r, TestCase::Build("rectI+rectD", kDeviceBounds)
1347 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1348 .rect(smR, lm2, GrAA::kYes, SkClipOp::kDifference)
1349 .finishElements()
1350 .expectActual()
1351 .state(GrClipStack::ClipState::kComplex)
1352 .finishTest());
1353 run_test_case(r, TestCase::Build("rrectI+rrectD", kDeviceBounds)
1354 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1355 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kDifference)
1356 .finishElements()
1357 .expectActual()
1358 .state(GrClipStack::ClipState::kComplex)
1359 .finishTest());
1360 run_test_case(r, TestCase::Build("rrectI+rectD", kDeviceBounds)
1361 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1362 .rect(smR, lm2, GrAA::kYes, SkClipOp::kDifference)
1363 .finishElements()
1364 .expectActual()
1365 .state(GrClipStack::ClipState::kComplex)
1366 .finishTest());
1367 run_test_case(r, TestCase::Build("rectI+rrectD", kDeviceBounds)
1368 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1369 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kDifference)
1370 .finishElements()
1371 .expectActual()
1372 .state(GrClipStack::ClipState::kComplex)
1373 .finishTest());
1374}
1375
1376// Tests that shapes with mixed AA state that contain each other can still be consolidated,
1377// unless they are too close to the edge and non-AA snapping can't be predicted
1378DEF_TEST(GrClipStack_MixedAAContains, r) {
1379 SkMatrix lm1 = SkMatrix::RotateDeg(45.f);
1380 SkRect r1 = {-20.f, -20.f, 20.f, 20.f};
1381
1382 SkMatrix lm2 = SkMatrix::RotateDeg(-45.f);
1383 SkRect r2Safe = {-10.f, -10.f, 10.f, 10.f};
1384 SkRect r2Unsafe = {-19.5f, -19.5f, 19.5f, 19.5f};
1385
1386 // Non-AA sufficiently inside AA element can discard the outer AA element
1387 run_test_case(r, TestCase::Build("mixed-outeraa-combine", kDeviceBounds)
1388 .actual().rect(r1, lm1, GrAA::kYes, SkClipOp::kIntersect)
1389 .rect(r2Safe, lm2, GrAA::kNo, SkClipOp::kIntersect)
1390 .finishElements()
1391 .expect().rect(r2Safe, lm2, GrAA::kNo, SkClipOp::kIntersect)
1392 .finishElements()
1393 .state(GrClipStack::ClipState::kComplex)
1394 .finishTest());
1395 // Vice versa
1396 run_test_case(r, TestCase::Build("mixed-inneraa-combine", kDeviceBounds)
1397 .actual().rect(r1, lm1, GrAA::kNo, SkClipOp::kIntersect)
1398 .rect(r2Safe, lm2, GrAA::kYes, SkClipOp::kIntersect)
1399 .finishElements()
1400 .expect().rect(r2Safe, lm2, GrAA::kYes, SkClipOp::kIntersect)
1401 .finishElements()
1402 .state(GrClipStack::ClipState::kComplex)
1403 .finishTest());
1404
1405 // Non-AA too close to AA edges keeps both
1406 run_test_case(r, TestCase::Build("mixed-outeraa-nocombine", kDeviceBounds)
1407 .actual().rect(r1, lm1, GrAA::kYes, SkClipOp::kIntersect)
1408 .rect(r2Unsafe, lm2, GrAA::kNo, SkClipOp::kIntersect)
1409 .finishElements()
1410 .expectActual()
1411 .state(GrClipStack::ClipState::kComplex)
1412 .finishTest());
1413 run_test_case(r, TestCase::Build("mixed-inneraa-nocombine", kDeviceBounds)
1414 .actual().rect(r1, lm1, GrAA::kNo, SkClipOp::kIntersect)
1415 .rect(r2Unsafe, lm2, GrAA::kYes, SkClipOp::kIntersect)
1416 .finishElements()
1417 .expectActual()
1418 .state(GrClipStack::ClipState::kComplex)
1419 .finishTest());
1420}
1421
1422// Tests that a shape that contains the device bounds updates the clip state directly
1423DEF_TEST(GrClipStack_ShapeContainsDevice, r) {
1424 SkRect rect = SkRect::Make(kDeviceBounds).makeOutset(10.f, 10.f);
1425 SkRRect rrect = SkRRect::MakeRectXY(rect, 10.f, 10.f);
1426 SkPath convex = make_octagon(rect, 10.f, 10.f);
1427
1428 // Intersect -> no-op
1429 run_test_case(r, TestCase::Build("rect-intersect", kDeviceBounds)
1430 .actual().intersect().rect(rect).finishElements()
1431 .state(GrClipStack::ClipState::kWideOpen)
1432 .finishTest());
1433 run_test_case(r, TestCase::Build("rrect-intersect", kDeviceBounds)
1434 .actual().intersect().rrect(rrect).finishElements()
1435 .state(GrClipStack::ClipState::kWideOpen)
1436 .finishTest());
1437 run_test_case(r, TestCase::Build("convex-intersect", kDeviceBounds)
1438 .actual().intersect().path(convex).finishElements()
1439 .state(GrClipStack::ClipState::kWideOpen)
1440 .finishTest());
1441
1442 // Difference -> empty
1443 run_test_case(r, TestCase::Build("rect-difference", kDeviceBounds)
1444 .actual().difference().rect(rect).finishElements()
1445 .state(GrClipStack::ClipState::kEmpty)
1446 .finishTest());
1447 run_test_case(r, TestCase::Build("rrect-difference", kDeviceBounds)
1448 .actual().difference().rrect(rrect).finishElements()
1449 .state(GrClipStack::ClipState::kEmpty)
1450 .finishTest());
1451 run_test_case(r, TestCase::Build("convex-difference", kDeviceBounds)
1452 .actual().difference().path(convex).finishElements()
1453 .state(GrClipStack::ClipState::kEmpty)
1454 .finishTest());
1455}
1456
1457// Tests that shapes that do not overlap make for an empty clip (when intersecting), pick just the
1458// intersecting op (when mixed), or are all kept (when diff'ing).
1459DEF_TEST(GrClipStack_DisjointShapes, r) {
1460 SkRect rt = {10.f, 10.f, 20.f, 20.f};
1461 SkRRect rr = SkRRect::MakeOval(rt.makeOffset({20.f, 0.f}));
1462 SkPath p = make_octagon(rt.makeOffset({0.f, 20.f}));
1463
1464 // I+I
1465 run_test_case(r, TestCase::Build("iii", kDeviceBounds)
1466 .actual().aa().intersect().rect(rt).rrect(rr).path(p).finishElements()
1467 .state(GrClipStack::ClipState::kEmpty)
1468 .finishTest());
1469
1470 // D+D
1471 run_test_case(r, TestCase::Build("ddd", kDeviceBounds)
1472 .actual().nonAA().difference().rect(rt).rrect(rr).path(p)
1473 .finishElements()
1474 .expectActual()
1475 .state(GrClipStack::ClipState::kComplex)
1476 .finishTest());
1477
1478 // I+D from rect
1479 run_test_case(r, TestCase::Build("idd", kDeviceBounds)
1480 .actual().aa().intersect().rect(rt)
1481 .nonAA().difference().rrect(rr).path(p)
1482 .finishElements()
1483 .expect().aa().intersect().rect(rt).finishElements()
1484 .state(GrClipStack::ClipState::kDeviceRect)
1485 .finishTest());
1486
1487 // I+D from rrect
1488 run_test_case(r, TestCase::Build("did", kDeviceBounds)
1489 .actual().aa().intersect().rrect(rr)
1490 .nonAA().difference().rect(rt).path(p)
1491 .finishElements()
1492 .expect().aa().intersect().rrect(rr).finishElements()
1493 .state(GrClipStack::ClipState::kDeviceRRect)
1494 .finishTest());
1495
1496 // I+D from path
1497 run_test_case(r, TestCase::Build("ddi", kDeviceBounds)
1498 .actual().aa().intersect().path(p)
1499 .nonAA().difference().rect(rt).rrect(rr)
1500 .finishElements()
1501 .expect().aa().intersect().path(p).finishElements()
1502 .state(GrClipStack::ClipState::kComplex)
1503 .finishTest());
1504}
1505
1506DEF_TEST(GrClipStack_ComplexClip, r) {
1507 static constexpr float kN = 10.f;
1508 static constexpr float kR = kN / 3.f;
1509
1510 // 4 rectangles that overlap by kN x 2kN (horiz), 2kN x kN (vert), or kN x kN (diagonal)
1511 static const SkRect kTL = {0.f, 0.f, 2.f * kN, 2.f * kN};
1512 static const SkRect kTR = {kN, 0.f, 3.f * kN, 2.f * kN};
1513 static const SkRect kBL = {0.f, kN, 2.f * kN, 3.f * kN};
1514 static const SkRect kBR = {kN, kN, 3.f * kN, 3.f * kN};
1515
1516 enum ShapeType { kRect, kRRect, kConvex };
1517
1518 SkRect rects[] = { kTL, kTR, kBL, kBR };
1519 for (ShapeType type : { kRect, kRRect, kConvex }) {
1520 for (int opBits = 6; opBits < 16; ++opBits) {
1521 SkString name;
1522 name.appendf("complex-%d-%d", (int) type, opBits);
1523
1524 SkRect expectedRectIntersection = SkRect::Make(kDeviceBounds);
1525 SkRRect expectedRRectIntersection = SkRRect::MakeRect(expectedRectIntersection);
1526
1527 auto b = TestCase::Build(name.c_str(), kDeviceBounds);
1528 for (int i = 0; i < 4; ++i) {
1529 SkClipOp op = (opBits & (1 << i)) ? SkClipOp::kIntersect : SkClipOp::kDifference;
1530 switch(type) {
1531 case kRect: {
1532 SkRect r = rects[i];
1533 if (op == SkClipOp::kDifference) {
1534 // Shrink the rect for difference ops, otherwise in the rect testcase
1535 // any difference op would remove the intersection of the other ops
1536 // given how the rects are defined, and that's just not interesting.
1537 r.inset(kR, kR);
1538 }
1539 b.actual().rect(r, GrAA::kYes, op);
1540 if (op == SkClipOp::kIntersect) {
1541 SkAssertResult(expectedRectIntersection.intersect(r));
1542 } else {
1543 b.expect().rect(r, GrAA::kYes, SkClipOp::kDifference);
1544 }
1545 break; }
1546 case kRRect: {
1547 SkRRect rrect = SkRRect::MakeRectXY(rects[i], kR, kR);
1548 b.actual().rrect(rrect, GrAA::kYes, op);
1549 if (op == SkClipOp::kIntersect) {
1550 expectedRRectIntersection = SkRRectPriv::ConservativeIntersect(
1551 expectedRRectIntersection, rrect);
1552 SkASSERT(!expectedRRectIntersection.isEmpty());
1553 } else {
1554 b.expect().rrect(rrect, GrAA::kYes, SkClipOp::kDifference);
1555 }
1556 break; }
1557 case kConvex:
1558 b.actual().path(make_octagon(rects[i], kR, kR), GrAA::kYes, op);
1559 // NOTE: We don't set any expectations here, since convex just calls
1560 // expectActual() at the end.
1561 break;
1562 }
1563 }
1564
1565 // The expectations differ depending on the shape type
1566 GrClipStack::ClipState state = GrClipStack::ClipState::kComplex;
1567 if (type == kConvex) {
1568 // The simplest case is when the paths cannot be combined together, so we expect
1569 // the actual elements to be unmodified (both intersect and difference).
1570 b.expectActual();
1571 } else if (opBits) {
1572 // All intersection ops were pre-computed into expectedR[R]ectIntersection
1573 // - difference ops already added in the for loop
1574 if (type == kRect) {
1575 SkASSERT(expectedRectIntersection != SkRect::Make(kDeviceBounds) &&
1576 !expectedRectIntersection.isEmpty());
1577 b.expect().rect(expectedRectIntersection, GrAA::kYes, SkClipOp::kIntersect);
1578 if (opBits == 0xf) {
1579 state = GrClipStack::ClipState::kDeviceRect;
1580 }
1581 } else {
1582 SkASSERT(expectedRRectIntersection !=
1583 SkRRect::MakeRect(SkRect::Make(kDeviceBounds)) &&
1584 !expectedRRectIntersection.isEmpty());
1585 b.expect().rrect(expectedRRectIntersection, GrAA::kYes, SkClipOp::kIntersect);
1586 if (opBits == 0xf) {
1587 state = GrClipStack::ClipState::kDeviceRRect;
1588 }
1589 }
1590 }
1591
1592 run_test_case(r, b.state(state).finishTest());
1593 }
1594 }
1595}
1596
1597// ///////////////////////////////////////////////////////////////////////////////
1598// // These tests do not use the TestCase infrastructure and manipulate a
1599// // GrClipStack directly.
1600
1601// Tests that replaceClip() works as expected across save/restores
1602DEF_TEST(GrClipStack_ReplaceClip, r) {
1603 GrClipStack cs(kDeviceBounds, nullptr, false);
1604
1605 SkRRect rrect = SkRRect::MakeRectXY({15.f, 12.25f, 40.3f, 23.5f}, 4.f, 6.f);
1606 cs.clipRRect(SkMatrix::I(), rrect, GrAA::kYes, SkClipOp::kIntersect);
1607
1608 SkIRect replace = {50, 25, 75, 40}; // Is disjoint from the rrect element
1609 cs.save();
1610 cs.replaceClip(replace);
1611
1612 REPORTER_ASSERT(r, cs.clipState() == GrClipStack::ClipState::kDeviceRect,
1613 "Clip did not become a device rect");
1614 REPORTER_ASSERT(r, cs.getConservativeBounds() == replace, "Unexpected replaced clip bounds");
1615 const GrClipStack::Element& replaceElement = *cs.begin();
1616 REPORTER_ASSERT(r, replaceElement.fShape.rect() == SkRect::Make(replace) &&
1617 replaceElement.fAA == GrAA::kNo &&
1618 replaceElement.fOp == SkClipOp::kIntersect &&
1619 replaceElement.fLocalToDevice == SkMatrix::I(),
1620 "Unexpected replace element state");
1621
1622 // Restore should undo the replaced clip and bring back the rrect
1623 cs.restore();
1624 REPORTER_ASSERT(r, cs.clipState() == GrClipStack::ClipState::kDeviceRRect,
1625 "Unexpected state after restore, not kDeviceRRect");
1626 const GrClipStack::Element& rrectElem = *cs.begin();
1627 REPORTER_ASSERT(r, rrectElem.fShape.rrect() == rrect &&
1628 rrectElem.fAA == GrAA::kYes &&
1629 rrectElem.fOp == SkClipOp::kIntersect &&
1630 rrectElem.fLocalToDevice == SkMatrix::I(),
1631 "RRect element state not restored properly after replace clip undone");
1632}
1633
Robert Phillipsc4fbc8d2020-11-30 10:17:53 -05001634// Try to overflow the number of allowed window rects (see skbug.com/10989)
1635DEF_TEST(GrClipStack_DiffRects, r) {
1636 GrMockOptions options;
1637 options.fMaxWindowRectangles = 8;
1638
1639 SkSimpleMatrixProvider matrixProvider = SkMatrix::I();
1640 sk_sp<GrDirectContext> context = GrDirectContext::MakeMock(&options);
Brian Salomoneebe7352020-12-09 16:37:04 -05001641 std::unique_ptr<GrSurfaceDrawContext> rtc = GrSurfaceDrawContext::Make(
Robert Phillipsc4fbc8d2020-11-30 10:17:53 -05001642 context.get(), GrColorType::kRGBA_8888, SkColorSpace::MakeSRGB(),
1643 SkBackingFit::kExact, kDeviceBounds.size());
1644
1645 GrClipStack cs(kDeviceBounds, &matrixProvider, false);
1646
1647 cs.save();
1648 for (int y = 0; y < 10; ++y) {
1649 for (int x = 0; x < 10; ++x) {
1650 cs.clipRect(SkMatrix::I(), SkRect::MakeXYWH(10*x+1, 10*y+1, 8, 8),
1651 GrAA::kNo, SkClipOp::kDifference);
1652 }
1653 }
1654
1655 GrAppliedClip out(kDeviceBounds.size());
1656 SkRect drawBounds = SkRect::Make(kDeviceBounds);
1657 GrClip::Effect effect = cs.apply(context.get(), rtc.get(), GrAAType::kCoverage, false,
1658 &out, &drawBounds);
1659
1660 REPORTER_ASSERT(r, effect == GrClip::Effect::kClipped);
1661 REPORTER_ASSERT(r, out.windowRectsState().numWindows() == 8);
1662
1663 cs.restore();
1664}
1665
Michael Ludwiga195d102020-09-15 14:51:52 -04001666// Tests that when a stack is forced to always be AA, non-AA elements become AA
1667DEF_TEST(GrClipStack_ForceAA, r) {
1668 GrClipStack cs(kDeviceBounds, nullptr, true);
1669
1670 // AA will remain AA
1671 SkRect aaRect = {0.25f, 12.43f, 25.2f, 23.f};
1672 cs.clipRect(SkMatrix::I(), aaRect, GrAA::kYes, SkClipOp::kIntersect);
1673
1674 // Non-AA will become AA
1675 SkPath nonAAPath = make_octagon({2.f, 10.f, 16.f, 20.f});
1676 cs.clipPath(SkMatrix::I(), nonAAPath, GrAA::kNo, SkClipOp::kIntersect);
1677
Michael Ludwig462bdfc2020-09-22 16:27:04 -04001678 // Non-AA rects remain non-AA so they can be applied as a scissor
Michael Ludwiga195d102020-09-15 14:51:52 -04001679 SkRect nonAARect = {4.5f, 5.f, 17.25f, 18.23f};
1680 cs.clipRect(SkMatrix::I(), nonAARect, GrAA::kNo, SkClipOp::kIntersect);
1681
1682 // The stack reports elements newest first, but the non-AA rect op was combined in place with
1683 // the first aa rect, so we should see nonAAPath as AA, and then the intersection of rects.
Michael Ludwiga195d102020-09-15 14:51:52 -04001684 auto elements = cs.begin();
1685
Michael Ludwig462bdfc2020-09-22 16:27:04 -04001686 const GrClipStack::Element& nonAARectElement = *elements;
1687 REPORTER_ASSERT(r, nonAARectElement.fShape.isRect(), "Expected rect element");
1688 REPORTER_ASSERT(r, nonAARectElement.fAA == GrAA::kNo,
1689 "Axis-aligned non-AA rect ignores forceAA");
1690 REPORTER_ASSERT(r, nonAARectElement.fShape.rect() == nonAARect,
1691 "Mixed AA rects should not combine");
Michael Ludwiga195d102020-09-15 14:51:52 -04001692
1693 ++elements;
Michael Ludwig462bdfc2020-09-22 16:27:04 -04001694 const GrClipStack::Element& aaPathElement = *elements;
1695 REPORTER_ASSERT(r, aaPathElement.fShape.isPath(), "Expected path element");
1696 REPORTER_ASSERT(r, aaPathElement.fShape.path() == nonAAPath, "Wrong path element");
1697 REPORTER_ASSERT(r, aaPathElement.fAA == GrAA::kYes, "Path element not promoted to AA");
Michael Ludwiga195d102020-09-15 14:51:52 -04001698
1699 ++elements;
Michael Ludwig462bdfc2020-09-22 16:27:04 -04001700 const GrClipStack::Element& aaRectElement = *elements;
1701 REPORTER_ASSERT(r, aaRectElement.fShape.isRect(), "Expected rect element");
1702 REPORTER_ASSERT(r, aaRectElement.fShape.rect() == aaRect,
1703 "Mixed AA rects should not combine");
1704 REPORTER_ASSERT(r, aaRectElement.fAA == GrAA::kYes, "Rect element stays AA");
1705
1706 ++elements;
1707 REPORTER_ASSERT(r, !(elements != cs.end()), "Expected only three clip elements");
Michael Ludwiga195d102020-09-15 14:51:52 -04001708}
1709
1710// Tests preApply works as expected for device rects, rrects, and reports clipped-out, etc. as
1711// expected.
1712DEF_TEST(GrClipStack_PreApply, r) {
1713 GrClipStack cs(kDeviceBounds, nullptr, false);
1714
1715 // Offscreen is kClippedOut
1716 GrClip::PreClipResult result = cs.preApply({-10.f, -10.f, -1.f, -1.f}, GrAA::kYes);
1717 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClippedOut,
1718 "Offscreen draw is kClippedOut");
1719
1720 // Intersecting screen with wide-open clip is kUnclipped
1721 result = cs.preApply({-10.f, -10.f, 10.f, 10.f}, GrAA::kYes);
1722 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kUnclipped,
1723 "Wide open screen intersection is still kUnclipped");
1724
1725 // Empty clip is clipped out
1726 cs.save();
1727 cs.clipRect(SkMatrix::I(), SkRect::MakeEmpty(), GrAA::kNo, SkClipOp::kIntersect);
1728 result = cs.preApply({0.f, 0.f, 20.f, 20.f}, GrAA::kYes);
1729 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClippedOut,
1730 "Empty clip stack preApplies as kClippedOut");
1731 cs.restore();
1732
1733 // Contained inside clip is kUnclipped (using rrect for the outer clip element since paths
1734 // don't support an inner bounds and anything complex is otherwise skipped in preApply).
1735 SkRect rect = {10.f, 10.f, 40.f, 40.f};
1736 SkRRect bigRRect = SkRRect::MakeRectXY(rect.makeOutset(5.f, 5.f), 5.f, 5.f);
1737 cs.save();
1738 cs.clipRRect(SkMatrix::I(), bigRRect, GrAA::kYes, SkClipOp::kIntersect);
1739 result = cs.preApply(rect, GrAA::kYes);
1740 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kUnclipped,
1741 "Draw contained within clip is kUnclipped");
1742
1743 // Disjoint from clip (but still on screen) is kClippedOut
1744 result = cs.preApply({50.f, 50.f, 60.f, 60.f}, GrAA::kYes);
1745 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClippedOut,
1746 "Draw not intersecting clip is kClippedOut");
1747 cs.restore();
1748
1749 // Intersecting clip is kClipped for complex shape
1750 cs.save();
1751 SkPath path = make_octagon(rect.makeOutset(5.f, 5.f), 5.f, 5.f);
1752 cs.clipPath(SkMatrix::I(), path, GrAA::kYes, SkClipOp::kIntersect);
1753 result = cs.preApply(path.getBounds(), GrAA::kNo);
1754 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClipped && !result.fIsRRect,
1755 "Draw with complex clip is kClipped, but is not an rrect");
1756 cs.restore();
1757
1758 // Intersecting clip is kDeviceRect for axis-aligned rect clip
1759 cs.save();
1760 cs.clipRect(SkMatrix::I(), rect, GrAA::kYes, SkClipOp::kIntersect);
1761 result = cs.preApply(rect.makeOffset(2.f, 2.f), GrAA::kNo);
1762 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClipped &&
1763 result.fAA == GrAA::kYes &&
1764 result.fIsRRect &&
1765 result.fRRect == SkRRect::MakeRect(rect),
1766 "kDeviceRect clip stack should be reported by preApply");
1767 cs.restore();
1768
1769 // Intersecting clip is kDeviceRRect for axis-aligned rrect clip
1770 cs.save();
1771 SkRRect clipRRect = SkRRect::MakeRectXY(rect, 5.f, 5.f);
1772 cs.clipRRect(SkMatrix::I(), clipRRect, GrAA::kYes, SkClipOp::kIntersect);
1773 result = cs.preApply(rect.makeOffset(2.f, 2.f), GrAA::kNo);
1774 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClipped &&
1775 result.fAA == GrAA::kYes &&
1776 result.fIsRRect &&
1777 result.fRRect == clipRRect,
1778 "kDeviceRRect clip stack should be reported by preApply");
1779 cs.restore();
1780}
1781
1782// Tests the clip shader entry point
1783DEF_TEST(GrClipStack_Shader, r) {
1784 sk_sp<SkShader> shader = SkShaders::Color({0.f, 0.f, 0.f, 0.5f}, nullptr);
1785
1786 SkSimpleMatrixProvider matrixProvider = SkMatrix::I();
1787 sk_sp<GrDirectContext> context = GrDirectContext::MakeMock(nullptr);
Brian Salomoneebe7352020-12-09 16:37:04 -05001788 std::unique_ptr<GrSurfaceDrawContext> rtc = GrSurfaceDrawContext::Make(
Michael Ludwiga195d102020-09-15 14:51:52 -04001789 context.get(), GrColorType::kRGBA_8888, SkColorSpace::MakeSRGB(),
1790 SkBackingFit::kExact, kDeviceBounds.size());
1791
1792 GrClipStack cs(kDeviceBounds, &matrixProvider, false);
1793 cs.save();
1794 cs.clipShader(shader);
1795
1796 REPORTER_ASSERT(r, cs.clipState() == GrClipStack::ClipState::kComplex,
1797 "A clip shader should be reported as a complex clip");
1798
1799 GrAppliedClip out(kDeviceBounds.size());
1800 SkRect drawBounds = {10.f, 11.f, 16.f, 32.f};
1801 GrClip::Effect effect = cs.apply(context.get(), rtc.get(), GrAAType::kCoverage, false,
1802 &out, &drawBounds);
1803
1804 REPORTER_ASSERT(r, effect == GrClip::Effect::kClipped,
1805 "apply() should return kClipped for a clip shader");
1806 REPORTER_ASSERT(r, out.hasCoverageFragmentProcessor(),
1807 "apply() should have converted clip shader to a coverage FP");
1808
1809 GrAppliedClip out2(kDeviceBounds.size());
1810 drawBounds = {-15.f, -10.f, -1.f, 10.f}; // offscreen
1811 effect = cs.apply(context.get(), rtc.get(), GrAAType::kCoverage, false,
1812 &out2, &drawBounds);
1813 REPORTER_ASSERT(r, effect == GrClip::Effect::kClippedOut,
1814 "apply() should still discard offscreen draws with a clip shader");
1815
1816 cs.restore();
1817 REPORTER_ASSERT(r, cs.clipState() == GrClipStack::ClipState::kWideOpen,
1818 "restore() should get rid of the clip shader");
1819
1820
1821 // Adding a clip shader on top of a device rect clip should prevent preApply from reporting
1822 // it as a device rect
1823 cs.clipRect(SkMatrix::I(), {10, 15, 30, 30}, GrAA::kNo, SkClipOp::kIntersect);
1824 SkASSERT(cs.clipState() == GrClipStack::ClipState::kDeviceRect); // test precondition
1825 cs.clipShader(shader);
1826 GrClip::PreClipResult result = cs.preApply(SkRect::Make(kDeviceBounds), GrAA::kYes);
1827 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClipped && !result.fIsRRect,
1828 "A clip shader should not produce a device rect from preApply");
1829}
1830
1831// Tests apply() under simple circumstances, that don't require actual rendering of masks, or
1832// atlases. This lets us define the test regularly instead of a GPU-only test.
1833// - This is not exhaustive and is challenging to unit test, so apply() is predominantly tested by
1834// the GMs instead.
1835DEF_TEST(GrClipStack_SimpleApply, r) {
1836 SkSimpleMatrixProvider matrixProvider = SkMatrix::I();
1837 sk_sp<GrDirectContext> context = GrDirectContext::MakeMock(nullptr);
Brian Salomoneebe7352020-12-09 16:37:04 -05001838 std::unique_ptr<GrSurfaceDrawContext> rtc = GrSurfaceDrawContext::Make(
Michael Ludwiga195d102020-09-15 14:51:52 -04001839 context.get(), GrColorType::kRGBA_8888, SkColorSpace::MakeSRGB(),
1840 SkBackingFit::kExact, kDeviceBounds.size());
1841
1842 GrClipStack cs(kDeviceBounds, &matrixProvider, false);
1843
1844 // Offscreen draw is kClippedOut
1845 {
1846 SkRect drawBounds = {-15.f, -15.f, -1.f, -1.f};
1847
1848 GrAppliedClip out(kDeviceBounds.size());
1849 GrClip::Effect effect = cs.apply(context.get(), rtc.get(), GrAAType::kCoverage, false,
1850 &out, &drawBounds);
1851 REPORTER_ASSERT(r, effect == GrClip::Effect::kClippedOut, "Offscreen draw is clipped out");
1852 }
1853
1854 // Draw contained in clip is kUnclipped
1855 {
1856 SkRect drawBounds = {15.4f, 16.3f, 26.f, 32.f};
1857 cs.save();
1858 cs.clipPath(SkMatrix::I(), make_octagon(drawBounds.makeOutset(5.f, 5.f), 5.f, 5.f),
1859 GrAA::kYes, SkClipOp::kIntersect);
1860
1861 GrAppliedClip out(kDeviceBounds.size());
1862 GrClip::Effect effect = cs.apply(context.get(), rtc.get(), GrAAType::kCoverage, false,
1863 &out, &drawBounds);
1864 REPORTER_ASSERT(r, effect == GrClip::Effect::kUnclipped, "Draw inside clip is unclipped");
1865 cs.restore();
1866 }
1867
1868 // Draw bounds are cropped to device space before checking contains
1869 {
1870 SkRect clipRect = {kDeviceBounds.fRight - 20.f, 10.f, kDeviceBounds.fRight, 20.f};
1871 SkRect drawRect = clipRect.makeOffset(10.f, 0.f);
1872
1873 cs.save();
1874 cs.clipRect(SkMatrix::I(), clipRect, GrAA::kNo, SkClipOp::kIntersect);
1875
1876 GrAppliedClip out(kDeviceBounds.size());
1877 GrClip::Effect effect = cs.apply(context.get(), rtc.get(), GrAAType::kCoverage, false,
1878 &out, &drawRect);
1879 REPORTER_ASSERT(r, SkRect::Make(kDeviceBounds).contains(drawRect),
1880 "Draw rect should be clipped to device rect");
1881 REPORTER_ASSERT(r, effect == GrClip::Effect::kUnclipped,
1882 "After device clipping, this should be detected as contained within clip");
1883 cs.restore();
1884 }
1885
1886 // Non-AA device rect intersect is just a scissor
1887 {
1888 SkRect clipRect = {15.3f, 17.23f, 30.2f, 50.8f};
1889 SkRect drawRect = clipRect.makeOutset(10.f, 10.f);
1890 SkIRect expectedScissor = clipRect.round();
1891
1892 cs.save();
1893 cs.clipRect(SkMatrix::I(), clipRect, GrAA::kNo, SkClipOp::kIntersect);
1894
1895 GrAppliedClip out(kDeviceBounds.size());
1896 GrClip::Effect effect = cs.apply(context.get(), rtc.get(), GrAAType::kCoverage, false,
1897 &out, &drawRect);
1898 REPORTER_ASSERT(r, effect == GrClip::Effect::kClipped, "Draw should be clipped by rect");
1899 REPORTER_ASSERT(r, !out.hasCoverageFragmentProcessor(), "Clip should not use coverage FPs");
1900 REPORTER_ASSERT(r, !out.hardClip().hasStencilClip(), "Clip should not need stencil");
1901 REPORTER_ASSERT(r, !out.hardClip().windowRectsState().enabled(),
1902 "Clip should not need window rects");
1903 REPORTER_ASSERT(r, out.scissorState().enabled() &&
1904 out.scissorState().rect() == expectedScissor,
1905 "Clip has unexpected scissor rectangle");
1906 cs.restore();
1907 }
1908
1909 // Analytic coverage FPs
1910 auto testHasCoverageFP = [&](SkRect drawBounds) {
1911 GrAppliedClip out(kDeviceBounds.size());
1912 GrClip::Effect effect = cs.apply(context.get(), rtc.get(), GrAAType::kCoverage, false,
1913 &out, &drawBounds);
1914 REPORTER_ASSERT(r, effect == GrClip::Effect::kClipped, "Draw should be clipped");
1915 REPORTER_ASSERT(r, out.scissorState().enabled(), "Coverage FPs should still set scissor");
1916 REPORTER_ASSERT(r, out.hasCoverageFragmentProcessor(), "Clip should use coverage FP");
1917 };
1918
1919 // Axis-aligned rect can be an analytic FP
1920 {
1921 cs.save();
1922 cs.clipRect(SkMatrix::I(), {10.2f, 8.342f, 63.f, 23.3f}, GrAA::kYes,
1923 SkClipOp::kDifference);
1924 testHasCoverageFP({9.f, 10.f, 30.f, 18.f});
1925 cs.restore();
1926 }
1927
1928 // Axis-aligned round rect can be an analytic FP
1929 {
1930 SkRect rect = {4.f, 8.f, 20.f, 20.f};
1931 cs.save();
1932 cs.clipRRect(SkMatrix::I(), SkRRect::MakeRectXY(rect, 3.f, 3.f), GrAA::kYes,
1933 SkClipOp::kIntersect);
1934 testHasCoverageFP(rect.makeOffset(2.f, 2.f));
1935 cs.restore();
1936 }
1937
1938 // Transformed rect can be an analytic FP
1939 {
1940 SkRect rect = {14.f, 8.f, 30.f, 22.34f};
1941 SkMatrix rot = SkMatrix::RotateDeg(34.f);
1942 cs.save();
1943 cs.clipRect(rot, rect, GrAA::kNo, SkClipOp::kIntersect);
1944 testHasCoverageFP(rot.mapRect(rect));
1945 cs.restore();
1946 }
1947
1948 // Convex polygons can be an analytic FP
1949 {
1950 SkRect rect = {15.f, 15.f, 45.f, 45.f};
1951 cs.save();
1952 cs.clipPath(SkMatrix::I(), make_octagon(rect), GrAA::kYes, SkClipOp::kIntersect);
1953 testHasCoverageFP(rect.makeOutset(2.f, 2.f));
1954 cs.restore();
1955 }
1956}
1957
1958// Must disable CCPR in order to trigger SW mask generation when the clip stack is applied.
1959static void only_allow_default(GrContextOptions* options) {
1960 options->fGpuPathRenderers = GpuPathRenderers::kNone;
Michael Ludwig1cf303f2020-09-15 16:42:16 -04001961 options->fDisableCoverageCountingPaths = true;
Michael Ludwiga195d102020-09-15 14:51:52 -04001962}
1963
1964DEF_GPUTEST_FOR_CONTEXTS(GrClipStack_SWMask,
1965 sk_gpu_test::GrContextFactory::IsRenderingContext,
1966 r, ctxInfo, only_allow_default) {
1967 GrDirectContext* context = ctxInfo.directContext();
Brian Salomoneebe7352020-12-09 16:37:04 -05001968 std::unique_ptr<GrSurfaceDrawContext> rtc = GrSurfaceDrawContext::Make(
Michael Ludwiga195d102020-09-15 14:51:52 -04001969 context, GrColorType::kRGBA_8888, nullptr, SkBackingFit::kExact, kDeviceBounds.size());
1970
1971 SkSimpleMatrixProvider matrixProvider = SkMatrix::I();
1972 std::unique_ptr<GrClipStack> cs(new GrClipStack(kDeviceBounds, &matrixProvider, false));
1973
1974 auto addMaskRequiringClip = [&](SkScalar x, SkScalar y, SkScalar radius) {
1975 SkPath path;
1976 path.addCircle(x, y, radius);
1977 path.addCircle(x + radius / 2.f, y + radius / 2.f, radius);
1978 path.setFillType(SkPathFillType::kEvenOdd);
1979
1980 // Use AA so that clip application does not route through the stencil buffer
1981 cs->clipPath(SkMatrix::I(), path, GrAA::kYes, SkClipOp::kIntersect);
1982 };
1983
1984 auto drawRect = [&](SkRect drawBounds) {
1985 GrPaint paint;
1986 paint.setColor4f({1.f, 1.f, 1.f, 1.f});
1987 rtc->drawRect(cs.get(), std::move(paint), GrAA::kYes, SkMatrix::I(), drawBounds);
1988 };
1989
1990 auto generateMask = [&](SkRect drawBounds) {
1991 GrUniqueKey priorKey = cs->testingOnly_getLastSWMaskKey();
1992 drawRect(drawBounds);
1993 GrUniqueKey newKey = cs->testingOnly_getLastSWMaskKey();
1994 REPORTER_ASSERT(r, priorKey != newKey, "Did not generate a new SW mask key as expected");
1995 return newKey;
1996 };
1997
1998 auto verifyKeys = [&](const std::vector<GrUniqueKey>& expectedKeys,
1999 const std::vector<GrUniqueKey>& releasedKeys) {
2000 context->flush();
2001 GrProxyProvider* proxyProvider = context->priv().proxyProvider();
2002
2003#ifdef SK_DEBUG
2004 // The proxy providers key count fluctuates based on proxy lifetime, but we want to
2005 // verify the resource count, and that requires using key tags that are debug-only.
2006 SkASSERT(expectedKeys.size() > 0 || releasedKeys.size() > 0);
2007 const char* tag = expectedKeys.size() > 0 ? expectedKeys[0].tag() : releasedKeys[0].tag();
2008 GrResourceCache* cache = context->priv().getResourceCache();
2009 int numProxies = cache->countUniqueKeysWithTag(tag);
2010 REPORTER_ASSERT(r, (int) expectedKeys.size() == numProxies,
2011 "Unexpected proxy count, got %d, not %d",
2012 numProxies, (int) expectedKeys.size());
2013#endif
2014
2015 for (const auto& key : expectedKeys) {
2016 auto proxy = proxyProvider->findOrCreateProxyByUniqueKey(key);
2017 REPORTER_ASSERT(r, SkToBool(proxy), "Unable to find resource for expected mask key");
2018 }
2019 for (const auto& key : releasedKeys) {
2020 auto proxy = proxyProvider->findOrCreateProxyByUniqueKey(key);
2021 REPORTER_ASSERT(r, !SkToBool(proxy), "SW mask not released as expected");
2022 }
2023 };
2024
2025 // Creates a mask for a complex clip
2026 cs->save();
2027 addMaskRequiringClip(5.f, 5.f, 20.f);
2028 GrUniqueKey keyADepth1 = generateMask({0.f, 0.f, 20.f, 20.f});
2029 GrUniqueKey keyBDepth1 = generateMask({10.f, 10.f, 30.f, 30.f});
2030 verifyKeys({keyADepth1, keyBDepth1}, {});
2031
2032 // Creates a new mask for a new save record, but doesn't delete the old records
2033 cs->save();
2034 addMaskRequiringClip(6.f, 6.f, 15.f);
2035 GrUniqueKey keyADepth2 = generateMask({0.f, 0.f, 20.f, 20.f});
2036 GrUniqueKey keyBDepth2 = generateMask({10.f, 10.f, 30.f, 30.f});
2037 verifyKeys({keyADepth1, keyBDepth1, keyADepth2, keyBDepth2}, {});
2038
2039 // Release after modifying the current record (even if we don't draw anything)
2040 addMaskRequiringClip(4.f, 4.f, 15.f);
2041 GrUniqueKey keyCDepth2 = generateMask({4.f, 4.f, 16.f, 20.f});
2042 verifyKeys({keyADepth1, keyBDepth1, keyCDepth2}, {keyADepth2, keyBDepth2});
2043
2044 // Release after restoring an older record
2045 cs->restore();
2046 verifyKeys({keyADepth1, keyBDepth1}, {keyCDepth2});
2047
2048 // Drawing finds the old masks at depth 1 still w/o making new ones
2049 drawRect({0.f, 0.f, 20.f, 20.f});
2050 drawRect({10.f, 10.f, 30.f, 30.f});
2051 verifyKeys({keyADepth1, keyBDepth1}, {});
2052
2053 // Drawing something contained within a previous mask also does not make a new one
2054 drawRect({5.f, 5.f, 15.f, 15.f});
2055 verifyKeys({keyADepth1, keyBDepth1}, {});
2056
2057 // Release on destruction
2058 cs = nullptr;
2059 verifyKeys({}, {keyADepth1, keyBDepth1});
2060}