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