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