blob: 9c69e6e0d55b1f376d70cad568156ac9d78a676e [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"
Robert Phillips4dca8312021-07-28 15:13:20 -040023#include "src/gpu/v1/SurfaceDrawContext_v1.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
Chris Daltonb1e8f852021-06-23 13:36:51 -0600466class NoOp : public GrDrawOp {
467public:
Chris Daltonea46ef32021-07-12 15:09:06 -0600468 static NoOp* Get() {
469 static NoOp gNoOp;
Chris Daltonb1e8f852021-06-23 13:36:51 -0600470 return &gNoOp;
471 }
472private:
473 DEFINE_OP_CLASS_ID
474 NoOp() : GrDrawOp(ClassID()) {}
475 const char* name() const override { return "NoOp"; }
476 GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, GrClampType) override {
477 return GrProcessorSet::EmptySetAnalysis();
478 }
479 void onPrePrepare(GrRecordingContext*, const GrSurfaceProxyView&, GrAppliedClip*, const
480 GrDstProxyView&, GrXferBarrierFlags, GrLoadOp) override {}
481 void onPrepare(GrOpFlushState*) override {}
482 void onExecute(GrOpFlushState*, const SkRect&) override {}
483};
484
Michael Ludwiga195d102020-09-15 14:51:52 -0400485} // anonymous namespace
486
487///////////////////////////////////////////////////////////////////////////////
488// These tests use the TestCase infrastructure to define clip stacks and
489// associated expectations.
490
491// Tests that the initialized state of the clip stack is wide-open
492DEF_TEST(GrClipStack_InitialState, r) {
493 run_test_case(r, TestCase::Build("initial-state", SkIRect::MakeWH(100, 100)).finishTest());
494}
495
496// Tests that intersection of rects combine to a single element when they have the same AA type,
497// or are pixel-aligned.
498DEF_TEST(GrClipStack_RectRectAACombine, r) {
499 SkRect pixelAligned = {0, 0, 10, 10};
500 SkRect fracRect1 = pixelAligned.makeOffset(5.3f, 3.7f);
501 SkRect fracRect2 = {fracRect1.fLeft + 0.75f * fracRect1.width(),
502 fracRect1.fTop + 0.75f * fracRect1.height(),
503 fracRect1.fRight, fracRect1.fBottom};
504
505 SkRect fracIntersect;
506 SkAssertResult(fracIntersect.intersect(fracRect1, fracRect2));
507 SkRect alignedIntersect;
508 SkAssertResult(alignedIntersect.intersect(pixelAligned, fracRect1));
509
510 // Both AA combine to one element
511 run_test_case(r, TestCase::Build("aa", kDeviceBounds)
512 .actual().aa().intersect()
513 .rect(fracRect1).rect(fracRect2)
514 .finishElements()
515 .expect().aa().intersect().rect(fracIntersect).finishElements()
516 .state(GrClipStack::ClipState::kDeviceRect)
517 .finishTest());
518
519 // Both non-AA combine to one element
520 run_test_case(r, TestCase::Build("nonaa", kDeviceBounds)
521 .actual().nonAA().intersect()
522 .rect(fracRect1).rect(fracRect2)
523 .finishElements()
524 .expect().nonAA().intersect().rect(fracIntersect).finishElements()
525 .state(GrClipStack::ClipState::kDeviceRect)
526 .finishTest());
527
528 // Pixel-aligned AA and non-AA combine
529 run_test_case(r, TestCase::Build("aligned-aa+nonaa", kDeviceBounds)
530 .actual().intersect()
531 .aa().rect(pixelAligned).nonAA().rect(fracRect1)
532 .finishElements()
533 .expect().nonAA().intersect().rect(alignedIntersect).finishElements()
534 .state(GrClipStack::ClipState::kDeviceRect)
535 .finishTest());
536
537 // AA and pixel-aligned non-AA combine
538 run_test_case(r, TestCase::Build("aa+aligned-nonaa", kDeviceBounds)
539 .actual().intersect()
540 .aa().rect(fracRect1).nonAA().rect(pixelAligned)
541 .finishElements()
542 .expect().aa().intersect().rect(alignedIntersect).finishElements()
543 .state(GrClipStack::ClipState::kDeviceRect)
544 .finishTest());
545
546 // Other mixed AA modes do not combine
547 run_test_case(r, TestCase::Build("aa+nonaa", kDeviceBounds)
548 .actual().intersect()
549 .aa().rect(fracRect1).nonAA().rect(fracRect2)
550 .finishElements()
551 .expectActual()
552 .state(GrClipStack::ClipState::kComplex)
553 .finishTest());
554}
555
556// Tests that an intersection and a difference op do not combine, even if they would have if both
557// were intersection ops.
558DEF_TEST(GrClipStack_DifferenceNoCombine, r) {
559 SkRect r1 = {15.f, 14.f, 23.22f, 58.2f};
560 SkRect r2 = r1.makeOffset(5.f, 8.f);
561 SkASSERT(r1.intersects(r2));
562
563 run_test_case(r, TestCase::Build("no-combine", kDeviceBounds)
564 .actual().aa().intersect().rect(r1)
565 .difference().rect(r2)
566 .finishElements()
567 .expectActual()
568 .state(GrClipStack::ClipState::kComplex)
569 .finishTest());
570}
571
572// Tests that intersection of rects in the same coordinate space can still be combined, but do not
573// when the spaces differ.
574DEF_TEST(GrClipStack_RectRectNonAxisAligned, r) {
575 SkRect pixelAligned = {0, 0, 10, 10};
576 SkRect fracRect1 = pixelAligned.makeOffset(5.3f, 3.7f);
577 SkRect fracRect2 = {fracRect1.fLeft + 0.75f * fracRect1.width(),
578 fracRect1.fTop + 0.75f * fracRect1.height(),
579 fracRect1.fRight, fracRect1.fBottom};
580
581 SkRect fracIntersect;
582 SkAssertResult(fracIntersect.intersect(fracRect1, fracRect2));
583
584 SkMatrix lm = SkMatrix::RotateDeg(45.f);
585
586 // Both AA combine
587 run_test_case(r, TestCase::Build("aa", kDeviceBounds)
588 .actual().aa().intersect().localToDevice(lm)
589 .rect(fracRect1).rect(fracRect2)
590 .finishElements()
591 .expect().aa().intersect().localToDevice(lm)
592 .rect(fracIntersect).finishElements()
593 .state(GrClipStack::ClipState::kComplex)
594 .finishTest());
595
596 // Both non-AA combine
597 run_test_case(r, TestCase::Build("nonaa", kDeviceBounds)
598 .actual().nonAA().intersect().localToDevice(lm)
599 .rect(fracRect1).rect(fracRect2)
600 .finishElements()
601 .expect().nonAA().intersect().localToDevice(lm)
602 .rect(fracIntersect).finishElements()
603 .state(GrClipStack::ClipState::kComplex)
604 .finishTest());
605
606 // Integer-aligned coordinates under a local matrix with mixed AA don't combine, though
607 run_test_case(r, TestCase::Build("local-aa", kDeviceBounds)
608 .actual().intersect().localToDevice(lm)
609 .aa().rect(pixelAligned).nonAA().rect(fracRect1)
610 .finishElements()
611 .expectActual()
612 .state(GrClipStack::ClipState::kComplex)
613 .finishTest());
614}
615
616// Tests that intersection of two round rects can simplify to a single round rect when they have
617// the same AA type.
618DEF_TEST(GrClipStack_RRectRRectAACombine, r) {
619 SkRRect r1 = SkRRect::MakeRectXY(SkRect::MakeWH(12, 12), 2.f, 2.f);
620 SkRRect r2 = r1.makeOffset(6.f, 6.f);
621
622 SkRRect intersect = SkRRectPriv::ConservativeIntersect(r1, r2);
623 SkASSERT(!intersect.isEmpty());
624
625 // Both AA combine
626 run_test_case(r, TestCase::Build("aa", kDeviceBounds)
627 .actual().aa().intersect()
628 .rrect(r1).rrect(r2)
629 .finishElements()
630 .expect().aa().intersect().rrect(intersect).finishElements()
631 .state(GrClipStack::ClipState::kDeviceRRect)
632 .finishTest());
633
634 // Both non-AA combine
635 run_test_case(r, TestCase::Build("nonaa", kDeviceBounds)
636 .actual().nonAA().intersect()
637 .rrect(r1).rrect(r2)
638 .finishElements()
639 .expect().nonAA().intersect().rrect(intersect).finishElements()
640 .state(GrClipStack::ClipState::kDeviceRRect)
641 .finishTest());
642
643 // Mixed do not combine
644 run_test_case(r, TestCase::Build("aa+nonaa", kDeviceBounds)
645 .actual().intersect()
646 .aa().rrect(r1).nonAA().rrect(r2)
647 .finishElements()
648 .expectActual()
649 .state(GrClipStack::ClipState::kComplex)
650 .finishTest());
651
652 // Same AA state can combine in the same local coordinate space
653 SkMatrix lm = SkMatrix::RotateDeg(45.f);
654 run_test_case(r, TestCase::Build("local-aa", kDeviceBounds)
655 .actual().aa().intersect().localToDevice(lm)
656 .rrect(r1).rrect(r2)
657 .finishElements()
658 .expect().aa().intersect().localToDevice(lm)
659 .rrect(intersect).finishElements()
660 .state(GrClipStack::ClipState::kComplex)
661 .finishTest());
662 run_test_case(r, TestCase::Build("local-nonaa", kDeviceBounds)
663 .actual().nonAA().intersect().localToDevice(lm)
664 .rrect(r1).rrect(r2)
665 .finishElements()
666 .expect().nonAA().intersect().localToDevice(lm)
667 .rrect(intersect).finishElements()
668 .state(GrClipStack::ClipState::kComplex)
669 .finishTest());
670}
671
672// Tests that intersection of a round rect and rect can simplify to a new round rect or even a rect.
673DEF_TEST(GrClipStack_RectRRectCombine, r) {
674 SkRRect rrect = SkRRect::MakeRectXY({0, 0, 10, 10}, 2.f, 2.f);
675 SkRect cutTop = {-10, -10, 10, 4};
676 SkRect cutMid = {-10, 3, 10, 7};
677
678 // Rect + RRect becomes a round rect with some square corners
679 SkVector cutCorners[4] = {{2.f, 2.f}, {2.f, 2.f}, {0, 0}, {0, 0}};
680 SkRRect cutRRect;
681 cutRRect.setRectRadii({0, 0, 10, 4}, cutCorners);
682 run_test_case(r, TestCase::Build("still-rrect", kDeviceBounds)
683 .actual().intersect().aa().rrect(rrect).rect(cutTop).finishElements()
684 .expect().intersect().aa().rrect(cutRRect).finishElements()
685 .state(GrClipStack::ClipState::kDeviceRRect)
686 .finishTest());
687
688 // Rect + RRect becomes a rect
689 SkRect cutRect = {0, 3, 10, 7};
690 run_test_case(r, TestCase::Build("to-rect", kDeviceBounds)
691 .actual().intersect().aa().rrect(rrect).rect(cutMid).finishElements()
692 .expect().intersect().aa().rect(cutRect).finishElements()
693 .state(GrClipStack::ClipState::kDeviceRect)
694 .finishTest());
695
696 // But they can only combine when the intersecting shape is representable as a [r]rect.
697 cutRect = {0, 0, 1.5f, 5.f};
698 run_test_case(r, TestCase::Build("no-combine", kDeviceBounds)
699 .actual().intersect().aa().rrect(rrect).rect(cutRect).finishElements()
700 .expectActual()
701 .state(GrClipStack::ClipState::kComplex)
702 .finishTest());
703}
704
705// Tests that a rect shape is actually pre-clipped to the device bounds
706DEF_TEST(GrClipStack_RectDeviceClip, r) {
707 SkRect crossesDeviceEdge = {20.f, kDeviceBounds.fTop - 13.2f,
708 kDeviceBounds.fRight + 15.5f, 30.f};
709 SkRect insideDevice = {20.f, kDeviceBounds.fTop, kDeviceBounds.fRight, 30.f};
710
711 run_test_case(r, TestCase::Build("device-aa-rect", kDeviceBounds)
712 .actual().intersect().aa().rect(crossesDeviceEdge).finishElements()
713 .expect().intersect().aa().rect(insideDevice).finishElements()
714 .state(GrClipStack::ClipState::kDeviceRect)
715 .finishTest());
716
717 run_test_case(r, TestCase::Build("device-nonaa-rect", kDeviceBounds)
718 .actual().intersect().nonAA().rect(crossesDeviceEdge).finishElements()
719 .expect().intersect().nonAA().rect(insideDevice).finishElements()
720 .state(GrClipStack::ClipState::kDeviceRect)
721 .finishTest());
722}
723
724// Tests that other shapes' bounds are contained by the device bounds, even if their shape is not.
725DEF_TEST(GrClipStack_ShapeDeviceBoundsClip, r) {
726 SkRect crossesDeviceEdge = {20.f, kDeviceBounds.fTop - 13.2f,
727 kDeviceBounds.fRight + 15.5f, 30.f};
728
729 // RRect
730 run_test_case(r, TestCase::Build("device-rrect", kDeviceBounds)
731 .actual().intersect().aa()
732 .rrect(SkRRect::MakeRectXY(crossesDeviceEdge, 4.f, 4.f))
733 .finishElements()
734 .expectActual()
735 .state(GrClipStack::ClipState::kDeviceRRect)
736 .finishTest());
737
738 // Path
739 run_test_case(r, TestCase::Build("device-path", kDeviceBounds)
740 .actual().intersect().aa()
741 .path(make_octagon(crossesDeviceEdge))
742 .finishElements()
743 .expectActual()
744 .state(GrClipStack::ClipState::kComplex)
745 .finishTest());
746}
747
748// Tests that a simplifiable path turns into a simpler element type
749DEF_TEST(GrClipStack_PathSimplify, r) {
750 // Empty, point, and line paths -> empty
751 SkPath empty;
752 run_test_case(r, TestCase::Build("empty", kDeviceBounds)
753 .actual().path(empty).finishElements()
754 .state(GrClipStack::ClipState::kEmpty)
755 .finishTest());
756 SkPath point;
757 point.moveTo({0.f, 0.f});
758 run_test_case(r, TestCase::Build("point", kDeviceBounds)
759 .actual().path(point).finishElements()
760 .state(GrClipStack::ClipState::kEmpty)
761 .finishTest());
762
763 SkPath line;
764 line.moveTo({0.f, 0.f});
765 line.lineTo({10.f, 5.f});
766 run_test_case(r, TestCase::Build("line", kDeviceBounds)
767 .actual().path(line).finishElements()
768 .state(GrClipStack::ClipState::kEmpty)
769 .finishTest());
770
771 // Rect path -> rect element
772 SkRect rect = {0.f, 2.f, 10.f, 15.4f};
773 SkPath rectPath;
774 rectPath.addRect(rect);
775 run_test_case(r, TestCase::Build("rect", kDeviceBounds)
776 .actual().path(rectPath).finishElements()
777 .expect().rect(rect).finishElements()
778 .state(GrClipStack::ClipState::kDeviceRect)
779 .finishTest());
780
781 // Oval path -> rrect element
782 SkPath ovalPath;
783 ovalPath.addOval(rect);
784 run_test_case(r, TestCase::Build("oval", kDeviceBounds)
785 .actual().path(ovalPath).finishElements()
786 .expect().rrect(SkRRect::MakeOval(rect)).finishElements()
787 .state(GrClipStack::ClipState::kDeviceRRect)
788 .finishTest());
789
790 // RRect path -> rrect element
791 SkRRect rrect = SkRRect::MakeRectXY(rect, 2.f, 2.f);
792 SkPath rrectPath;
793 rrectPath.addRRect(rrect);
794 run_test_case(r, TestCase::Build("rrect", kDeviceBounds)
795 .actual().path(rrectPath).finishElements()
796 .expect().rrect(rrect).finishElements()
797 .state(GrClipStack::ClipState::kDeviceRRect)
798 .finishTest());
799}
800
801// Tests that repeated identical clip operations are idempotent
802DEF_TEST(GrClipStack_RepeatElement, r) {
803 // Same rect
804 SkRect rect = {5.3f, 62.f, 20.f, 85.f};
805 run_test_case(r, TestCase::Build("same-rects", kDeviceBounds)
806 .actual().rect(rect).rect(rect).rect(rect).finishElements()
807 .expect().rect(rect).finishElements()
808 .state(GrClipStack::ClipState::kDeviceRect)
809 .finishTest());
810 SkMatrix lm;
811 lm.setRotate(30.f, rect.centerX(), rect.centerY());
812 run_test_case(r, TestCase::Build("same-local-rects", kDeviceBounds)
813 .actual().localToDevice(lm).rect(rect).rect(rect).rect(rect)
814 .finishElements()
815 .expect().localToDevice(lm).rect(rect).finishElements()
816 .state(GrClipStack::ClipState::kComplex)
817 .finishTest());
818
819 // Same rrect
820 SkRRect rrect = SkRRect::MakeRectXY(rect, 5.f, 2.5f);
821 run_test_case(r, TestCase::Build("same-rrects", kDeviceBounds)
822 .actual().rrect(rrect).rrect(rrect).rrect(rrect).finishElements()
823 .expect().rrect(rrect).finishElements()
824 .state(GrClipStack::ClipState::kDeviceRRect)
825 .finishTest());
826 run_test_case(r, TestCase::Build("same-local-rrects", kDeviceBounds)
827 .actual().localToDevice(lm).rrect(rrect).rrect(rrect).rrect(rrect)
828 .finishElements()
829 .expect().localToDevice(lm).rrect(rrect).finishElements()
830 .state(GrClipStack::ClipState::kComplex)
831 .finishTest());
832
833 // Same convex path, by ==
834 run_test_case(r, TestCase::Build("same-convex", kDeviceBounds)
835 .actual().path(make_octagon(rect)).path(make_octagon(rect))
836 .finishElements()
837 .expect().path(make_octagon(rect)).finishElements()
838 .state(GrClipStack::ClipState::kComplex)
839 .finishTest());
840 run_test_case(r, TestCase::Build("same-local-convex", kDeviceBounds)
841 .actual().localToDevice(lm)
842 .path(make_octagon(rect)).path(make_octagon(rect))
843 .finishElements()
844 .expect().localToDevice(lm).path(make_octagon(rect))
845 .finishElements()
846 .state(GrClipStack::ClipState::kComplex)
847 .finishTest());
848
849 // Same complicated path by gen-id but not ==
850 SkPath path; // an hour glass
851 path.moveTo({0.f, 0.f});
852 path.lineTo({20.f, 20.f});
853 path.lineTo({0.f, 20.f});
854 path.lineTo({20.f, 0.f});
855 path.close();
856
857 run_test_case(r, TestCase::Build("same-path", kDeviceBounds)
858 .actual().path(path).path(path).path(path).finishElements()
859 .expect().path(path).finishElements()
860 .state(GrClipStack::ClipState::kComplex)
861 .finishTest());
862 run_test_case(r, TestCase::Build("same-local-path", kDeviceBounds)
863 .actual().localToDevice(lm)
864 .path(path).path(path).path(path).finishElements()
865 .expect().localToDevice(lm).path(path)
866 .finishElements()
867 .state(GrClipStack::ClipState::kComplex)
868 .finishTest());
869}
870
871// Tests that inverse-filled paths are canonicalized to a regular fill and a swapped clip op
872DEF_TEST(GrClipStack_InverseFilledPath, r) {
873 SkRect rect = {0.f, 0.f, 16.f, 17.f};
874 SkPath rectPath;
875 rectPath.addRect(rect);
876
877 SkPath inverseRectPath = rectPath;
878 inverseRectPath.toggleInverseFillType();
879
880 SkPath complexPath = make_octagon(rect);
881 SkPath inverseComplexPath = complexPath;
882 inverseComplexPath.toggleInverseFillType();
883
884 // Inverse filled rect + intersect -> diff rect
885 run_test_case(r, TestCase::Build("inverse-rect-intersect", kDeviceBounds)
886 .actual().aa().intersect().path(inverseRectPath).finishElements()
887 .expect().aa().difference().rect(rect).finishElements()
888 .state(GrClipStack::ClipState::kComplex)
889 .finishTest());
890
891 // Inverse filled rect + difference -> int. rect
892 run_test_case(r, TestCase::Build("inverse-rect-difference", kDeviceBounds)
893 .actual().aa().difference().path(inverseRectPath).finishElements()
894 .expect().aa().intersect().rect(rect).finishElements()
895 .state(GrClipStack::ClipState::kDeviceRect)
896 .finishTest());
897
898 // Inverse filled path + intersect -> diff path
899 run_test_case(r, TestCase::Build("inverse-path-intersect", kDeviceBounds)
900 .actual().aa().intersect().path(inverseComplexPath).finishElements()
901 .expect().aa().difference().path(complexPath).finishElements()
902 .state(GrClipStack::ClipState::kComplex)
903 .finishTest());
904
905 // Inverse filled path + difference -> int. path
906 run_test_case(r, TestCase::Build("inverse-path-difference", kDeviceBounds)
907 .actual().aa().difference().path(inverseComplexPath).finishElements()
908 .expect().aa().intersect().path(complexPath).finishElements()
909 .state(GrClipStack::ClipState::kComplex)
910 .finishTest());
911}
912
913// Tests that clip operations that are offscreen either make the clip empty or stay wide open
914DEF_TEST(GrClipStack_Offscreen, r) {
915 SkRect offscreenRect = {kDeviceBounds.fRight + 10.f, kDeviceBounds.fTop + 20.f,
916 kDeviceBounds.fRight + 40.f, kDeviceBounds.fTop + 60.f};
917 SkASSERT(!offscreenRect.intersects(SkRect::Make(kDeviceBounds)));
918
919 SkRRect offscreenRRect = SkRRect::MakeRectXY(offscreenRect, 5.f, 5.f);
920 SkPath offscreenPath = make_octagon(offscreenRect);
921
922 // Intersect -> empty
923 run_test_case(r, TestCase::Build("intersect-combo", kDeviceBounds)
924 .actual().aa().intersect()
925 .rect(offscreenRect)
926 .rrect(offscreenRRect)
927 .path(offscreenPath)
928 .finishElements()
929 .state(GrClipStack::ClipState::kEmpty)
930 .finishTest());
931 run_test_case(r, TestCase::Build("intersect-rect", kDeviceBounds)
932 .actual().aa().intersect()
933 .rect(offscreenRect)
934 .finishElements()
935 .state(GrClipStack::ClipState::kEmpty)
936 .finishTest());
937 run_test_case(r, TestCase::Build("intersect-rrect", kDeviceBounds)
938 .actual().aa().intersect()
939 .rrect(offscreenRRect)
940 .finishElements()
941 .state(GrClipStack::ClipState::kEmpty)
942 .finishTest());
943 run_test_case(r, TestCase::Build("intersect-path", kDeviceBounds)
944 .actual().aa().intersect()
945 .path(offscreenPath)
946 .finishElements()
947 .state(GrClipStack::ClipState::kEmpty)
948 .finishTest());
949
950 // Difference -> wide open
951 run_test_case(r, TestCase::Build("difference-combo", kDeviceBounds)
952 .actual().aa().difference()
953 .rect(offscreenRect)
954 .rrect(offscreenRRect)
955 .path(offscreenPath)
956 .finishElements()
957 .state(GrClipStack::ClipState::kWideOpen)
958 .finishTest());
959 run_test_case(r, TestCase::Build("difference-rect", kDeviceBounds)
960 .actual().aa().difference()
961 .rect(offscreenRect)
962 .finishElements()
963 .state(GrClipStack::ClipState::kWideOpen)
964 .finishTest());
965 run_test_case(r, TestCase::Build("difference-rrect", kDeviceBounds)
966 .actual().aa().difference()
967 .rrect(offscreenRRect)
968 .finishElements()
969 .state(GrClipStack::ClipState::kWideOpen)
970 .finishTest());
971 run_test_case(r, TestCase::Build("difference-path", kDeviceBounds)
972 .actual().aa().difference()
973 .path(offscreenPath)
974 .finishElements()
975 .state(GrClipStack::ClipState::kWideOpen)
976 .finishTest());
977}
978
979// Tests that an empty shape updates the clip state directly without needing an element
980DEF_TEST(GrClipStack_EmptyShape, r) {
981 // Intersect -> empty
982 run_test_case(r, TestCase::Build("empty-intersect", kDeviceBounds)
983 .actual().intersect().rect(SkRect::MakeEmpty()).finishElements()
984 .state(GrClipStack::ClipState::kEmpty)
985 .finishTest());
986
987 // Difference -> no-op
988 run_test_case(r, TestCase::Build("empty-difference", kDeviceBounds)
989 .actual().difference().rect(SkRect::MakeEmpty()).finishElements()
990 .state(GrClipStack::ClipState::kWideOpen)
991 .finishTest());
992
993 SkRRect rrect = SkRRect::MakeRectXY({4.f, 10.f, 16.f, 32.f}, 2.f, 2.f);
994 run_test_case(r, TestCase::Build("noop-difference", kDeviceBounds)
995 .actual().difference().rrect(rrect).rect(SkRect::MakeEmpty())
996 .finishElements()
997 .expect().difference().rrect(rrect).finishElements()
998 .state(GrClipStack::ClipState::kComplex)
999 .finishTest());
1000}
1001
1002// Tests that sufficiently large difference operations can shrink the conservative bounds
1003DEF_TEST(GrClipStack_DifferenceBounds, r) {
1004 SkRect rightSide = {50.f, -10.f, 2.f * kDeviceBounds.fRight, kDeviceBounds.fBottom + 10.f};
1005 SkRect clipped = rightSide;
1006 SkAssertResult(clipped.intersect(SkRect::Make(kDeviceBounds)));
1007
1008 run_test_case(r, TestCase::Build("difference-cut", kDeviceBounds)
1009 .actual().nonAA().difference().rect(rightSide).finishElements()
1010 .expect().nonAA().difference().rect(clipped).finishElements()
1011 .state(GrClipStack::ClipState::kComplex)
1012 .finishTest());
1013}
1014
1015// Tests that intersections can combine even if there's a difference operation in the middle
1016DEF_TEST(GrClipStack_NoDifferenceInterference, r) {
1017 SkRect intR1 = {0.f, 0.f, 30.f, 30.f};
1018 SkRect intR2 = {15.f, 15.f, 45.f, 45.f};
1019 SkRect intCombo = {15.f, 15.f, 30.f, 30.f};
1020 SkRect diff = {20.f, 6.f, 50.f, 50.f};
1021
1022 run_test_case(r, TestCase::Build("cross-diff-combine", kDeviceBounds)
1023 .actual().rect(intR1, GrAA::kYes, SkClipOp::kIntersect)
1024 .rect(diff, GrAA::kYes, SkClipOp::kDifference)
1025 .rect(intR2, GrAA::kYes, SkClipOp::kIntersect)
1026 .finishElements()
1027 .expect().rect(intCombo, GrAA::kYes, SkClipOp::kIntersect)
1028 .rect(diff, GrAA::kYes, SkClipOp::kDifference)
1029 .finishElements()
1030 .state(GrClipStack::ClipState::kComplex)
1031 .finishTest());
1032}
1033
1034// Tests that multiple path operations are all recorded, but not otherwise consolidated
1035DEF_TEST(GrClipStack_MultiplePaths, r) {
1036 // Chosen to be greater than the number of inline-allocated elements and save records of the
1037 // GrClipStack so that we test heap allocation as well.
1038 static constexpr int kNumOps = 16;
1039
1040 auto b = TestCase::Build("many-paths-difference", kDeviceBounds);
1041 SkRect d = {0.f, 0.f, 12.f, 12.f};
1042 for (int i = 0; i < kNumOps; ++i) {
1043 b.actual().path(make_octagon(d), GrAA::kNo, SkClipOp::kDifference);
1044
1045 d.offset(15.f, 0.f);
1046 if (d.fRight > kDeviceBounds.fRight) {
1047 d.fLeft = 0.f;
1048 d.fRight = 12.f;
1049 d.offset(0.f, 15.f);
1050 }
1051 }
1052
1053 run_test_case(r, b.expectActual()
1054 .state(GrClipStack::ClipState::kComplex)
1055 .finishTest());
1056
1057 b = TestCase::Build("many-paths-intersect", kDeviceBounds);
1058 d = {0.f, 0.f, 12.f, 12.f};
1059 for (int i = 0; i < kNumOps; ++i) {
1060 b.actual().path(make_octagon(d), GrAA::kYes, SkClipOp::kIntersect);
1061 d.offset(0.01f, 0.01f);
1062 }
1063
1064 run_test_case(r, b.expectActual()
1065 .state(GrClipStack::ClipState::kComplex)
1066 .finishTest());
1067}
1068
1069// Tests that a single rect is treated as kDeviceRect state when it's axis-aligned and intersect.
1070DEF_TEST(GrClipStack_DeviceRect, r) {
1071 // Axis-aligned + intersect -> kDeviceRect
1072 SkRect rect = {0, 0, 20, 20};
1073 run_test_case(r, TestCase::Build("device-rect", kDeviceBounds)
1074 .actual().intersect().aa().rect(rect).finishElements()
1075 .expectActual()
1076 .state(GrClipStack::ClipState::kDeviceRect)
1077 .finishTest());
1078
1079 // Not axis-aligned -> kComplex
1080 SkMatrix lm = SkMatrix::RotateDeg(15.f);
1081 run_test_case(r, TestCase::Build("unaligned-rect", kDeviceBounds)
1082 .actual().localToDevice(lm).intersect().aa().rect(rect)
1083 .finishElements()
1084 .expectActual()
1085 .state(GrClipStack::ClipState::kComplex)
1086 .finishTest());
1087
1088 // Not intersect -> kComplex
1089 run_test_case(r, TestCase::Build("diff-rect", kDeviceBounds)
1090 .actual().difference().aa().rect(rect).finishElements()
1091 .expectActual()
1092 .state(GrClipStack::ClipState::kComplex)
1093 .finishTest());
1094}
1095
1096// Tests that a single rrect is treated as kDeviceRRect state when it's axis-aligned and intersect.
1097DEF_TEST(GrClipStack_DeviceRRect, r) {
1098 // Axis-aligned + intersect -> kDeviceRRect
1099 SkRect rect = {0, 0, 20, 20};
1100 SkRRect rrect = SkRRect::MakeRectXY(rect, 5.f, 5.f);
1101 run_test_case(r, TestCase::Build("device-rrect", kDeviceBounds)
1102 .actual().intersect().aa().rrect(rrect).finishElements()
1103 .expectActual()
1104 .state(GrClipStack::ClipState::kDeviceRRect)
1105 .finishTest());
1106
1107 // Not axis-aligned -> kComplex
1108 SkMatrix lm = SkMatrix::RotateDeg(15.f);
1109 run_test_case(r, TestCase::Build("unaligned-rrect", kDeviceBounds)
1110 .actual().localToDevice(lm).intersect().aa().rrect(rrect)
1111 .finishElements()
1112 .expectActual()
1113 .state(GrClipStack::ClipState::kComplex)
1114 .finishTest());
1115
1116 // Not intersect -> kComplex
1117 run_test_case(r, TestCase::Build("diff-rrect", kDeviceBounds)
1118 .actual().difference().aa().rrect(rrect).finishElements()
1119 .expectActual()
1120 .state(GrClipStack::ClipState::kComplex)
1121 .finishTest());
1122}
1123
1124// Tests that scale+translate matrices are pre-applied to rects and rrects, which also then allows
1125// elements with different scale+translate matrices to be consolidated as if they were in the same
1126// coordinate space.
1127DEF_TEST(GrClipStack_ScaleTranslate, r) {
1128 SkMatrix lm = SkMatrix::Scale(2.f, 4.f);
1129 lm.postTranslate(15.5f, 14.3f);
Michael Ludwigd30e9ef2020-09-28 12:03:01 -04001130 SkASSERT(lm.preservesAxisAlignment() && lm.isScaleTranslate());
Michael Ludwiga195d102020-09-15 14:51:52 -04001131
1132 // Rect -> matrix is applied up front
1133 SkRect rect = {0.f, 0.f, 10.f, 10.f};
1134 run_test_case(r, TestCase::Build("st+rect", kDeviceBounds)
1135 .actual().rect(rect, lm, GrAA::kYes, SkClipOp::kIntersect)
1136 .finishElements()
1137 .expect().rect(lm.mapRect(rect), GrAA::kYes, SkClipOp::kIntersect)
1138 .finishElements()
1139 .state(GrClipStack::ClipState::kDeviceRect)
1140 .finishTest());
1141
1142 // RRect -> matrix is applied up front
1143 SkRRect localRRect = SkRRect::MakeRectXY(rect, 2.f, 2.f);
1144 SkRRect deviceRRect;
1145 SkAssertResult(localRRect.transform(lm, &deviceRRect));
1146 run_test_case(r, TestCase::Build("st+rrect", kDeviceBounds)
1147 .actual().rrect(localRRect, lm, GrAA::kYes, SkClipOp::kIntersect)
1148 .finishElements()
1149 .expect().rrect(deviceRRect, GrAA::kYes, SkClipOp::kIntersect)
1150 .finishElements()
1151 .state(GrClipStack::ClipState::kDeviceRRect)
1152 .finishTest());
1153
1154 // Path -> matrix is NOT applied
1155 run_test_case(r, TestCase::Build("st+path", kDeviceBounds)
1156 .actual().intersect().localToDevice(lm).path(make_octagon(rect))
1157 .finishElements()
1158 .expectActual()
1159 .state(GrClipStack::ClipState::kComplex)
1160 .finishTest());
1161}
1162
Michael Ludwigd30e9ef2020-09-28 12:03:01 -04001163// Tests that rect-stays-rect matrices that are not scale+translate matrices are pre-applied.
1164DEF_TEST(GrClipStack_PreserveAxisAlignment, r) {
1165 SkMatrix lm = SkMatrix::RotateDeg(90.f);
1166 lm.postTranslate(15.5f, 14.3f);
1167 SkASSERT(lm.preservesAxisAlignment() && !lm.isScaleTranslate());
1168
1169 // Rect -> matrix is applied up front
1170 SkRect rect = {0.f, 0.f, 10.f, 10.f};
1171 run_test_case(r, TestCase::Build("r90+rect", kDeviceBounds)
1172 .actual().rect(rect, lm, GrAA::kYes, SkClipOp::kIntersect)
1173 .finishElements()
1174 .expect().rect(lm.mapRect(rect), GrAA::kYes, SkClipOp::kIntersect)
1175 .finishElements()
1176 .state(GrClipStack::ClipState::kDeviceRect)
1177 .finishTest());
1178
1179 // RRect -> matrix is applied up front
1180 SkRRect localRRect = SkRRect::MakeRectXY(rect, 2.f, 2.f);
1181 SkRRect deviceRRect;
1182 SkAssertResult(localRRect.transform(lm, &deviceRRect));
1183 run_test_case(r, TestCase::Build("r90+rrect", kDeviceBounds)
1184 .actual().rrect(localRRect, lm, GrAA::kYes, SkClipOp::kIntersect)
1185 .finishElements()
1186 .expect().rrect(deviceRRect, GrAA::kYes, SkClipOp::kIntersect)
1187 .finishElements()
1188 .state(GrClipStack::ClipState::kDeviceRRect)
1189 .finishTest());
1190
1191 // Path -> matrix is NOT applied
1192 run_test_case(r, TestCase::Build("r90+path", kDeviceBounds)
1193 .actual().intersect().localToDevice(lm).path(make_octagon(rect))
1194 .finishElements()
1195 .expectActual()
1196 .state(GrClipStack::ClipState::kComplex)
1197 .finishTest());
1198}
1199
Michael Ludwiga195d102020-09-15 14:51:52 -04001200// Tests that a convex path element can contain a rect or round rect, allowing the stack to be
1201// simplified
1202DEF_TEST(GrClipStack_ConvexPathContains, r) {
1203 SkRect rect = {15.f, 15.f, 30.f, 30.f};
1204 SkRRect rrect = SkRRect::MakeRectXY(rect, 5.f, 5.f);
1205 SkPath bigPath = make_octagon(rect.makeOutset(10.f, 10.f), 5.f, 5.f);
1206
1207 // Intersect -> path element isn't kept
1208 run_test_case(r, TestCase::Build("convex+rect-intersect", kDeviceBounds)
1209 .actual().aa().intersect().rect(rect).path(bigPath).finishElements()
1210 .expect().aa().intersect().rect(rect).finishElements()
1211 .state(GrClipStack::ClipState::kDeviceRect)
1212 .finishTest());
1213 run_test_case(r, TestCase::Build("convex+rrect-intersect", kDeviceBounds)
1214 .actual().aa().intersect().rrect(rrect).path(bigPath).finishElements()
1215 .expect().aa().intersect().rrect(rrect).finishElements()
1216 .state(GrClipStack::ClipState::kDeviceRRect)
1217 .finishTest());
1218
1219 // Difference -> path element is the only one left
1220 run_test_case(r, TestCase::Build("convex+rect-difference", kDeviceBounds)
1221 .actual().aa().difference().rect(rect).path(bigPath).finishElements()
1222 .expect().aa().difference().path(bigPath).finishElements()
1223 .state(GrClipStack::ClipState::kComplex)
1224 .finishTest());
1225 run_test_case(r, TestCase::Build("convex+rrect-difference", kDeviceBounds)
1226 .actual().aa().difference().rrect(rrect).path(bigPath)
1227 .finishElements()
1228 .expect().aa().difference().path(bigPath).finishElements()
1229 .state(GrClipStack::ClipState::kComplex)
1230 .finishTest());
1231
1232 // Intersect small shape + difference big path -> empty
1233 run_test_case(r, TestCase::Build("convex-diff+rect-int", kDeviceBounds)
1234 .actual().aa().intersect().rect(rect)
1235 .difference().path(bigPath).finishElements()
1236 .state(GrClipStack::ClipState::kEmpty)
1237 .finishTest());
1238 run_test_case(r, TestCase::Build("convex-diff+rrect-int", kDeviceBounds)
1239 .actual().aa().intersect().rrect(rrect)
1240 .difference().path(bigPath).finishElements()
1241 .state(GrClipStack::ClipState::kEmpty)
1242 .finishTest());
1243
1244 // Diff small shape + intersect big path -> both
1245 run_test_case(r, TestCase::Build("convex-int+rect-diff", kDeviceBounds)
1246 .actual().aa().intersect().path(bigPath).difference().rect(rect)
1247 .finishElements()
1248 .expectActual()
1249 .state(GrClipStack::ClipState::kComplex)
1250 .finishTest());
1251 run_test_case(r, TestCase::Build("convex-int+rrect-diff", kDeviceBounds)
1252 .actual().aa().intersect().path(bigPath).difference().rrect(rrect)
1253 .finishElements()
1254 .expectActual()
1255 .state(GrClipStack::ClipState::kComplex)
1256 .finishTest());
1257}
1258
1259// Tests that rects/rrects in different coordinate spaces can be consolidated when one is fully
1260// contained by the other.
1261DEF_TEST(GrClipStack_NonAxisAlignedContains, r) {
1262 SkMatrix lm1 = SkMatrix::RotateDeg(45.f);
1263 SkRect bigR = {-20.f, -20.f, 20.f, 20.f};
1264 SkRRect bigRR = SkRRect::MakeRectXY(bigR, 1.f, 1.f);
1265
1266 SkMatrix lm2 = SkMatrix::RotateDeg(-45.f);
1267 SkRect smR = {-10.f, -10.f, 10.f, 10.f};
1268 SkRRect smRR = SkRRect::MakeRectXY(smR, 1.f, 1.f);
1269
1270 // I+I should select the smaller 2nd shape (r2 or rr2)
1271 run_test_case(r, TestCase::Build("rect-rect-ii", kDeviceBounds)
1272 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1273 .rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1274 .finishElements()
1275 .expect().rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1276 .finishElements()
1277 .state(GrClipStack::ClipState::kComplex)
1278 .finishTest());
1279 run_test_case(r, TestCase::Build("rrect-rrect-ii", kDeviceBounds)
1280 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1281 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1282 .finishElements()
1283 .expect().rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1284 .finishElements()
1285 .state(GrClipStack::ClipState::kComplex)
1286 .finishTest());
1287 run_test_case(r, TestCase::Build("rect-rrect-ii", kDeviceBounds)
1288 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1289 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1290 .finishElements()
1291 .expect().rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1292 .finishElements()
1293 .state(GrClipStack::ClipState::kComplex)
1294 .finishTest());
1295 run_test_case(r, TestCase::Build("rrect-rect-ii", kDeviceBounds)
1296 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1297 .rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1298 .finishElements()
1299 .expect().rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1300 .finishElements()
1301 .state(GrClipStack::ClipState::kComplex)
1302 .finishTest());
1303
1304 // D+D should select the larger shape (r1 or rr1)
1305 run_test_case(r, TestCase::Build("rect-rect-dd", kDeviceBounds)
1306 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference)
1307 .rect(smR, lm2, GrAA::kYes, SkClipOp::kDifference)
1308 .finishElements()
1309 .expect().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference)
1310 .finishElements()
1311 .state(GrClipStack::ClipState::kComplex)
1312 .finishTest());
1313 run_test_case(r, TestCase::Build("rrect-rrect-dd", kDeviceBounds)
1314 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference)
1315 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kDifference)
1316 .finishElements()
1317 .expect().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference)
1318 .finishElements()
1319 .state(GrClipStack::ClipState::kComplex)
1320 .finishTest());
1321 run_test_case(r, TestCase::Build("rect-rrect-dd", kDeviceBounds)
1322 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference)
1323 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kDifference)
1324 .finishElements()
1325 .expect().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference)
1326 .finishElements()
1327 .state(GrClipStack::ClipState::kComplex)
1328 .finishTest());
1329 run_test_case(r, TestCase::Build("rrect-rect-dd", kDeviceBounds)
1330 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference)
1331 .rect(smR, lm2, GrAA::kYes, SkClipOp::kDifference)
1332 .finishElements()
1333 .expect().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference)
1334 .finishElements()
1335 .state(GrClipStack::ClipState::kComplex)
1336 .finishTest());
1337
1338 // D(1)+I(2) should result in empty
1339 run_test_case(r, TestCase::Build("rectD-rectI", kDeviceBounds)
1340 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference)
1341 .rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1342 .finishElements()
1343 .state(GrClipStack::ClipState::kEmpty)
1344 .finishTest());
1345 run_test_case(r, TestCase::Build("rrectD-rrectI", kDeviceBounds)
1346 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference)
1347 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1348 .finishElements()
1349 .state(GrClipStack::ClipState::kEmpty)
1350 .finishTest());
1351 run_test_case(r, TestCase::Build("rectD-rrectI", kDeviceBounds)
1352 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kDifference)
1353 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1354 .finishElements()
1355 .state(GrClipStack::ClipState::kEmpty)
1356 .finishTest());
1357 run_test_case(r, TestCase::Build("rrectD-rectI", kDeviceBounds)
1358 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kDifference)
1359 .rect(smR, lm2, GrAA::kYes, SkClipOp::kIntersect)
1360 .finishElements()
1361 .state(GrClipStack::ClipState::kEmpty)
1362 .finishTest());
1363
1364 // I(1)+D(2) should result in both shapes
1365 run_test_case(r, TestCase::Build("rectI+rectD", kDeviceBounds)
1366 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1367 .rect(smR, lm2, GrAA::kYes, SkClipOp::kDifference)
1368 .finishElements()
1369 .expectActual()
1370 .state(GrClipStack::ClipState::kComplex)
1371 .finishTest());
1372 run_test_case(r, TestCase::Build("rrectI+rrectD", kDeviceBounds)
1373 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1374 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kDifference)
1375 .finishElements()
1376 .expectActual()
1377 .state(GrClipStack::ClipState::kComplex)
1378 .finishTest());
1379 run_test_case(r, TestCase::Build("rrectI+rectD", kDeviceBounds)
1380 .actual().rrect(bigRR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1381 .rect(smR, lm2, GrAA::kYes, SkClipOp::kDifference)
1382 .finishElements()
1383 .expectActual()
1384 .state(GrClipStack::ClipState::kComplex)
1385 .finishTest());
1386 run_test_case(r, TestCase::Build("rectI+rrectD", kDeviceBounds)
1387 .actual().rect(bigR, lm1, GrAA::kYes, SkClipOp::kIntersect)
1388 .rrect(smRR, lm2, GrAA::kYes, SkClipOp::kDifference)
1389 .finishElements()
1390 .expectActual()
1391 .state(GrClipStack::ClipState::kComplex)
1392 .finishTest());
1393}
1394
1395// Tests that shapes with mixed AA state that contain each other can still be consolidated,
1396// unless they are too close to the edge and non-AA snapping can't be predicted
1397DEF_TEST(GrClipStack_MixedAAContains, r) {
1398 SkMatrix lm1 = SkMatrix::RotateDeg(45.f);
1399 SkRect r1 = {-20.f, -20.f, 20.f, 20.f};
1400
1401 SkMatrix lm2 = SkMatrix::RotateDeg(-45.f);
1402 SkRect r2Safe = {-10.f, -10.f, 10.f, 10.f};
1403 SkRect r2Unsafe = {-19.5f, -19.5f, 19.5f, 19.5f};
1404
1405 // Non-AA sufficiently inside AA element can discard the outer AA element
1406 run_test_case(r, TestCase::Build("mixed-outeraa-combine", kDeviceBounds)
1407 .actual().rect(r1, lm1, GrAA::kYes, SkClipOp::kIntersect)
1408 .rect(r2Safe, lm2, GrAA::kNo, SkClipOp::kIntersect)
1409 .finishElements()
1410 .expect().rect(r2Safe, lm2, GrAA::kNo, SkClipOp::kIntersect)
1411 .finishElements()
1412 .state(GrClipStack::ClipState::kComplex)
1413 .finishTest());
1414 // Vice versa
1415 run_test_case(r, TestCase::Build("mixed-inneraa-combine", kDeviceBounds)
1416 .actual().rect(r1, lm1, GrAA::kNo, SkClipOp::kIntersect)
1417 .rect(r2Safe, lm2, GrAA::kYes, SkClipOp::kIntersect)
1418 .finishElements()
1419 .expect().rect(r2Safe, lm2, GrAA::kYes, SkClipOp::kIntersect)
1420 .finishElements()
1421 .state(GrClipStack::ClipState::kComplex)
1422 .finishTest());
1423
1424 // Non-AA too close to AA edges keeps both
1425 run_test_case(r, TestCase::Build("mixed-outeraa-nocombine", kDeviceBounds)
1426 .actual().rect(r1, lm1, GrAA::kYes, SkClipOp::kIntersect)
1427 .rect(r2Unsafe, lm2, GrAA::kNo, SkClipOp::kIntersect)
1428 .finishElements()
1429 .expectActual()
1430 .state(GrClipStack::ClipState::kComplex)
1431 .finishTest());
1432 run_test_case(r, TestCase::Build("mixed-inneraa-nocombine", kDeviceBounds)
1433 .actual().rect(r1, lm1, GrAA::kNo, SkClipOp::kIntersect)
1434 .rect(r2Unsafe, lm2, GrAA::kYes, SkClipOp::kIntersect)
1435 .finishElements()
1436 .expectActual()
1437 .state(GrClipStack::ClipState::kComplex)
1438 .finishTest());
1439}
1440
1441// Tests that a shape that contains the device bounds updates the clip state directly
1442DEF_TEST(GrClipStack_ShapeContainsDevice, r) {
1443 SkRect rect = SkRect::Make(kDeviceBounds).makeOutset(10.f, 10.f);
1444 SkRRect rrect = SkRRect::MakeRectXY(rect, 10.f, 10.f);
1445 SkPath convex = make_octagon(rect, 10.f, 10.f);
1446
1447 // Intersect -> no-op
1448 run_test_case(r, TestCase::Build("rect-intersect", kDeviceBounds)
1449 .actual().intersect().rect(rect).finishElements()
1450 .state(GrClipStack::ClipState::kWideOpen)
1451 .finishTest());
1452 run_test_case(r, TestCase::Build("rrect-intersect", kDeviceBounds)
1453 .actual().intersect().rrect(rrect).finishElements()
1454 .state(GrClipStack::ClipState::kWideOpen)
1455 .finishTest());
1456 run_test_case(r, TestCase::Build("convex-intersect", kDeviceBounds)
1457 .actual().intersect().path(convex).finishElements()
1458 .state(GrClipStack::ClipState::kWideOpen)
1459 .finishTest());
1460
1461 // Difference -> empty
1462 run_test_case(r, TestCase::Build("rect-difference", kDeviceBounds)
1463 .actual().difference().rect(rect).finishElements()
1464 .state(GrClipStack::ClipState::kEmpty)
1465 .finishTest());
1466 run_test_case(r, TestCase::Build("rrect-difference", kDeviceBounds)
1467 .actual().difference().rrect(rrect).finishElements()
1468 .state(GrClipStack::ClipState::kEmpty)
1469 .finishTest());
1470 run_test_case(r, TestCase::Build("convex-difference", kDeviceBounds)
1471 .actual().difference().path(convex).finishElements()
1472 .state(GrClipStack::ClipState::kEmpty)
1473 .finishTest());
1474}
1475
1476// Tests that shapes that do not overlap make for an empty clip (when intersecting), pick just the
1477// intersecting op (when mixed), or are all kept (when diff'ing).
1478DEF_TEST(GrClipStack_DisjointShapes, r) {
1479 SkRect rt = {10.f, 10.f, 20.f, 20.f};
1480 SkRRect rr = SkRRect::MakeOval(rt.makeOffset({20.f, 0.f}));
1481 SkPath p = make_octagon(rt.makeOffset({0.f, 20.f}));
1482
1483 // I+I
1484 run_test_case(r, TestCase::Build("iii", kDeviceBounds)
1485 .actual().aa().intersect().rect(rt).rrect(rr).path(p).finishElements()
1486 .state(GrClipStack::ClipState::kEmpty)
1487 .finishTest());
1488
1489 // D+D
1490 run_test_case(r, TestCase::Build("ddd", kDeviceBounds)
1491 .actual().nonAA().difference().rect(rt).rrect(rr).path(p)
1492 .finishElements()
1493 .expectActual()
1494 .state(GrClipStack::ClipState::kComplex)
1495 .finishTest());
1496
1497 // I+D from rect
1498 run_test_case(r, TestCase::Build("idd", kDeviceBounds)
1499 .actual().aa().intersect().rect(rt)
1500 .nonAA().difference().rrect(rr).path(p)
1501 .finishElements()
1502 .expect().aa().intersect().rect(rt).finishElements()
1503 .state(GrClipStack::ClipState::kDeviceRect)
1504 .finishTest());
1505
1506 // I+D from rrect
1507 run_test_case(r, TestCase::Build("did", kDeviceBounds)
1508 .actual().aa().intersect().rrect(rr)
1509 .nonAA().difference().rect(rt).path(p)
1510 .finishElements()
1511 .expect().aa().intersect().rrect(rr).finishElements()
1512 .state(GrClipStack::ClipState::kDeviceRRect)
1513 .finishTest());
1514
1515 // I+D from path
1516 run_test_case(r, TestCase::Build("ddi", kDeviceBounds)
1517 .actual().aa().intersect().path(p)
1518 .nonAA().difference().rect(rt).rrect(rr)
1519 .finishElements()
1520 .expect().aa().intersect().path(p).finishElements()
1521 .state(GrClipStack::ClipState::kComplex)
1522 .finishTest());
1523}
1524
1525DEF_TEST(GrClipStack_ComplexClip, r) {
1526 static constexpr float kN = 10.f;
1527 static constexpr float kR = kN / 3.f;
1528
1529 // 4 rectangles that overlap by kN x 2kN (horiz), 2kN x kN (vert), or kN x kN (diagonal)
1530 static const SkRect kTL = {0.f, 0.f, 2.f * kN, 2.f * kN};
1531 static const SkRect kTR = {kN, 0.f, 3.f * kN, 2.f * kN};
1532 static const SkRect kBL = {0.f, kN, 2.f * kN, 3.f * kN};
1533 static const SkRect kBR = {kN, kN, 3.f * kN, 3.f * kN};
1534
1535 enum ShapeType { kRect, kRRect, kConvex };
1536
1537 SkRect rects[] = { kTL, kTR, kBL, kBR };
1538 for (ShapeType type : { kRect, kRRect, kConvex }) {
1539 for (int opBits = 6; opBits < 16; ++opBits) {
1540 SkString name;
1541 name.appendf("complex-%d-%d", (int) type, opBits);
1542
1543 SkRect expectedRectIntersection = SkRect::Make(kDeviceBounds);
1544 SkRRect expectedRRectIntersection = SkRRect::MakeRect(expectedRectIntersection);
1545
1546 auto b = TestCase::Build(name.c_str(), kDeviceBounds);
1547 for (int i = 0; i < 4; ++i) {
1548 SkClipOp op = (opBits & (1 << i)) ? SkClipOp::kIntersect : SkClipOp::kDifference;
1549 switch(type) {
1550 case kRect: {
1551 SkRect r = rects[i];
1552 if (op == SkClipOp::kDifference) {
1553 // Shrink the rect for difference ops, otherwise in the rect testcase
1554 // any difference op would remove the intersection of the other ops
1555 // given how the rects are defined, and that's just not interesting.
1556 r.inset(kR, kR);
1557 }
1558 b.actual().rect(r, GrAA::kYes, op);
1559 if (op == SkClipOp::kIntersect) {
1560 SkAssertResult(expectedRectIntersection.intersect(r));
1561 } else {
1562 b.expect().rect(r, GrAA::kYes, SkClipOp::kDifference);
1563 }
1564 break; }
1565 case kRRect: {
1566 SkRRect rrect = SkRRect::MakeRectXY(rects[i], kR, kR);
1567 b.actual().rrect(rrect, GrAA::kYes, op);
1568 if (op == SkClipOp::kIntersect) {
1569 expectedRRectIntersection = SkRRectPriv::ConservativeIntersect(
1570 expectedRRectIntersection, rrect);
1571 SkASSERT(!expectedRRectIntersection.isEmpty());
1572 } else {
1573 b.expect().rrect(rrect, GrAA::kYes, SkClipOp::kDifference);
1574 }
1575 break; }
1576 case kConvex:
1577 b.actual().path(make_octagon(rects[i], kR, kR), GrAA::kYes, op);
1578 // NOTE: We don't set any expectations here, since convex just calls
1579 // expectActual() at the end.
1580 break;
1581 }
1582 }
1583
1584 // The expectations differ depending on the shape type
1585 GrClipStack::ClipState state = GrClipStack::ClipState::kComplex;
1586 if (type == kConvex) {
1587 // The simplest case is when the paths cannot be combined together, so we expect
1588 // the actual elements to be unmodified (both intersect and difference).
1589 b.expectActual();
1590 } else if (opBits) {
1591 // All intersection ops were pre-computed into expectedR[R]ectIntersection
1592 // - difference ops already added in the for loop
1593 if (type == kRect) {
1594 SkASSERT(expectedRectIntersection != SkRect::Make(kDeviceBounds) &&
1595 !expectedRectIntersection.isEmpty());
1596 b.expect().rect(expectedRectIntersection, GrAA::kYes, SkClipOp::kIntersect);
1597 if (opBits == 0xf) {
1598 state = GrClipStack::ClipState::kDeviceRect;
1599 }
1600 } else {
1601 SkASSERT(expectedRRectIntersection !=
1602 SkRRect::MakeRect(SkRect::Make(kDeviceBounds)) &&
1603 !expectedRRectIntersection.isEmpty());
1604 b.expect().rrect(expectedRRectIntersection, GrAA::kYes, SkClipOp::kIntersect);
1605 if (opBits == 0xf) {
1606 state = GrClipStack::ClipState::kDeviceRRect;
1607 }
1608 }
1609 }
1610
1611 run_test_case(r, b.state(state).finishTest());
1612 }
1613 }
1614}
1615
1616// ///////////////////////////////////////////////////////////////////////////////
1617// // These tests do not use the TestCase infrastructure and manipulate a
1618// // GrClipStack directly.
1619
1620// Tests that replaceClip() works as expected across save/restores
1621DEF_TEST(GrClipStack_ReplaceClip, r) {
1622 GrClipStack cs(kDeviceBounds, nullptr, false);
1623
1624 SkRRect rrect = SkRRect::MakeRectXY({15.f, 12.25f, 40.3f, 23.5f}, 4.f, 6.f);
1625 cs.clipRRect(SkMatrix::I(), rrect, GrAA::kYes, SkClipOp::kIntersect);
1626
1627 SkIRect replace = {50, 25, 75, 40}; // Is disjoint from the rrect element
1628 cs.save();
1629 cs.replaceClip(replace);
1630
1631 REPORTER_ASSERT(r, cs.clipState() == GrClipStack::ClipState::kDeviceRect,
1632 "Clip did not become a device rect");
1633 REPORTER_ASSERT(r, cs.getConservativeBounds() == replace, "Unexpected replaced clip bounds");
1634 const GrClipStack::Element& replaceElement = *cs.begin();
1635 REPORTER_ASSERT(r, replaceElement.fShape.rect() == SkRect::Make(replace) &&
1636 replaceElement.fAA == GrAA::kNo &&
1637 replaceElement.fOp == SkClipOp::kIntersect &&
1638 replaceElement.fLocalToDevice == SkMatrix::I(),
1639 "Unexpected replace element state");
1640
1641 // Restore should undo the replaced clip and bring back the rrect
1642 cs.restore();
1643 REPORTER_ASSERT(r, cs.clipState() == GrClipStack::ClipState::kDeviceRRect,
1644 "Unexpected state after restore, not kDeviceRRect");
1645 const GrClipStack::Element& rrectElem = *cs.begin();
1646 REPORTER_ASSERT(r, rrectElem.fShape.rrect() == rrect &&
1647 rrectElem.fAA == GrAA::kYes &&
1648 rrectElem.fOp == SkClipOp::kIntersect &&
1649 rrectElem.fLocalToDevice == SkMatrix::I(),
1650 "RRect element state not restored properly after replace clip undone");
1651}
1652
Robert Phillipsc4fbc8d2020-11-30 10:17:53 -05001653// Try to overflow the number of allowed window rects (see skbug.com/10989)
1654DEF_TEST(GrClipStack_DiffRects, r) {
1655 GrMockOptions options;
1656 options.fMaxWindowRectangles = 8;
1657
1658 SkSimpleMatrixProvider matrixProvider = SkMatrix::I();
1659 sk_sp<GrDirectContext> context = GrDirectContext::MakeMock(&options);
Robert Phillips4dca8312021-07-28 15:13:20 -04001660 std::unique_ptr<skgpu::v1::SurfaceDrawContext> sdc = skgpu::v1::SurfaceDrawContext::Make(
Robert Phillipsc4fbc8d2020-11-30 10:17:53 -05001661 context.get(), GrColorType::kRGBA_8888, SkColorSpace::MakeSRGB(),
Chris Daltonf5b87f92021-04-19 17:27:09 -06001662 SkBackingFit::kExact, kDeviceBounds.size(), SkSurfaceProps());
Robert Phillipsc4fbc8d2020-11-30 10:17:53 -05001663
1664 GrClipStack cs(kDeviceBounds, &matrixProvider, false);
1665
1666 cs.save();
1667 for (int y = 0; y < 10; ++y) {
1668 for (int x = 0; x < 10; ++x) {
1669 cs.clipRect(SkMatrix::I(), SkRect::MakeXYWH(10*x+1, 10*y+1, 8, 8),
1670 GrAA::kNo, SkClipOp::kDifference);
1671 }
1672 }
1673
1674 GrAppliedClip out(kDeviceBounds.size());
1675 SkRect drawBounds = SkRect::Make(kDeviceBounds);
Robert Phillips4dca8312021-07-28 15:13:20 -04001676 GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage,
Chris Daltonb1e8f852021-06-23 13:36:51 -06001677 &out, &drawBounds);
Robert Phillipsc4fbc8d2020-11-30 10:17:53 -05001678
1679 REPORTER_ASSERT(r, effect == GrClip::Effect::kClipped);
1680 REPORTER_ASSERT(r, out.windowRectsState().numWindows() == 8);
1681
1682 cs.restore();
1683}
1684
Michael Ludwiga195d102020-09-15 14:51:52 -04001685// Tests that when a stack is forced to always be AA, non-AA elements become AA
1686DEF_TEST(GrClipStack_ForceAA, r) {
1687 GrClipStack cs(kDeviceBounds, nullptr, true);
1688
1689 // AA will remain AA
1690 SkRect aaRect = {0.25f, 12.43f, 25.2f, 23.f};
1691 cs.clipRect(SkMatrix::I(), aaRect, GrAA::kYes, SkClipOp::kIntersect);
1692
1693 // Non-AA will become AA
1694 SkPath nonAAPath = make_octagon({2.f, 10.f, 16.f, 20.f});
1695 cs.clipPath(SkMatrix::I(), nonAAPath, GrAA::kNo, SkClipOp::kIntersect);
1696
Michael Ludwig462bdfc2020-09-22 16:27:04 -04001697 // Non-AA rects remain non-AA so they can be applied as a scissor
Michael Ludwiga195d102020-09-15 14:51:52 -04001698 SkRect nonAARect = {4.5f, 5.f, 17.25f, 18.23f};
1699 cs.clipRect(SkMatrix::I(), nonAARect, GrAA::kNo, SkClipOp::kIntersect);
1700
1701 // The stack reports elements newest first, but the non-AA rect op was combined in place with
1702 // the first aa rect, so we should see nonAAPath as AA, and then the intersection of rects.
Michael Ludwiga195d102020-09-15 14:51:52 -04001703 auto elements = cs.begin();
1704
Michael Ludwig462bdfc2020-09-22 16:27:04 -04001705 const GrClipStack::Element& nonAARectElement = *elements;
1706 REPORTER_ASSERT(r, nonAARectElement.fShape.isRect(), "Expected rect element");
1707 REPORTER_ASSERT(r, nonAARectElement.fAA == GrAA::kNo,
1708 "Axis-aligned non-AA rect ignores forceAA");
1709 REPORTER_ASSERT(r, nonAARectElement.fShape.rect() == nonAARect,
1710 "Mixed AA rects should not combine");
Michael Ludwiga195d102020-09-15 14:51:52 -04001711
1712 ++elements;
Michael Ludwig462bdfc2020-09-22 16:27:04 -04001713 const GrClipStack::Element& aaPathElement = *elements;
1714 REPORTER_ASSERT(r, aaPathElement.fShape.isPath(), "Expected path element");
1715 REPORTER_ASSERT(r, aaPathElement.fShape.path() == nonAAPath, "Wrong path element");
1716 REPORTER_ASSERT(r, aaPathElement.fAA == GrAA::kYes, "Path element not promoted to AA");
Michael Ludwiga195d102020-09-15 14:51:52 -04001717
1718 ++elements;
Michael Ludwig462bdfc2020-09-22 16:27:04 -04001719 const GrClipStack::Element& aaRectElement = *elements;
1720 REPORTER_ASSERT(r, aaRectElement.fShape.isRect(), "Expected rect element");
1721 REPORTER_ASSERT(r, aaRectElement.fShape.rect() == aaRect,
1722 "Mixed AA rects should not combine");
1723 REPORTER_ASSERT(r, aaRectElement.fAA == GrAA::kYes, "Rect element stays AA");
1724
1725 ++elements;
1726 REPORTER_ASSERT(r, !(elements != cs.end()), "Expected only three clip elements");
Michael Ludwiga195d102020-09-15 14:51:52 -04001727}
1728
1729// Tests preApply works as expected for device rects, rrects, and reports clipped-out, etc. as
1730// expected.
1731DEF_TEST(GrClipStack_PreApply, r) {
1732 GrClipStack cs(kDeviceBounds, nullptr, false);
1733
1734 // Offscreen is kClippedOut
1735 GrClip::PreClipResult result = cs.preApply({-10.f, -10.f, -1.f, -1.f}, GrAA::kYes);
1736 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClippedOut,
1737 "Offscreen draw is kClippedOut");
1738
1739 // Intersecting screen with wide-open clip is kUnclipped
1740 result = cs.preApply({-10.f, -10.f, 10.f, 10.f}, GrAA::kYes);
1741 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kUnclipped,
1742 "Wide open screen intersection is still kUnclipped");
1743
1744 // Empty clip is clipped out
1745 cs.save();
1746 cs.clipRect(SkMatrix::I(), SkRect::MakeEmpty(), GrAA::kNo, SkClipOp::kIntersect);
1747 result = cs.preApply({0.f, 0.f, 20.f, 20.f}, GrAA::kYes);
1748 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClippedOut,
1749 "Empty clip stack preApplies as kClippedOut");
1750 cs.restore();
1751
1752 // Contained inside clip is kUnclipped (using rrect for the outer clip element since paths
1753 // don't support an inner bounds and anything complex is otherwise skipped in preApply).
1754 SkRect rect = {10.f, 10.f, 40.f, 40.f};
1755 SkRRect bigRRect = SkRRect::MakeRectXY(rect.makeOutset(5.f, 5.f), 5.f, 5.f);
1756 cs.save();
1757 cs.clipRRect(SkMatrix::I(), bigRRect, GrAA::kYes, SkClipOp::kIntersect);
1758 result = cs.preApply(rect, GrAA::kYes);
1759 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kUnclipped,
1760 "Draw contained within clip is kUnclipped");
1761
1762 // Disjoint from clip (but still on screen) is kClippedOut
1763 result = cs.preApply({50.f, 50.f, 60.f, 60.f}, GrAA::kYes);
1764 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClippedOut,
1765 "Draw not intersecting clip is kClippedOut");
1766 cs.restore();
1767
1768 // Intersecting clip is kClipped for complex shape
1769 cs.save();
1770 SkPath path = make_octagon(rect.makeOutset(5.f, 5.f), 5.f, 5.f);
1771 cs.clipPath(SkMatrix::I(), path, GrAA::kYes, SkClipOp::kIntersect);
1772 result = cs.preApply(path.getBounds(), GrAA::kNo);
1773 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClipped && !result.fIsRRect,
1774 "Draw with complex clip is kClipped, but is not an rrect");
1775 cs.restore();
1776
1777 // Intersecting clip is kDeviceRect for axis-aligned rect clip
1778 cs.save();
1779 cs.clipRect(SkMatrix::I(), rect, GrAA::kYes, SkClipOp::kIntersect);
1780 result = cs.preApply(rect.makeOffset(2.f, 2.f), GrAA::kNo);
1781 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClipped &&
1782 result.fAA == GrAA::kYes &&
1783 result.fIsRRect &&
1784 result.fRRect == SkRRect::MakeRect(rect),
1785 "kDeviceRect clip stack should be reported by preApply");
1786 cs.restore();
1787
1788 // Intersecting clip is kDeviceRRect for axis-aligned rrect clip
1789 cs.save();
1790 SkRRect clipRRect = SkRRect::MakeRectXY(rect, 5.f, 5.f);
1791 cs.clipRRect(SkMatrix::I(), clipRRect, GrAA::kYes, SkClipOp::kIntersect);
1792 result = cs.preApply(rect.makeOffset(2.f, 2.f), GrAA::kNo);
1793 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClipped &&
1794 result.fAA == GrAA::kYes &&
1795 result.fIsRRect &&
1796 result.fRRect == clipRRect,
1797 "kDeviceRRect clip stack should be reported by preApply");
1798 cs.restore();
1799}
1800
1801// Tests the clip shader entry point
1802DEF_TEST(GrClipStack_Shader, r) {
1803 sk_sp<SkShader> shader = SkShaders::Color({0.f, 0.f, 0.f, 0.5f}, nullptr);
1804
1805 SkSimpleMatrixProvider matrixProvider = SkMatrix::I();
1806 sk_sp<GrDirectContext> context = GrDirectContext::MakeMock(nullptr);
Robert Phillips4dca8312021-07-28 15:13:20 -04001807 std::unique_ptr<skgpu::v1::SurfaceDrawContext> sdc = skgpu::v1::SurfaceDrawContext::Make(
Michael Ludwiga195d102020-09-15 14:51:52 -04001808 context.get(), GrColorType::kRGBA_8888, SkColorSpace::MakeSRGB(),
Chris Daltonf5b87f92021-04-19 17:27:09 -06001809 SkBackingFit::kExact, kDeviceBounds.size(), SkSurfaceProps());
Michael Ludwiga195d102020-09-15 14:51:52 -04001810
1811 GrClipStack cs(kDeviceBounds, &matrixProvider, false);
1812 cs.save();
1813 cs.clipShader(shader);
1814
1815 REPORTER_ASSERT(r, cs.clipState() == GrClipStack::ClipState::kComplex,
1816 "A clip shader should be reported as a complex clip");
1817
1818 GrAppliedClip out(kDeviceBounds.size());
1819 SkRect drawBounds = {10.f, 11.f, 16.f, 32.f};
Robert Phillips4dca8312021-07-28 15:13:20 -04001820 GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage,
Chris Daltonb1e8f852021-06-23 13:36:51 -06001821 &out, &drawBounds);
Michael Ludwiga195d102020-09-15 14:51:52 -04001822
1823 REPORTER_ASSERT(r, effect == GrClip::Effect::kClipped,
1824 "apply() should return kClipped for a clip shader");
1825 REPORTER_ASSERT(r, out.hasCoverageFragmentProcessor(),
1826 "apply() should have converted clip shader to a coverage FP");
1827
1828 GrAppliedClip out2(kDeviceBounds.size());
1829 drawBounds = {-15.f, -10.f, -1.f, 10.f}; // offscreen
Robert Phillips4dca8312021-07-28 15:13:20 -04001830 effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage, &out2,
Chris Daltonb1e8f852021-06-23 13:36:51 -06001831 &drawBounds);
Michael Ludwiga195d102020-09-15 14:51:52 -04001832 REPORTER_ASSERT(r, effect == GrClip::Effect::kClippedOut,
1833 "apply() should still discard offscreen draws with a clip shader");
1834
1835 cs.restore();
1836 REPORTER_ASSERT(r, cs.clipState() == GrClipStack::ClipState::kWideOpen,
1837 "restore() should get rid of the clip shader");
1838
1839
1840 // Adding a clip shader on top of a device rect clip should prevent preApply from reporting
1841 // it as a device rect
1842 cs.clipRect(SkMatrix::I(), {10, 15, 30, 30}, GrAA::kNo, SkClipOp::kIntersect);
1843 SkASSERT(cs.clipState() == GrClipStack::ClipState::kDeviceRect); // test precondition
1844 cs.clipShader(shader);
1845 GrClip::PreClipResult result = cs.preApply(SkRect::Make(kDeviceBounds), GrAA::kYes);
1846 REPORTER_ASSERT(r, result.fEffect == GrClip::Effect::kClipped && !result.fIsRRect,
1847 "A clip shader should not produce a device rect from preApply");
1848}
1849
1850// Tests apply() under simple circumstances, that don't require actual rendering of masks, or
1851// atlases. This lets us define the test regularly instead of a GPU-only test.
1852// - This is not exhaustive and is challenging to unit test, so apply() is predominantly tested by
1853// the GMs instead.
1854DEF_TEST(GrClipStack_SimpleApply, r) {
1855 SkSimpleMatrixProvider matrixProvider = SkMatrix::I();
1856 sk_sp<GrDirectContext> context = GrDirectContext::MakeMock(nullptr);
Robert Phillips4dca8312021-07-28 15:13:20 -04001857 std::unique_ptr<skgpu::v1::SurfaceDrawContext> sdc = skgpu::v1::SurfaceDrawContext::Make(
Michael Ludwiga195d102020-09-15 14:51:52 -04001858 context.get(), GrColorType::kRGBA_8888, SkColorSpace::MakeSRGB(),
Chris Daltonf5b87f92021-04-19 17:27:09 -06001859 SkBackingFit::kExact, kDeviceBounds.size(), SkSurfaceProps());
Michael Ludwiga195d102020-09-15 14:51:52 -04001860
1861 GrClipStack cs(kDeviceBounds, &matrixProvider, false);
1862
1863 // Offscreen draw is kClippedOut
1864 {
1865 SkRect drawBounds = {-15.f, -15.f, -1.f, -1.f};
1866
1867 GrAppliedClip out(kDeviceBounds.size());
Robert Phillips4dca8312021-07-28 15:13:20 -04001868 GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage,
Chris Daltonb1e8f852021-06-23 13:36:51 -06001869 &out, &drawBounds);
Michael Ludwiga195d102020-09-15 14:51:52 -04001870 REPORTER_ASSERT(r, effect == GrClip::Effect::kClippedOut, "Offscreen draw is clipped out");
1871 }
1872
1873 // Draw contained in clip is kUnclipped
1874 {
1875 SkRect drawBounds = {15.4f, 16.3f, 26.f, 32.f};
1876 cs.save();
1877 cs.clipPath(SkMatrix::I(), make_octagon(drawBounds.makeOutset(5.f, 5.f), 5.f, 5.f),
1878 GrAA::kYes, SkClipOp::kIntersect);
1879
1880 GrAppliedClip out(kDeviceBounds.size());
Robert Phillips4dca8312021-07-28 15:13:20 -04001881 GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage,
Chris Daltonb1e8f852021-06-23 13:36:51 -06001882 &out, &drawBounds);
Michael Ludwiga195d102020-09-15 14:51:52 -04001883 REPORTER_ASSERT(r, effect == GrClip::Effect::kUnclipped, "Draw inside clip is unclipped");
1884 cs.restore();
1885 }
1886
1887 // Draw bounds are cropped to device space before checking contains
1888 {
1889 SkRect clipRect = {kDeviceBounds.fRight - 20.f, 10.f, kDeviceBounds.fRight, 20.f};
1890 SkRect drawRect = clipRect.makeOffset(10.f, 0.f);
1891
1892 cs.save();
1893 cs.clipRect(SkMatrix::I(), clipRect, GrAA::kNo, SkClipOp::kIntersect);
1894
1895 GrAppliedClip out(kDeviceBounds.size());
Robert Phillips4dca8312021-07-28 15:13:20 -04001896 GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage,
Chris Daltonb1e8f852021-06-23 13:36:51 -06001897 &out, &drawRect);
Michael Ludwiga195d102020-09-15 14:51:52 -04001898 REPORTER_ASSERT(r, SkRect::Make(kDeviceBounds).contains(drawRect),
1899 "Draw rect should be clipped to device rect");
1900 REPORTER_ASSERT(r, effect == GrClip::Effect::kUnclipped,
1901 "After device clipping, this should be detected as contained within clip");
1902 cs.restore();
1903 }
1904
1905 // Non-AA device rect intersect is just a scissor
1906 {
1907 SkRect clipRect = {15.3f, 17.23f, 30.2f, 50.8f};
1908 SkRect drawRect = clipRect.makeOutset(10.f, 10.f);
1909 SkIRect expectedScissor = clipRect.round();
1910
1911 cs.save();
1912 cs.clipRect(SkMatrix::I(), clipRect, GrAA::kNo, SkClipOp::kIntersect);
1913
1914 GrAppliedClip out(kDeviceBounds.size());
Robert Phillips4dca8312021-07-28 15:13:20 -04001915 GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage,
Chris Daltonb1e8f852021-06-23 13:36:51 -06001916 &out, &drawRect);
Michael Ludwiga195d102020-09-15 14:51:52 -04001917 REPORTER_ASSERT(r, effect == GrClip::Effect::kClipped, "Draw should be clipped by rect");
1918 REPORTER_ASSERT(r, !out.hasCoverageFragmentProcessor(), "Clip should not use coverage FPs");
1919 REPORTER_ASSERT(r, !out.hardClip().hasStencilClip(), "Clip should not need stencil");
1920 REPORTER_ASSERT(r, !out.hardClip().windowRectsState().enabled(),
1921 "Clip should not need window rects");
1922 REPORTER_ASSERT(r, out.scissorState().enabled() &&
1923 out.scissorState().rect() == expectedScissor,
1924 "Clip has unexpected scissor rectangle");
1925 cs.restore();
1926 }
1927
1928 // Analytic coverage FPs
1929 auto testHasCoverageFP = [&](SkRect drawBounds) {
1930 GrAppliedClip out(kDeviceBounds.size());
Robert Phillips4dca8312021-07-28 15:13:20 -04001931 GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage,
Chris Daltonb1e8f852021-06-23 13:36:51 -06001932 &out, &drawBounds);
Michael Ludwiga195d102020-09-15 14:51:52 -04001933 REPORTER_ASSERT(r, effect == GrClip::Effect::kClipped, "Draw should be clipped");
1934 REPORTER_ASSERT(r, out.scissorState().enabled(), "Coverage FPs should still set scissor");
1935 REPORTER_ASSERT(r, out.hasCoverageFragmentProcessor(), "Clip should use coverage FP");
1936 };
1937
1938 // Axis-aligned rect can be an analytic FP
1939 {
1940 cs.save();
1941 cs.clipRect(SkMatrix::I(), {10.2f, 8.342f, 63.f, 23.3f}, GrAA::kYes,
1942 SkClipOp::kDifference);
1943 testHasCoverageFP({9.f, 10.f, 30.f, 18.f});
1944 cs.restore();
1945 }
1946
1947 // Axis-aligned round rect can be an analytic FP
1948 {
1949 SkRect rect = {4.f, 8.f, 20.f, 20.f};
1950 cs.save();
1951 cs.clipRRect(SkMatrix::I(), SkRRect::MakeRectXY(rect, 3.f, 3.f), GrAA::kYes,
1952 SkClipOp::kIntersect);
1953 testHasCoverageFP(rect.makeOffset(2.f, 2.f));
1954 cs.restore();
1955 }
1956
1957 // Transformed rect can be an analytic FP
1958 {
1959 SkRect rect = {14.f, 8.f, 30.f, 22.34f};
1960 SkMatrix rot = SkMatrix::RotateDeg(34.f);
1961 cs.save();
1962 cs.clipRect(rot, rect, GrAA::kNo, SkClipOp::kIntersect);
1963 testHasCoverageFP(rot.mapRect(rect));
1964 cs.restore();
1965 }
1966
1967 // Convex polygons can be an analytic FP
1968 {
1969 SkRect rect = {15.f, 15.f, 45.f, 45.f};
1970 cs.save();
1971 cs.clipPath(SkMatrix::I(), make_octagon(rect), GrAA::kYes, SkClipOp::kIntersect);
1972 testHasCoverageFP(rect.makeOutset(2.f, 2.f));
1973 cs.restore();
1974 }
1975}
1976
Chris Daltone8f66632021-06-16 11:59:22 -06001977// Must disable tessellation in order to trigger SW mask generation when the clip stack is applied.
1978static void disable_tessellation_atlas(GrContextOptions* options) {
Michael Ludwiga195d102020-09-15 14:51:52 -04001979 options->fGpuPathRenderers = GpuPathRenderers::kNone;
Chris Daltone8f66632021-06-16 11:59:22 -06001980 options->fAvoidStencilBuffers = true;
Michael Ludwiga195d102020-09-15 14:51:52 -04001981}
1982
1983DEF_GPUTEST_FOR_CONTEXTS(GrClipStack_SWMask,
1984 sk_gpu_test::GrContextFactory::IsRenderingContext,
Chris Daltone8f66632021-06-16 11:59:22 -06001985 r, ctxInfo, disable_tessellation_atlas) {
Michael Ludwiga195d102020-09-15 14:51:52 -04001986 GrDirectContext* context = ctxInfo.directContext();
Robert Phillips4dca8312021-07-28 15:13:20 -04001987 std::unique_ptr<skgpu::v1::SurfaceDrawContext> sdc = skgpu::v1::SurfaceDrawContext::Make(
Chris Daltonf5b87f92021-04-19 17:27:09 -06001988 context, GrColorType::kRGBA_8888, nullptr, SkBackingFit::kExact, kDeviceBounds.size(),
1989 SkSurfaceProps());
Michael Ludwiga195d102020-09-15 14:51:52 -04001990
1991 SkSimpleMatrixProvider matrixProvider = SkMatrix::I();
1992 std::unique_ptr<GrClipStack> cs(new GrClipStack(kDeviceBounds, &matrixProvider, false));
1993
1994 auto addMaskRequiringClip = [&](SkScalar x, SkScalar y, SkScalar radius) {
1995 SkPath path;
1996 path.addCircle(x, y, radius);
1997 path.addCircle(x + radius / 2.f, y + radius / 2.f, radius);
1998 path.setFillType(SkPathFillType::kEvenOdd);
1999
2000 // Use AA so that clip application does not route through the stencil buffer
2001 cs->clipPath(SkMatrix::I(), path, GrAA::kYes, SkClipOp::kIntersect);
2002 };
2003
2004 auto drawRect = [&](SkRect drawBounds) {
2005 GrPaint paint;
2006 paint.setColor4f({1.f, 1.f, 1.f, 1.f});
Robert Phillips4dca8312021-07-28 15:13:20 -04002007 sdc->drawRect(cs.get(), std::move(paint), GrAA::kYes, SkMatrix::I(), drawBounds);
Michael Ludwiga195d102020-09-15 14:51:52 -04002008 };
2009
2010 auto generateMask = [&](SkRect drawBounds) {
2011 GrUniqueKey priorKey = cs->testingOnly_getLastSWMaskKey();
2012 drawRect(drawBounds);
2013 GrUniqueKey newKey = cs->testingOnly_getLastSWMaskKey();
2014 REPORTER_ASSERT(r, priorKey != newKey, "Did not generate a new SW mask key as expected");
2015 return newKey;
2016 };
2017
2018 auto verifyKeys = [&](const std::vector<GrUniqueKey>& expectedKeys,
2019 const std::vector<GrUniqueKey>& releasedKeys) {
2020 context->flush();
2021 GrProxyProvider* proxyProvider = context->priv().proxyProvider();
2022
2023#ifdef SK_DEBUG
2024 // The proxy providers key count fluctuates based on proxy lifetime, but we want to
2025 // verify the resource count, and that requires using key tags that are debug-only.
2026 SkASSERT(expectedKeys.size() > 0 || releasedKeys.size() > 0);
2027 const char* tag = expectedKeys.size() > 0 ? expectedKeys[0].tag() : releasedKeys[0].tag();
2028 GrResourceCache* cache = context->priv().getResourceCache();
2029 int numProxies = cache->countUniqueKeysWithTag(tag);
2030 REPORTER_ASSERT(r, (int) expectedKeys.size() == numProxies,
2031 "Unexpected proxy count, got %d, not %d",
2032 numProxies, (int) expectedKeys.size());
2033#endif
2034
2035 for (const auto& key : expectedKeys) {
2036 auto proxy = proxyProvider->findOrCreateProxyByUniqueKey(key);
2037 REPORTER_ASSERT(r, SkToBool(proxy), "Unable to find resource for expected mask key");
2038 }
2039 for (const auto& key : releasedKeys) {
2040 auto proxy = proxyProvider->findOrCreateProxyByUniqueKey(key);
2041 REPORTER_ASSERT(r, !SkToBool(proxy), "SW mask not released as expected");
2042 }
2043 };
2044
2045 // Creates a mask for a complex clip
2046 cs->save();
2047 addMaskRequiringClip(5.f, 5.f, 20.f);
2048 GrUniqueKey keyADepth1 = generateMask({0.f, 0.f, 20.f, 20.f});
2049 GrUniqueKey keyBDepth1 = generateMask({10.f, 10.f, 30.f, 30.f});
2050 verifyKeys({keyADepth1, keyBDepth1}, {});
2051
2052 // Creates a new mask for a new save record, but doesn't delete the old records
2053 cs->save();
2054 addMaskRequiringClip(6.f, 6.f, 15.f);
2055 GrUniqueKey keyADepth2 = generateMask({0.f, 0.f, 20.f, 20.f});
2056 GrUniqueKey keyBDepth2 = generateMask({10.f, 10.f, 30.f, 30.f});
2057 verifyKeys({keyADepth1, keyBDepth1, keyADepth2, keyBDepth2}, {});
2058
2059 // Release after modifying the current record (even if we don't draw anything)
2060 addMaskRequiringClip(4.f, 4.f, 15.f);
2061 GrUniqueKey keyCDepth2 = generateMask({4.f, 4.f, 16.f, 20.f});
2062 verifyKeys({keyADepth1, keyBDepth1, keyCDepth2}, {keyADepth2, keyBDepth2});
2063
2064 // Release after restoring an older record
2065 cs->restore();
2066 verifyKeys({keyADepth1, keyBDepth1}, {keyCDepth2});
2067
2068 // Drawing finds the old masks at depth 1 still w/o making new ones
2069 drawRect({0.f, 0.f, 20.f, 20.f});
2070 drawRect({10.f, 10.f, 30.f, 30.f});
2071 verifyKeys({keyADepth1, keyBDepth1}, {});
2072
2073 // Drawing something contained within a previous mask also does not make a new one
2074 drawRect({5.f, 5.f, 15.f, 15.f});
2075 verifyKeys({keyADepth1, keyBDepth1}, {});
2076
2077 // Release on destruction
2078 cs = nullptr;
2079 verifyKeys({}, {keyADepth1, keyBDepth1});
2080}