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