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