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