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