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