blob: afea138e7c9c378a3f31a4de94bfedefd69051ee [file] [log] [blame]
Michael Ludwiga195d102020-09-15 14:51:52 -04001
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
Robert Phillips74b94322021-08-17 11:50:20 -04009#include "src/gpu/v1/ClipStack.h"
Michael Ludwiga195d102020-09-15 14:51:52 -040010#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"
Adlai Hollera0693042020-10-14 11:23:11 -040021#include "src/gpu/GrDirectContextPriv.h"
Michael Ludwiga195d102020-09-15 14:51:52 -040022#include "src/gpu/GrProxyProvider.h"
Robert Phillips06273bc2021-08-11 15:43:50 -040023#include "src/gpu/ops/GrDrawOp.h"
Robert Phillips4dca8312021-07-28 15:13:20 -040024#include "src/gpu/v1/SurfaceDrawContext_v1.h"
Michael Ludwiga195d102020-09-15 14:51:52 -040025
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:
Robert Phillips74b94322021-08-17 11:50:20 -040042 using ClipStack = skgpu::v1::ClipStack;
43
Michael Ludwiga195d102020-09-15 14:51:52 -040044 // 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; }
Robert Phillips74b94322021-08-17 11:50:20 -040063 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; }
Michael Ludwiga195d102020-09-15 14:51:52 -040066
67private:
68 friend class TestCaseBuilder;
69
70 TestCase(SkString name,
71 const SkIRect& deviceBounds,
Robert Phillips74b94322021-08-17 11:50:20 -040072 ClipStack::ClipState expectedState,
73 std::vector<ClipStack::Element> actual,
74 std::vector<ClipStack::Element> expected)
Michael Ludwiga195d102020-09-15 14:51:52 -040075 : 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
Robert Phillips74b94322021-08-17 11:50:20 -040083 // 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
Michael Ludwiga195d102020-09-15 14:51:52 -040085 // difference op.
86 std::pair<SkIRect, bool> getOptimalBounds() const;
87
88 SkString fName;
89
Robert Phillips74b94322021-08-17 11:50:20 -040090 // The input shapes+state to ClipStack
91 std::vector<ClipStack::Element> fElements;
Michael Ludwiga195d102020-09-15 14:51:52 -040092 SkIRect fDeviceBounds;
93
Robert Phillips74b94322021-08-17 11:50:20 -040094 // The expected output of iterating over the ClipStack after all fElements are added, although
Michael Ludwiga195d102020-09-15 14:51:52 -040095 // order is not important
Robert Phillips74b94322021-08-17 11:50:20 -040096 std::vector<ClipStack::Element> fExpectedElements;
97 ClipStack::ClipState fExpectedState;
Michael Ludwiga195d102020-09-15 14:51:52 -040098};
99
100class ElementsBuilder {
101public:
Robert Phillips74b94322021-08-17 11:50:20 -0400102 using ClipStack = skgpu::v1::ClipStack;
103
Michael Ludwiga195d102020-09-15 14:51:52 -0400104 // 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
Robert Phillips74b94322021-08-17 11:50:20 -0400154 ElementsBuilder(TestCaseBuilder* builder, std::vector<ClipStack::Element>* elements)
Michael Ludwiga195d102020-09-15 14:51:52 -0400155 : fBuilder(builder)
156 , fElements(elements) {}
157
158 SkMatrix fLocalToDevice = SkMatrix::I();
159 GrAA fAA = GrAA::kNo;
160 SkClipOp fOp = SkClipOp::kIntersect;
161
Robert Phillips74b94322021-08-17 11:50:20 -0400162 TestCaseBuilder* fBuilder;
163 std::vector<ClipStack::Element>* fElements;
Michael Ludwiga195d102020-09-15 14:51:52 -0400164};
165
166class TestCaseBuilder {
167public:
Robert Phillips74b94322021-08-17 11:50:20 -0400168 using ClipStack = skgpu::v1::ClipStack;
169
Michael Ludwiga195d102020-09-15 14:51:52 -0400170 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
Robert Phillips74b94322021-08-17 11:50:20 -0400178 TestCaseBuilder& state(ClipStack::ClipState state) {
Michael Ludwiga195d102020-09-15 14:51:52 -0400179 fExpectedState = state;
180 return *this;
181 }
182
183 TestCase finishTest() {
184 TestCase test(fName, fDeviceBounds, fExpectedState,
185 std::move(fActualElements), std::move(fExpectedElements));
186
Robert Phillips74b94322021-08-17 11:50:20 -0400187 fExpectedState = ClipStack::ClipState::kWideOpen;
Michael Ludwiga195d102020-09-15 14:51:52 -0400188 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)
Robert Phillips74b94322021-08-17 11:50:20 -0400197 , fExpectedState(ClipStack::ClipState::kWideOpen) {}
Michael Ludwiga195d102020-09-15 14:51:52 -0400198
199 SkString fName;
200 SkIRect fDeviceBounds;
Robert Phillips74b94322021-08-17 11:50:20 -0400201 ClipStack::ClipState fExpectedState;
Michael Ludwiga195d102020-09-15 14:51:52 -0400202
Robert Phillips74b94322021-08-17 11:50:20 -0400203 std::vector<ClipStack::Element> fActualElements;
204 std::vector<ClipStack::Element> fExpectedElements;
Michael Ludwiga195d102020-09-15 14:51:52 -0400205};
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 {
Robert Phillips74b94322021-08-17 11:50:20 -0400242 if (fExpectedState == ClipStack::ClipState::kEmpty) {
Michael Ludwiga195d102020-09-15 14:51:52 -0400243 return {SkIRect::MakeEmpty(), true};
244 }
245
246 bool expectOptimal = true;
247 SkRegion region(fDeviceBounds);
Robert Phillips74b94322021-08-17 11:50:20 -0400248 for (const ClipStack::Element& e : fExpectedElements) {
Michael Ludwiga195d102020-09-15 14:51:52 -0400249 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
Robert Phillips74b94322021-08-17 11:50:20 -0400278static bool compare_elements(const skgpu::v1::ClipStack::Element& a,
279 const skgpu::v1::ClipStack::Element& b) {
Michael Ludwiga195d102020-09-15 14:51:52 -0400280 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
Robert Phillips74b94322021-08-17 11:50:20 -0400303void TestCase::run(const std::vector<int>& order,
304 SavePolicy policy,
Michael Ludwiga195d102020-09-15 14:51:52 -0400305 skiatest::Reporter* reporter) const {
306 SkASSERT(fElements.size() == order.size());
307
308 SkSimpleMatrixProvider matrixProvider(SkMatrix::I());
Robert Phillips74b94322021-08-17 11:50:20 -0400309 ClipStack cs(fDeviceBounds, &matrixProvider, false);
Michael Ludwiga195d102020-09-15 14:51:52 -0400310
311 if (policy == SavePolicy::kAtStart) {
312 cs.save();
313 }
314
315 for (int i : order) {
316 if (policy == SavePolicy::kBetweenEveryOp) {
317 cs.save();
318 }
Robert Phillips74b94322021-08-17 11:50:20 -0400319 const ClipStack::Element& e = fElements[i];
Michael Ludwiga195d102020-09-15 14:51:52 -0400320 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;
Robert Phillips74b94322021-08-17 11:50:20 -0400366 for (const ClipStack::Element& a : cs) {
Michael Ludwiga195d102020-09-15 14:51:52 -0400367 bool found = false;
Robert Phillips74b94322021-08-17 11:50:20 -0400368 for (const ClipStack::Element& e : fExpectedElements) {
Michael Ludwiga195d102020-09-15 14:51:52 -0400369 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) {
Robert Phillips74b94322021-08-17 11:50:20 -0400387 ClipStack::ClipState oldState = cs.clipState();
Michael Ludwiga195d102020-09-15 14:51:52 -0400388 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
Robert Phillips74b94322021-08-17 11:50:20 -0400398 REPORTER_ASSERT(reporter, cs.clipState() == ClipStack::ClipState::kWideOpen,
Michael Ludwiga195d102020-09-15 14:51:52 -0400399 "%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
Chris Daltonb1e8f852021-06-23 13:36:51 -0600475class NoOp : public GrDrawOp {
476public:
Chris Daltonea46ef32021-07-12 15:09:06 -0600477 static NoOp* Get() {
478 static NoOp gNoOp;
Chris Daltonb1e8f852021-06-23 13:36:51 -0600479 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
Michael Ludwiga195d102020-09-15 14:51:52 -0400494} // 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
Robert Phillips74b94322021-08-17 11:50:20 -0400501DEF_TEST(ClipStack_InitialState, r) {
Michael Ludwiga195d102020-09-15 14:51:52 -0400502 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.
Robert Phillips74b94322021-08-17 11:50:20 -0400507DEF_TEST(ClipStack_RectRectAACombine, r) {
508 using ClipState = skgpu::v1::ClipStack::ClipState;
509
Michael Ludwiga195d102020-09-15 14:51:52 -0400510 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()
Robert Phillips74b94322021-08-17 11:50:20 -0400527 .state(ClipState::kDeviceRect)
Michael Ludwiga195d102020-09-15 14:51:52 -0400528 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400536 .state(ClipState::kDeviceRect)
Michael Ludwiga195d102020-09-15 14:51:52 -0400537 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400545 .state(ClipState::kDeviceRect)
Michael Ludwiga195d102020-09-15 14:51:52 -0400546 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400554 .state(ClipState::kDeviceRect)
Michael Ludwiga195d102020-09-15 14:51:52 -0400555 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400563 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -0400564 .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.
Robert Phillips74b94322021-08-17 11:50:20 -0400569DEF_TEST(ClipStack_DifferenceNoCombine, r) {
570 using ClipState = skgpu::v1::ClipStack::ClipState;
571
Michael Ludwiga195d102020-09-15 14:51:52 -0400572 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()
Robert Phillips74b94322021-08-17 11:50:20 -0400581 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -0400582 .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.
Robert Phillips74b94322021-08-17 11:50:20 -0400587DEF_TEST(ClipStack_RectRectNonAxisAligned, r) {
588 using ClipState = skgpu::v1::ClipStack::ClipState;
589
Michael Ludwiga195d102020-09-15 14:51:52 -0400590 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()
Robert Phillips74b94322021-08-17 11:50:20 -0400608 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -0400609 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400618 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -0400619 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400627 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -0400628 .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.
Robert Phillips74b94322021-08-17 11:50:20 -0400633DEF_TEST(ClipStack_RRectRRectAACombine, r) {
634 using ClipState = skgpu::v1::ClipStack::ClipState;
635
Michael Ludwiga195d102020-09-15 14:51:52 -0400636 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()
Robert Phillips74b94322021-08-17 11:50:20 -0400648 .state(ClipState::kDeviceRRect)
Michael Ludwiga195d102020-09-15 14:51:52 -0400649 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400657 .state(ClipState::kDeviceRRect)
Michael Ludwiga195d102020-09-15 14:51:52 -0400658 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400666 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -0400667 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400677 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -0400678 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400685 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -0400686 .finishTest());
687}
688
689// Tests that intersection of a round rect and rect can simplify to a new round rect or even a rect.
Robert Phillips74b94322021-08-17 11:50:20 -0400690DEF_TEST(ClipStack_RectRRectCombine, r) {
691 using ClipState = skgpu::v1::ClipStack::ClipState;
692
Michael Ludwiga195d102020-09-15 14:51:52 -0400693 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()
Robert Phillips74b94322021-08-17 11:50:20 -0400704 .state(ClipState::kDeviceRRect)
Michael Ludwiga195d102020-09-15 14:51:52 -0400705 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400712 .state(ClipState::kDeviceRect)
Michael Ludwiga195d102020-09-15 14:51:52 -0400713 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400720 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -0400721 .finishTest());
722}
723
724// Tests that a rect shape is actually pre-clipped to the device bounds
Robert Phillips74b94322021-08-17 11:50:20 -0400725DEF_TEST(ClipStack_RectDeviceClip, r) {
726 using ClipState = skgpu::v1::ClipStack::ClipState;
727
Michael Ludwiga195d102020-09-15 14:51:52 -0400728 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()
Robert Phillips74b94322021-08-17 11:50:20 -0400735 .state(ClipState::kDeviceRect)
Michael Ludwiga195d102020-09-15 14:51:52 -0400736 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400741 .state(ClipState::kDeviceRect)
Michael Ludwiga195d102020-09-15 14:51:52 -0400742 .finishTest());
743}
744
745// Tests that other shapes' bounds are contained by the device bounds, even if their shape is not.
Robert Phillips74b94322021-08-17 11:50:20 -0400746DEF_TEST(ClipStack_ShapeDeviceBoundsClip, r) {
747 using ClipState = skgpu::v1::ClipStack::ClipState;
748
Michael Ludwiga195d102020-09-15 14:51:52 -0400749 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()
Robert Phillips74b94322021-08-17 11:50:20 -0400758 .state(ClipState::kDeviceRRect)
Michael Ludwiga195d102020-09-15 14:51:52 -0400759 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400767 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -0400768 .finishTest());
769}
770
771// Tests that a simplifiable path turns into a simpler element type
Robert Phillips74b94322021-08-17 11:50:20 -0400772DEF_TEST(ClipStack_PathSimplify, r) {
773 using ClipState = skgpu::v1::ClipStack::ClipState;
774
Michael Ludwiga195d102020-09-15 14:51:52 -0400775 // Empty, point, and line paths -> empty
776 SkPath empty;
777 run_test_case(r, TestCase::Build("empty", kDeviceBounds)
778 .actual().path(empty).finishElements()
Robert Phillips74b94322021-08-17 11:50:20 -0400779 .state(ClipState::kEmpty)
Michael Ludwiga195d102020-09-15 14:51:52 -0400780 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400785 .state(ClipState::kEmpty)
Michael Ludwiga195d102020-09-15 14:51:52 -0400786 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400793 .state(ClipState::kEmpty)
Michael Ludwiga195d102020-09-15 14:51:52 -0400794 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400803 .state(ClipState::kDeviceRect)
Michael Ludwiga195d102020-09-15 14:51:52 -0400804 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400812 .state(ClipState::kDeviceRRect)
Michael Ludwiga195d102020-09-15 14:51:52 -0400813 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400822 .state(ClipState::kDeviceRRect)
Michael Ludwiga195d102020-09-15 14:51:52 -0400823 .finishTest());
824}
825
826// Tests that repeated identical clip operations are idempotent
Robert Phillips74b94322021-08-17 11:50:20 -0400827DEF_TEST(ClipStack_RepeatElement, r) {
828 using ClipState = skgpu::v1::ClipStack::ClipState;
829
Michael Ludwiga195d102020-09-15 14:51:52 -0400830 // 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()
Robert Phillips74b94322021-08-17 11:50:20 -0400835 .state(ClipState::kDeviceRect)
Michael Ludwiga195d102020-09-15 14:51:52 -0400836 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400843 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -0400844 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400851 .state(ClipState::kDeviceRRect)
Michael Ludwiga195d102020-09-15 14:51:52 -0400852 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400857 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -0400858 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400865 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -0400866 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400873 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -0400874 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400887 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -0400888 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400894 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -0400895 .finishTest());
896}
897
898// Tests that inverse-filled paths are canonicalized to a regular fill and a swapped clip op
Robert Phillips74b94322021-08-17 11:50:20 -0400899DEF_TEST(ClipStack_InverseFilledPath, r) {
900 using ClipState = skgpu::v1::ClipStack::ClipState;
901
Michael Ludwiga195d102020-09-15 14:51:52 -0400902 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()
Robert Phillips74b94322021-08-17 11:50:20 -0400917 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -0400918 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400924 .state(ClipState::kDeviceRect)
Michael Ludwiga195d102020-09-15 14:51:52 -0400925 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400931 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -0400932 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400938 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -0400939 .finishTest());
940}
941
942// Tests that clip operations that are offscreen either make the clip empty or stay wide open
Robert Phillips74b94322021-08-17 11:50:20 -0400943DEF_TEST(ClipStack_Offscreen, r) {
944 using ClipState = skgpu::v1::ClipStack::ClipState;
945
Michael Ludwiga195d102020-09-15 14:51:52 -0400946 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()
Robert Phillips74b94322021-08-17 11:50:20 -0400960 .state(ClipState::kEmpty)
Michael Ludwiga195d102020-09-15 14:51:52 -0400961 .finishTest());
962 run_test_case(r, TestCase::Build("intersect-rect", kDeviceBounds)
963 .actual().aa().intersect()
964 .rect(offscreenRect)
965 .finishElements()
Robert Phillips74b94322021-08-17 11:50:20 -0400966 .state(ClipState::kEmpty)
Michael Ludwiga195d102020-09-15 14:51:52 -0400967 .finishTest());
968 run_test_case(r, TestCase::Build("intersect-rrect", kDeviceBounds)
969 .actual().aa().intersect()
970 .rrect(offscreenRRect)
971 .finishElements()
Robert Phillips74b94322021-08-17 11:50:20 -0400972 .state(ClipState::kEmpty)
Michael Ludwiga195d102020-09-15 14:51:52 -0400973 .finishTest());
974 run_test_case(r, TestCase::Build("intersect-path", kDeviceBounds)
975 .actual().aa().intersect()
976 .path(offscreenPath)
977 .finishElements()
Robert Phillips74b94322021-08-17 11:50:20 -0400978 .state(ClipState::kEmpty)
Michael Ludwiga195d102020-09-15 14:51:52 -0400979 .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()
Robert Phillips74b94322021-08-17 11:50:20 -0400988 .state(ClipState::kWideOpen)
Michael Ludwiga195d102020-09-15 14:51:52 -0400989 .finishTest());
990 run_test_case(r, TestCase::Build("difference-rect", kDeviceBounds)
991 .actual().aa().difference()
992 .rect(offscreenRect)
993 .finishElements()
Robert Phillips74b94322021-08-17 11:50:20 -0400994 .state(ClipState::kWideOpen)
Michael Ludwiga195d102020-09-15 14:51:52 -0400995 .finishTest());
996 run_test_case(r, TestCase::Build("difference-rrect", kDeviceBounds)
997 .actual().aa().difference()
998 .rrect(offscreenRRect)
999 .finishElements()
Robert Phillips74b94322021-08-17 11:50:20 -04001000 .state(ClipState::kWideOpen)
Michael Ludwiga195d102020-09-15 14:51:52 -04001001 .finishTest());
1002 run_test_case(r, TestCase::Build("difference-path", kDeviceBounds)
1003 .actual().aa().difference()
1004 .path(offscreenPath)
1005 .finishElements()
Robert Phillips74b94322021-08-17 11:50:20 -04001006 .state(ClipState::kWideOpen)
Michael Ludwiga195d102020-09-15 14:51:52 -04001007 .finishTest());
1008}
1009
1010// Tests that an empty shape updates the clip state directly without needing an element
Robert Phillips74b94322021-08-17 11:50:20 -04001011DEF_TEST(ClipStack_EmptyShape, r) {
1012 using ClipState = skgpu::v1::ClipStack::ClipState;
1013
Michael Ludwiga195d102020-09-15 14:51:52 -04001014 // Intersect -> empty
1015 run_test_case(r, TestCase::Build("empty-intersect", kDeviceBounds)
1016 .actual().intersect().rect(SkRect::MakeEmpty()).finishElements()
Robert Phillips74b94322021-08-17 11:50:20 -04001017 .state(ClipState::kEmpty)
Michael Ludwiga195d102020-09-15 14:51:52 -04001018 .finishTest());
1019
1020 // Difference -> no-op
1021 run_test_case(r, TestCase::Build("empty-difference", kDeviceBounds)
1022 .actual().difference().rect(SkRect::MakeEmpty()).finishElements()
Robert Phillips74b94322021-08-17 11:50:20 -04001023 .state(ClipState::kWideOpen)
Michael Ludwiga195d102020-09-15 14:51:52 -04001024 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001031 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001032 .finishTest());
1033}
1034
1035// Tests that sufficiently large difference operations can shrink the conservative bounds
Robert Phillips74b94322021-08-17 11:50:20 -04001036DEF_TEST(ClipStack_DifferenceBounds, r) {
1037 using ClipState = skgpu::v1::ClipStack::ClipState;
1038
Michael Ludwiga195d102020-09-15 14:51:52 -04001039 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()
Robert Phillips74b94322021-08-17 11:50:20 -04001046 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001047 .finishTest());
1048}
1049
1050// Tests that intersections can combine even if there's a difference operation in the middle
Robert Phillips74b94322021-08-17 11:50:20 -04001051DEF_TEST(ClipStack_NoDifferenceInterference, r) {
1052 using ClipState = skgpu::v1::ClipStack::ClipState;
1053
Michael Ludwiga195d102020-09-15 14:51:52 -04001054 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()
Robert Phillips74b94322021-08-17 11:50:20 -04001067 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001068 .finishTest());
1069}
1070
1071// Tests that multiple path operations are all recorded, but not otherwise consolidated
Robert Phillips74b94322021-08-17 11:50:20 -04001072DEF_TEST(ClipStack_MultiplePaths, r) {
1073 using ClipState = skgpu::v1::ClipStack::ClipState;
1074
Michael Ludwiga195d102020-09-15 14:51:52 -04001075 // Chosen to be greater than the number of inline-allocated elements and save records of the
Robert Phillips74b94322021-08-17 11:50:20 -04001076 // ClipStack so that we test heap allocation as well.
Michael Ludwiga195d102020-09-15 14:51:52 -04001077 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()
Robert Phillips74b94322021-08-17 11:50:20 -04001093 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001094 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001104 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001105 .finishTest());
1106}
1107
1108// Tests that a single rect is treated as kDeviceRect state when it's axis-aligned and intersect.
Robert Phillips74b94322021-08-17 11:50:20 -04001109DEF_TEST(ClipStack_DeviceRect, r) {
1110 using ClipState = skgpu::v1::ClipStack::ClipState;
1111
Michael Ludwiga195d102020-09-15 14:51:52 -04001112 // 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()
Robert Phillips74b94322021-08-17 11:50:20 -04001117 .state(ClipState::kDeviceRect)
Michael Ludwiga195d102020-09-15 14:51:52 -04001118 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001126 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001127 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001133 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001134 .finishTest());
1135}
1136
1137// Tests that a single rrect is treated as kDeviceRRect state when it's axis-aligned and intersect.
Robert Phillips74b94322021-08-17 11:50:20 -04001138DEF_TEST(ClipStack_DeviceRRect, r) {
1139 using ClipState = skgpu::v1::ClipStack::ClipState;
1140
Michael Ludwiga195d102020-09-15 14:51:52 -04001141 // 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()
Robert Phillips74b94322021-08-17 11:50:20 -04001147 .state(ClipState::kDeviceRRect)
Michael Ludwiga195d102020-09-15 14:51:52 -04001148 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001156 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001157 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001163 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001164 .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.
Robert Phillips74b94322021-08-17 11:50:20 -04001170DEF_TEST(ClipStack_ScaleTranslate, r) {
1171 using ClipState = skgpu::v1::ClipStack::ClipState;
1172
Michael Ludwiga195d102020-09-15 14:51:52 -04001173 SkMatrix lm = SkMatrix::Scale(2.f, 4.f);
1174 lm.postTranslate(15.5f, 14.3f);
Michael Ludwigd30e9ef2020-09-28 12:03:01 -04001175 SkASSERT(lm.preservesAxisAlignment() && lm.isScaleTranslate());
Michael Ludwiga195d102020-09-15 14:51:52 -04001176
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()
Robert Phillips74b94322021-08-17 11:50:20 -04001184 .state(ClipState::kDeviceRect)
Michael Ludwiga195d102020-09-15 14:51:52 -04001185 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001196 .state(ClipState::kDeviceRRect)
Michael Ludwiga195d102020-09-15 14:51:52 -04001197 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001204 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001205 .finishTest());
1206}
1207
Michael Ludwigd30e9ef2020-09-28 12:03:01 -04001208// Tests that rect-stays-rect matrices that are not scale+translate matrices are pre-applied.
Robert Phillips74b94322021-08-17 11:50:20 -04001209DEF_TEST(ClipStack_PreserveAxisAlignment, r) {
1210 using ClipState = skgpu::v1::ClipStack::ClipState;
1211
Michael Ludwigd30e9ef2020-09-28 12:03:01 -04001212 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()
Robert Phillips74b94322021-08-17 11:50:20 -04001223 .state(ClipState::kDeviceRect)
Michael Ludwigd30e9ef2020-09-28 12:03:01 -04001224 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001235 .state(ClipState::kDeviceRRect)
Michael Ludwigd30e9ef2020-09-28 12:03:01 -04001236 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001243 .state(ClipState::kComplex)
Michael Ludwigd30e9ef2020-09-28 12:03:01 -04001244 .finishTest());
1245}
1246
Michael Ludwiga195d102020-09-15 14:51:52 -04001247// Tests that a convex path element can contain a rect or round rect, allowing the stack to be
1248// simplified
Robert Phillips74b94322021-08-17 11:50:20 -04001249DEF_TEST(ClipStack_ConvexPathContains, r) {
1250 using ClipState = skgpu::v1::ClipStack::ClipState;
1251
Michael Ludwiga195d102020-09-15 14:51:52 -04001252 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()
Robert Phillips74b94322021-08-17 11:50:20 -04001260 .state(ClipState::kDeviceRect)
Michael Ludwiga195d102020-09-15 14:51:52 -04001261 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001265 .state(ClipState::kDeviceRRect)
Michael Ludwiga195d102020-09-15 14:51:52 -04001266 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001272 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001273 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001278 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001279 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001285 .state(ClipState::kEmpty)
Michael Ludwiga195d102020-09-15 14:51:52 -04001286 .finishTest());
1287 run_test_case(r, TestCase::Build("convex-diff+rrect-int", kDeviceBounds)
1288 .actual().aa().intersect().rrect(rrect)
1289 .difference().path(bigPath).finishElements()
Robert Phillips74b94322021-08-17 11:50:20 -04001290 .state(ClipState::kEmpty)
Michael Ludwiga195d102020-09-15 14:51:52 -04001291 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001298 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001299 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001304 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001305 .finishTest());
1306}
1307
1308// Tests that rects/rrects in different coordinate spaces can be consolidated when one is fully
1309// contained by the other.
Robert Phillips74b94322021-08-17 11:50:20 -04001310DEF_TEST(ClipStack_NonAxisAlignedContains, r) {
1311 using ClipState = skgpu::v1::ClipStack::ClipState;
1312
Michael Ludwiga195d102020-09-15 14:51:52 -04001313 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()
Robert Phillips74b94322021-08-17 11:50:20 -04001328 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001329 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001336 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001337 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001344 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001345 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001352 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001353 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001362 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001363 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001370 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001371 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001378 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001379 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001386 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001387 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001394 .state(ClipState::kEmpty)
Michael Ludwiga195d102020-09-15 14:51:52 -04001395 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001400 .state(ClipState::kEmpty)
Michael Ludwiga195d102020-09-15 14:51:52 -04001401 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001406 .state(ClipState::kEmpty)
Michael Ludwiga195d102020-09-15 14:51:52 -04001407 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001412 .state(ClipState::kEmpty)
Michael Ludwiga195d102020-09-15 14:51:52 -04001413 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001421 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001422 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001428 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001429 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001435 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001436 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001442 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001443 .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
Robert Phillips74b94322021-08-17 11:50:20 -04001448DEF_TEST(ClipStack_MixedAAContains, r) {
1449 using ClipState = skgpu::v1::ClipStack::ClipState;
1450
Michael Ludwiga195d102020-09-15 14:51:52 -04001451 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()
Robert Phillips74b94322021-08-17 11:50:20 -04001465 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001466 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001474 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001475 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001483 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001484 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001490 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001491 .finishTest());
1492}
1493
1494// Tests that a shape that contains the device bounds updates the clip state directly
Robert Phillips74b94322021-08-17 11:50:20 -04001495DEF_TEST(ClipStack_ShapeContainsDevice, r) {
1496 using ClipState = skgpu::v1::ClipStack::ClipState;
1497
Michael Ludwiga195d102020-09-15 14:51:52 -04001498 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()
Robert Phillips74b94322021-08-17 11:50:20 -04001505 .state(ClipState::kWideOpen)
Michael Ludwiga195d102020-09-15 14:51:52 -04001506 .finishTest());
1507 run_test_case(r, TestCase::Build("rrect-intersect", kDeviceBounds)
1508 .actual().intersect().rrect(rrect).finishElements()
Robert Phillips74b94322021-08-17 11:50:20 -04001509 .state(ClipState::kWideOpen)
Michael Ludwiga195d102020-09-15 14:51:52 -04001510 .finishTest());
1511 run_test_case(r, TestCase::Build("convex-intersect", kDeviceBounds)
1512 .actual().intersect().path(convex).finishElements()
Robert Phillips74b94322021-08-17 11:50:20 -04001513 .state(ClipState::kWideOpen)
Michael Ludwiga195d102020-09-15 14:51:52 -04001514 .finishTest());
1515
1516 // Difference -> empty
1517 run_test_case(r, TestCase::Build("rect-difference", kDeviceBounds)
1518 .actual().difference().rect(rect).finishElements()
Robert Phillips74b94322021-08-17 11:50:20 -04001519 .state(ClipState::kEmpty)
Michael Ludwiga195d102020-09-15 14:51:52 -04001520 .finishTest());
1521 run_test_case(r, TestCase::Build("rrect-difference", kDeviceBounds)
1522 .actual().difference().rrect(rrect).finishElements()
Robert Phillips74b94322021-08-17 11:50:20 -04001523 .state(ClipState::kEmpty)
Michael Ludwiga195d102020-09-15 14:51:52 -04001524 .finishTest());
1525 run_test_case(r, TestCase::Build("convex-difference", kDeviceBounds)
1526 .actual().difference().path(convex).finishElements()
Robert Phillips74b94322021-08-17 11:50:20 -04001527 .state(ClipState::kEmpty)
Michael Ludwiga195d102020-09-15 14:51:52 -04001528 .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).
Robert Phillips74b94322021-08-17 11:50:20 -04001533DEF_TEST(ClipStack_DisjointShapes, r) {
1534 using ClipState = skgpu::v1::ClipStack::ClipState;
1535
Michael Ludwiga195d102020-09-15 14:51:52 -04001536 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()
Robert Phillips74b94322021-08-17 11:50:20 -04001543 .state(ClipState::kEmpty)
Michael Ludwiga195d102020-09-15 14:51:52 -04001544 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001551 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001552 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001560 .state(ClipState::kDeviceRect)
Michael Ludwiga195d102020-09-15 14:51:52 -04001561 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001569 .state(ClipState::kDeviceRRect)
Michael Ludwiga195d102020-09-15 14:51:52 -04001570 .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()
Robert Phillips74b94322021-08-17 11:50:20 -04001578 .state(ClipState::kComplex)
Michael Ludwiga195d102020-09-15 14:51:52 -04001579 .finishTest());
1580}
1581
Robert Phillips74b94322021-08-17 11:50:20 -04001582DEF_TEST(ClipStack_ComplexClip, reporter) {
1583 using ClipStack = skgpu::v1::ClipStack;
1584
Michael Ludwiga195d102020-09-15 14:51:52 -04001585 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
Robert Phillips74b94322021-08-17 11:50:20 -04001644 ClipStack::ClipState state = ClipStack::ClipState::kComplex;
Michael Ludwiga195d102020-09-15 14:51:52 -04001645 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) {
Robert Phillips74b94322021-08-17 11:50:20 -04001657 state = ClipStack::ClipState::kDeviceRect;
Michael Ludwiga195d102020-09-15 14:51:52 -04001658 }
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) {
Robert Phillips74b94322021-08-17 11:50:20 -04001665 state = ClipStack::ClipState::kDeviceRRect;
Michael Ludwiga195d102020-09-15 14:51:52 -04001666 }
1667 }
1668 }
1669
John Stilesfa088a62021-08-12 23:01:41 -04001670 run_test_case(reporter, b.state(state).finishTest());
Michael Ludwiga195d102020-09-15 14:51:52 -04001671 }
1672 }
1673}
1674
1675// ///////////////////////////////////////////////////////////////////////////////
1676// // These tests do not use the TestCase infrastructure and manipulate a
Robert Phillips74b94322021-08-17 11:50:20 -04001677// // ClipStack directly.
Michael Ludwiga195d102020-09-15 14:51:52 -04001678
1679// Tests that replaceClip() works as expected across save/restores
Robert Phillips74b94322021-08-17 11:50:20 -04001680DEF_TEST(ClipStack_ReplaceClip, r) {
1681 using ClipStack = skgpu::v1::ClipStack;
1682
1683 ClipStack cs(kDeviceBounds, nullptr, false);
Michael Ludwiga195d102020-09-15 14:51:52 -04001684
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
Robert Phillips74b94322021-08-17 11:50:20 -04001692 REPORTER_ASSERT(r, cs.clipState() == ClipStack::ClipState::kDeviceRect,
Michael Ludwiga195d102020-09-15 14:51:52 -04001693 "Clip did not become a device rect");
1694 REPORTER_ASSERT(r, cs.getConservativeBounds() == replace, "Unexpected replaced clip bounds");
Robert Phillips74b94322021-08-17 11:50:20 -04001695 const ClipStack::Element& replaceElement = *cs.begin();
Michael Ludwiga195d102020-09-15 14:51:52 -04001696 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();
Robert Phillips74b94322021-08-17 11:50:20 -04001704 REPORTER_ASSERT(r, cs.clipState() == ClipStack::ClipState::kDeviceRRect,
Michael Ludwiga195d102020-09-15 14:51:52 -04001705 "Unexpected state after restore, not kDeviceRRect");
Robert Phillips74b94322021-08-17 11:50:20 -04001706 const ClipStack::Element& rrectElem = *cs.begin();
Michael Ludwiga195d102020-09-15 14:51:52 -04001707 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
Robert Phillipsc4fbc8d2020-11-30 10:17:53 -05001714// Try to overflow the number of allowed window rects (see skbug.com/10989)
Robert Phillips74b94322021-08-17 11:50:20 -04001715DEF_TEST(ClipStack_DiffRects, r) {
1716 using ClipStack = skgpu::v1::ClipStack;
1717 using SurfaceDrawContext = skgpu::v1::SurfaceDrawContext;
1718
Robert Phillipsc4fbc8d2020-11-30 10:17:53 -05001719 GrMockOptions options;
1720 options.fMaxWindowRectangles = 8;
1721
1722 SkSimpleMatrixProvider matrixProvider = SkMatrix::I();
1723 sk_sp<GrDirectContext> context = GrDirectContext::MakeMock(&options);
Robert Phillips74b94322021-08-17 11:50:20 -04001724 std::unique_ptr<SurfaceDrawContext> sdc = SurfaceDrawContext::Make(
Robert Phillipsc4fbc8d2020-11-30 10:17:53 -05001725 context.get(), GrColorType::kRGBA_8888, SkColorSpace::MakeSRGB(),
Chris Daltonf5b87f92021-04-19 17:27:09 -06001726 SkBackingFit::kExact, kDeviceBounds.size(), SkSurfaceProps());
Robert Phillipsc4fbc8d2020-11-30 10:17:53 -05001727
Robert Phillips74b94322021-08-17 11:50:20 -04001728 ClipStack cs(kDeviceBounds, &matrixProvider, false);
Robert Phillipsc4fbc8d2020-11-30 10:17:53 -05001729
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);
Robert Phillips4dca8312021-07-28 15:13:20 -04001740 GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage,
Chris Daltonb1e8f852021-06-23 13:36:51 -06001741 &out, &drawBounds);
Robert Phillipsc4fbc8d2020-11-30 10:17:53 -05001742
1743 REPORTER_ASSERT(r, effect == GrClip::Effect::kClipped);
1744 REPORTER_ASSERT(r, out.windowRectsState().numWindows() == 8);
1745
1746 cs.restore();
1747}
1748
Michael Ludwiga195d102020-09-15 14:51:52 -04001749// Tests that when a stack is forced to always be AA, non-AA elements become AA
Robert Phillips74b94322021-08-17 11:50:20 -04001750DEF_TEST(ClipStack_ForceAA, r) {
1751 using ClipStack = skgpu::v1::ClipStack;
1752
1753 ClipStack cs(kDeviceBounds, nullptr, true);
Michael Ludwiga195d102020-09-15 14:51:52 -04001754
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
Michael Ludwig462bdfc2020-09-22 16:27:04 -04001763 // Non-AA rects remain non-AA so they can be applied as a scissor
Michael Ludwiga195d102020-09-15 14:51:52 -04001764 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.
Michael Ludwiga195d102020-09-15 14:51:52 -04001769 auto elements = cs.begin();
1770
Robert Phillips74b94322021-08-17 11:50:20 -04001771 const ClipStack::Element& nonAARectElement = *elements;
Michael Ludwig462bdfc2020-09-22 16:27:04 -04001772 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");
Michael Ludwiga195d102020-09-15 14:51:52 -04001777
1778 ++elements;
Robert Phillips74b94322021-08-17 11:50:20 -04001779 const ClipStack::Element& aaPathElement = *elements;
Michael Ludwig462bdfc2020-09-22 16:27:04 -04001780 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");
Michael Ludwiga195d102020-09-15 14:51:52 -04001783
1784 ++elements;
Robert Phillips74b94322021-08-17 11:50:20 -04001785 const ClipStack::Element& aaRectElement = *elements;
Michael Ludwig462bdfc2020-09-22 16:27:04 -04001786 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");
Michael Ludwiga195d102020-09-15 14:51:52 -04001793}
1794
1795// Tests preApply works as expected for device rects, rrects, and reports clipped-out, etc. as
1796// expected.
Robert Phillips74b94322021-08-17 11:50:20 -04001797DEF_TEST(ClipStack_PreApply, r) {
1798 using ClipStack = skgpu::v1::ClipStack;
1799
1800 ClipStack cs(kDeviceBounds, nullptr, false);
Michael Ludwiga195d102020-09-15 14:51:52 -04001801
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
Robert Phillips74b94322021-08-17 11:50:20 -04001870DEF_TEST(ClipStack_Shader, r) {
1871 using ClipStack = skgpu::v1::ClipStack;
1872 using SurfaceDrawContext = skgpu::v1::SurfaceDrawContext;
1873
Michael Ludwiga195d102020-09-15 14:51:52 -04001874 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);
Robert Phillips74b94322021-08-17 11:50:20 -04001878 std::unique_ptr<SurfaceDrawContext> sdc = SurfaceDrawContext::Make(
Michael Ludwiga195d102020-09-15 14:51:52 -04001879 context.get(), GrColorType::kRGBA_8888, SkColorSpace::MakeSRGB(),
Chris Daltonf5b87f92021-04-19 17:27:09 -06001880 SkBackingFit::kExact, kDeviceBounds.size(), SkSurfaceProps());
Michael Ludwiga195d102020-09-15 14:51:52 -04001881
Robert Phillips74b94322021-08-17 11:50:20 -04001882 ClipStack cs(kDeviceBounds, &matrixProvider, false);
Michael Ludwiga195d102020-09-15 14:51:52 -04001883 cs.save();
1884 cs.clipShader(shader);
1885
Robert Phillips74b94322021-08-17 11:50:20 -04001886 REPORTER_ASSERT(r, cs.clipState() == ClipStack::ClipState::kComplex,
Michael Ludwiga195d102020-09-15 14:51:52 -04001887 "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};
Robert Phillips4dca8312021-07-28 15:13:20 -04001891 GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage,
Chris Daltonb1e8f852021-06-23 13:36:51 -06001892 &out, &drawBounds);
Michael Ludwiga195d102020-09-15 14:51:52 -04001893
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
Robert Phillips4dca8312021-07-28 15:13:20 -04001901 effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage, &out2,
Chris Daltonb1e8f852021-06-23 13:36:51 -06001902 &drawBounds);
Michael Ludwiga195d102020-09-15 14:51:52 -04001903 REPORTER_ASSERT(r, effect == GrClip::Effect::kClippedOut,
1904 "apply() should still discard offscreen draws with a clip shader");
1905
1906 cs.restore();
Robert Phillips74b94322021-08-17 11:50:20 -04001907 REPORTER_ASSERT(r, cs.clipState() == ClipStack::ClipState::kWideOpen,
Michael Ludwiga195d102020-09-15 14:51:52 -04001908 "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);
Robert Phillips74b94322021-08-17 11:50:20 -04001914 SkASSERT(cs.clipState() == ClipStack::ClipState::kDeviceRect); // test precondition
Michael Ludwiga195d102020-09-15 14:51:52 -04001915 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.
Robert Phillips74b94322021-08-17 11:50:20 -04001925DEF_TEST(ClipStack_SimpleApply, r) {
1926 using ClipStack = skgpu::v1::ClipStack;
1927 using SurfaceDrawContext = skgpu::v1::SurfaceDrawContext;
1928
Michael Ludwiga195d102020-09-15 14:51:52 -04001929 SkSimpleMatrixProvider matrixProvider = SkMatrix::I();
1930 sk_sp<GrDirectContext> context = GrDirectContext::MakeMock(nullptr);
Robert Phillips74b94322021-08-17 11:50:20 -04001931 std::unique_ptr<SurfaceDrawContext> sdc = SurfaceDrawContext::Make(
Michael Ludwiga195d102020-09-15 14:51:52 -04001932 context.get(), GrColorType::kRGBA_8888, SkColorSpace::MakeSRGB(),
Chris Daltonf5b87f92021-04-19 17:27:09 -06001933 SkBackingFit::kExact, kDeviceBounds.size(), SkSurfaceProps());
Michael Ludwiga195d102020-09-15 14:51:52 -04001934
Robert Phillips74b94322021-08-17 11:50:20 -04001935 ClipStack cs(kDeviceBounds, &matrixProvider, false);
Michael Ludwiga195d102020-09-15 14:51:52 -04001936
1937 // Offscreen draw is kClippedOut
1938 {
1939 SkRect drawBounds = {-15.f, -15.f, -1.f, -1.f};
1940
1941 GrAppliedClip out(kDeviceBounds.size());
Robert Phillips4dca8312021-07-28 15:13:20 -04001942 GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage,
Chris Daltonb1e8f852021-06-23 13:36:51 -06001943 &out, &drawBounds);
Michael Ludwiga195d102020-09-15 14:51:52 -04001944 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());
Robert Phillips4dca8312021-07-28 15:13:20 -04001955 GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage,
Chris Daltonb1e8f852021-06-23 13:36:51 -06001956 &out, &drawBounds);
Michael Ludwiga195d102020-09-15 14:51:52 -04001957 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());
Robert Phillips4dca8312021-07-28 15:13:20 -04001970 GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage,
Chris Daltonb1e8f852021-06-23 13:36:51 -06001971 &out, &drawRect);
Michael Ludwiga195d102020-09-15 14:51:52 -04001972 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());
Robert Phillips4dca8312021-07-28 15:13:20 -04001989 GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage,
Chris Daltonb1e8f852021-06-23 13:36:51 -06001990 &out, &drawRect);
Michael Ludwiga195d102020-09-15 14:51:52 -04001991 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());
Robert Phillips4dca8312021-07-28 15:13:20 -04002005 GrClip::Effect effect = cs.apply(context.get(), sdc.get(), NoOp::Get(), GrAAType::kCoverage,
Chris Daltonb1e8f852021-06-23 13:36:51 -06002006 &out, &drawBounds);
Michael Ludwiga195d102020-09-15 14:51:52 -04002007 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
Chris Daltone8f66632021-06-16 11:59:22 -06002051// Must disable tessellation in order to trigger SW mask generation when the clip stack is applied.
2052static void disable_tessellation_atlas(GrContextOptions* options) {
Michael Ludwiga195d102020-09-15 14:51:52 -04002053 options->fGpuPathRenderers = GpuPathRenderers::kNone;
Chris Daltone8f66632021-06-16 11:59:22 -06002054 options->fAvoidStencilBuffers = true;
Michael Ludwiga195d102020-09-15 14:51:52 -04002055}
2056
Robert Phillips74b94322021-08-17 11:50:20 -04002057DEF_GPUTEST_FOR_CONTEXTS(ClipStack_SWMask,
Michael Ludwiga195d102020-09-15 14:51:52 -04002058 sk_gpu_test::GrContextFactory::IsRenderingContext,
Chris Daltone8f66632021-06-16 11:59:22 -06002059 r, ctxInfo, disable_tessellation_atlas) {
Robert Phillips74b94322021-08-17 11:50:20 -04002060 using ClipStack = skgpu::v1::ClipStack;
2061 using SurfaceDrawContext = skgpu::v1::SurfaceDrawContext;
2062
Michael Ludwiga195d102020-09-15 14:51:52 -04002063 GrDirectContext* context = ctxInfo.directContext();
Robert Phillips74b94322021-08-17 11:50:20 -04002064 std::unique_ptr<SurfaceDrawContext> sdc = SurfaceDrawContext::Make(
Chris Daltonf5b87f92021-04-19 17:27:09 -06002065 context, GrColorType::kRGBA_8888, nullptr, SkBackingFit::kExact, kDeviceBounds.size(),
2066 SkSurfaceProps());
Michael Ludwiga195d102020-09-15 14:51:52 -04002067
2068 SkSimpleMatrixProvider matrixProvider = SkMatrix::I();
Robert Phillips74b94322021-08-17 11:50:20 -04002069 std::unique_ptr<ClipStack> cs(new ClipStack(kDeviceBounds, &matrixProvider, false));
Michael Ludwiga195d102020-09-15 14:51:52 -04002070
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});
Robert Phillips4dca8312021-07-28 15:13:20 -04002084 sdc->drawRect(cs.get(), std::move(paint), GrAA::kYes, SkMatrix::I(), drawBounds);
Michael Ludwiga195d102020-09-15 14:51:52 -04002085 };
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}