Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
GrClipStackTest.cpp
Go to the documentation of this file.
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
12#include "include/core/SkPath.h"
16#include "include/core/SkRect.h"
30#include "src/gpu/ResourceKey.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
56class GrCaps;
57class GrDstProxyView;
58class GrOpFlushState;
61enum class GrXferBarrierFlags;
62
63namespace {
64
65class TestCaseBuilder;
66
67enum 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
76class TestCase {
77public:
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
98 const SkIRect& deviceBounds() const { return fDeviceBounds; }
99 ClipStack::ClipState expectedState() const { return fExpectedState; }
100 const std::vector<ClipStack::Element>& initialElements() const { return fElements; }
101 const std::vector<ClipStack::Element>& expectedElements() const { return fExpectedElements; }
102
103private:
104 friend class TestCaseBuilder;
105
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
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
136class ElementsBuilder {
137public:
138 using ClipStack = skgpu::ganesh::ClipStack;
139
140 // Update the default matrix, aa, and op state for elements that are added.
141 ElementsBuilder& localToDevice(const SkMatrix& m) { fLocalToDevice = m; return *this; }
142 ElementsBuilder& aa() { fAA = GrAA::kYes; return *this; }
143 ElementsBuilder& nonAA() { fAA = GrAA::kNo; return *this; }
144 ElementsBuilder& intersect() { fOp = SkClipOp::kIntersect; return *this; }
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.
149 ElementsBuilder& rect(const SkRect& rect) {
150 return this->rect(rect, fLocalToDevice, fAA, fOp);
151 }
152 ElementsBuilder& rect(const SkRect& rect, GrAA aa, SkClipOp op) {
153 return this->rect(rect, fLocalToDevice, aa, op);
154 }
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
160 ElementsBuilder& rrect(const SkRRect& rrect) {
161 return this->rrect(rrect, fLocalToDevice, fAA, fOp);
162 }
163 ElementsBuilder& rrect(const SkRRect& rrect, GrAA aa, SkClipOp op) {
164 return this->rrect(rrect, fLocalToDevice, aa, op);
165 }
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
171 ElementsBuilder& path(const SkPath& path) {
172 return this->path(path, fLocalToDevice, fAA, fOp);
173 }
174 ElementsBuilder& path(const SkPath& path, GrAA aa, SkClipOp op) {
175 return this->path(path, fLocalToDevice, aa, op);
176 }
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
183 TestCaseBuilder& finishElements() {
184 return *fBuilder;
185 }
186
187private:
188 friend class TestCaseBuilder;
189
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;
197
198 TestCaseBuilder* fBuilder;
199 std::vector<ClipStack::Element>* fElements;
200};
201
202class TestCaseBuilder {
203public:
204 using ClipStack = skgpu::ganesh::ClipStack;
205
206 ElementsBuilder actual() { return ElementsBuilder(this, &fActualElements); }
207 ElementsBuilder expect() { return ElementsBuilder(this, &fExpectedElements); }
208
209 TestCaseBuilder& expectActual() {
210 fExpectedElements = fActualElements;
211 return *this;
212 }
213
214 TestCaseBuilder& state(ClipStack::ClipState state) {
215 fExpectedState = state;
216 return *this;
217 }
218
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
227private:
228 friend class TestCase;
229
230 explicit TestCaseBuilder(const char* name, const SkIRect& deviceBounds)
231 : fName(name)
232 , fDeviceBounds(deviceBounds)
233 , fExpectedState(ClipStack::ClipState::kWideOpen) {}
234
236 SkIRect fDeviceBounds;
237 ClipStack::ClipState fExpectedState;
238
239 std::vector<ClipStack::Element> fActualElements;
240 std::vector<ClipStack::Element> fExpectedElements;
241};
242
243TestCaseBuilder TestCase::Build(const char* name, const SkIRect& deviceBounds) {
244 return TestCaseBuilder(name, deviceBounds);
245}
246
247SkString TestCase::getTestName(const std::vector<int>& order, SavePolicy policy) const {
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
277std::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) {
292 expectOptimal &= e.fLocalToDevice.isIdentity();
293 elementBounds = GrClip::GetPixelIBounds(e.fLocalToDevice.mapRect(e.fShape.bounds()),
295 } else {
297 expectOptimal = false;
298 if (e.fShape.isRect() && e.fLocalToDevice.isIdentity()) {
299 elementBounds = GrClip::GetPixelIBounds(e.fShape.rect(), e.fAA,
301 } else if (e.fShape.isRRect() && e.fLocalToDevice.isIdentity()) {
302 elementBounds = GrClip::GetPixelIBounds(SkRRectPriv::InnerBounds(e.fShape.rrect()),
304 } else {
305 elementBounds = SkIRect::MakeEmpty();
306 }
307 }
308
309 region.op(SkRegion(elementBounds), op);
310 }
311 return {region.getBounds(), expectOptimal};
312}
313
314static bool compare_elements(const skgpu::ganesh::ClipStack::Element& a,
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()) {
322 return a.fShape.rect() == b.fShape.rect();
324 return a.fShape.rrect() == b.fShape.rrect();
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
339void TestCase::run(const std::vector<int>& order,
340 SavePolicy policy,
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()) {
357 cs.clipRect(e.fLocalToDevice, e.fShape.rect(), e.fAA, e.fOp);
358 break;
360 cs.clipRRect(e.fLocalToDevice, e.fShape.rrect(), e.fAA, e.fOp);
361 break;
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
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.
442static 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
489static 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
503static 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
509static constexpr SkIRect kDeviceBounds = {0, 0, 100, 100};
510
511class NoOp : public GrDrawOp {
512public:
513 static NoOp* Get() {
514 static NoOp gNoOp;
515 return &gNoOp;
516 }
517private:
519 NoOp() : GrDrawOp(ClassID()) {}
520 const char* name() const override { return "NoOp"; }
521 GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, GrClampType) override {
523 }
524 void onPrePrepare(GrRecordingContext*, const GrSurfaceProxyView&, GrAppliedClip*, const
526 void onPrepare(GrOpFlushState*) override {}
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
537DEF_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.
543DEF_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.
605DEF_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.
623DEF_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.
669DEF_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
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.
726DEF_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
761DEF_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.
782DEF_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
808DEF_TEST(ClipStack_PathSimplify, r) {
809 using ClipState = skgpu::ganesh::ClipStack::ClipState;
810
811 // Empty, point, and line paths -> 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
863DEF_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
935DEF_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
979DEF_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
1047DEF_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
1072DEF_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
1087DEF_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
1108DEF_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.
1145DEF_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.
1174DEF_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.
1206DEF_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);
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.
1245DEF_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);
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
1285DEF_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.
1346DEF_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
1484DEF_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
1531DEF_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).
1569DEF_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
1618DEF_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
1716DEF_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)
1751DEF_TEST(ClipStack_DiffRects, r) {
1752 using ClipStack = skgpu::ganesh::ClipStack;
1754
1757
1759 std::unique_ptr<SurfaceDrawContext> sdc = SurfaceDrawContext::Make(
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),
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
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
1786DEF_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.
1833DEF_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);
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);
1846 "Wide open screen intersection is still kUnclipped");
1847
1848 // Empty clip is clipped out
1849 cs.save();
1851 result = cs.preApply({0.f, 0.f, 20.f, 20.f}, GrAA::kYes);
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);
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);
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);
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);
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
1906DEF_TEST(ClipStack_Shader, r) {
1907 using ClipStack = skgpu::ganesh::ClipStack;
1909
1910 sk_sp<SkShader> shader = SkShaders::Color({0.f, 0.f, 0.f, 0.5f}, nullptr);
1911
1913 std::unique_ptr<SurfaceDrawContext> sdc = SurfaceDrawContext::Make(
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
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);
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.
1961DEF_TEST(ClipStack_SimpleApply, r) {
1962 using ClipStack = skgpu::ganesh::ClipStack;
1964
1966 std::unique_ptr<SurfaceDrawContext> sdc = SurfaceDrawContext::Make(
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),
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");
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,
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,
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.
2089 options->fGpuPathRenderers = GpuPathRenderers::kNone;
2090 options->fAvoidStencilBuffers = true;
2091}
2092
2095 r,
2096 ctxInfo,
2099 using ClipStack = skgpu::ganesh::ClipStack;
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}
const char * options
#define test(name)
const char * fName
reporter
static bool intersect(const SkPoint &p0, const SkPoint &n0, const SkPoint &p1, const SkPoint &n1, SkScalar *t)
static void disable_tessellation_atlas(GrContextOptions *options)
#define DEFINE_OP_CLASS_ID
Definition GrOp.h:64
GrClampType
GrAA
GrLoadOp
GrXferBarrierFlags
static const uint16_t kTL
static const uint16_t kBL
static const uint16_t kBR
static const uint16_t kTR
#define SkAssertResult(cond)
Definition SkAssert.h:123
#define SkDEBUGFAIL(message)
Definition SkAssert.h:118
#define SkASSERT(cond)
Definition SkAssert.h:116
SkClipOp
Definition SkClipOp.h:13
static bool contains(const SkRect &r, SkPoint p)
static size_t difference(size_t minuend, size_t subtrahend)
@ kLine_SkPathSegmentMask
Definition SkPathTypes.h:42
@ kRRect
void swap(sk_sp< T > &a, sk_sp< T > &b)
Definition SkRefCnt.h:341
constexpr auto kNever
Definition SkSLTest.cpp:958
static constexpr bool SkToBool(const T &x)
Definition SkTo.h:35
#define DEF_TEST(name, reporter)
Definition Test.h:312
#define REPORTER_ASSERT(r, cond,...)
Definition Test.h:286
#define DEF_GANESH_TEST_FOR_CONTEXTS( name, context_filter, reporter, context_info, options_filter, ctsEnforcement)
Definition Test.h:426
Type::kYUV Type::kRGBA() int(0.7 *637)
static void run_test_case(const SkString &testdata, const SkBitmap &bitmap, SkCanvas *canvas)
constexpr SkRect kRect
static SkIRect GetPixelIBounds(const SkRect &bounds, GrAA aa, BoundsType mode=BoundsType::kExterior)
Definition GrClip.h:173
Effect
Definition GrClip.h:31
GrResourceCache * getResourceCache()
static sk_sp< GrDirectContext > MakeMock(const GrMockOptions *, const GrContextOptions &)
GrSemaphoresSubmitted flush(const GrFlushInfo &info)
GrDirectContextPriv priv()
static constexpr Analysis EmptySetAnalysis()
sk_sp< GrTextureProxy > findOrCreateProxyByUniqueKey(const skgpu::UniqueKey &, UseAllocator=UseAllocator::kYes)
GrProxyProvider * proxyProvider()
static sk_sp< SkColorSpace > MakeSRGB()
static SkMatrix Scale(SkScalar sx, SkScalar sy)
Definition SkMatrix.h:75
SkMatrix & postTranslate(SkScalar dx, SkScalar dy)
Definition SkMatrix.cpp:281
static SkMatrix RotateDeg(SkScalar deg)
Definition SkMatrix.h:104
SkMatrix & setRotate(SkScalar degrees, SkScalar px, SkScalar py)
Definition SkMatrix.cpp:452
static const SkMatrix & I()
bool isScaleTranslate() const
Definition SkMatrix.h:236
bool preservesAxisAlignment() const
Definition SkMatrix.h:299
bool mapRect(SkRect *dst, const SkRect &src, SkApplyPerspectiveClip pc=SkApplyPerspectiveClip::kYes) const
SkPath & moveTo(SkScalar x, SkScalar y)
Definition SkPath.cpp:678
SkPath & addRRect(const SkRRect &rrect, SkPathDirection dir=SkPathDirection::kCW)
Definition SkPath.cpp:990
void toggleInverseFillType()
Definition SkPath.h:249
SkPath & addOval(const SkRect &oval, SkPathDirection dir=SkPathDirection::kCW)
Definition SkPath.cpp:1101
SkPath & addRect(const SkRect &rect, SkPathDirection dir, unsigned start)
Definition SkPath.cpp:854
static SkRect InnerBounds(const SkRRect &rr)
Definition SkRRect.cpp:750
static SkRRect ConservativeIntersect(const SkRRect &a, const SkRRect &b)
Definition SkRRect.cpp:812
static SkRRect MakeOval(const SkRect &oval)
Definition SkRRect.h:162
bool transform(const SkMatrix &matrix, SkRRect *dst) const
Definition SkRRect.cpp:436
static SkRRect MakeRect(const SkRect &r)
Definition SkRRect.h:149
static SkRRect MakeRectXY(const SkRect &rect, SkScalar xRad, SkScalar yRad)
Definition SkRRect.h:180
void setRectRadii(const SkRect &rect, const SkVector radii[4])
Definition SkRRect.cpp:189
bool isEmpty() const
Definition SkRRect.h:83
SkRRect makeOffset(SkScalar dx, SkScalar dy) const
Definition SkRRect.h:397
@ kIntersect_Op
target intersected with operand
Definition SkRegion.h:368
@ kDifference_Op
target minus operand
Definition SkRegion.h:367
const SkIRect & getBounds() const
Definition SkRegion.h:165
bool op(const SkIRect &rect, Op op)
Definition SkRegion.h:384
const char * c_str() const
Definition SkString.h:133
void void void appendf(const char format[],...) SK_PRINTF_LIKE(2
Definition SkString.cpp:550
T * get() const
Definition SkRefCnt.h:303
static std::unique_ptr< SurfaceDrawContext > Make(GrRecordingContext *, GrColorType, sk_sp< GrSurfaceProxy >, sk_sp< SkColorSpace >, GrSurfaceOrigin, const SkSurfaceProps &)
const Paint & paint
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE auto & d
Definition main.cc:19
float SkScalar
Definition extension.cpp:12
static bool b
struct MyStruct a[10]
EMSCRIPTEN_KEEPALIVE void empty()
AtkStateType state
GAsyncResult * result
const char * name
Definition fuchsia.cc:50
double y
double x
const GrXPFactory * Get(SkBlendMode mode)
ClipOpAndAA opAA SkRegion region
Definition SkRecords.h:238
SkRRect rrect
Definition SkRecords.h:232
sk_sp< SkBlender > blender SkRect rect
Definition SkRecords.h:350
Build(configs, env, options)
Definition build.py:232
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition switches.h:57
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network policy
Definition switches.h:248
Definition run.py:1
bool IsRenderingContext(skgpu::ContextType type)
Definition ref_ptr.h:256
int fMaxWindowRectangles
int32_t fBottom
larger y-axis bounds
Definition SkRect.h:36
int32_t fTop
smaller y-axis bounds
Definition SkRect.h:34
static constexpr SkIRect MakeEmpty()
Definition SkRect.h:45
static constexpr SkIRect MakeWH(int32_t w, int32_t h)
Definition SkRect.h:56
int32_t fLeft
smaller x-axis bounds
Definition SkRect.h:33
bool contains(int32_t x, int32_t y) const
Definition SkRect.h:463
int32_t fRight
larger x-axis bounds
Definition SkRect.h:35
static SkRect Make(const SkISize &size)
Definition SkRect.h:669
static constexpr SkRect MakeEmpty()
Definition SkRect.h:595
SkScalar fBottom
larger y-axis bounds
Definition extension.cpp:17
void inset(float dx, float dy)
Definition SkRect.h:1060
constexpr SkRect makeOffset(float dx, float dy) const
Definition SkRect.h:965
bool intersect(const SkRect &r)
Definition SkRect.cpp:114
SkScalar fLeft
smaller x-axis bounds
Definition extension.cpp:14
SkRect makeOutset(float dx, float dy) const
Definition SkRect.h:1002
static constexpr SkRect MakeXYWH(float x, float y, float w, float h)
Definition SkRect.h:659
bool intersects(const SkRect &r) const
Definition SkRect.h:1121
SkScalar fRight
larger x-axis bounds
Definition extension.cpp:16
void offset(float dx, float dy)
Definition SkRect.h:1016
constexpr float height() const
Definition SkRect.h:769
constexpr float width() const
Definition SkRect.h:762
bool isEmpty() const
Definition SkRect.h:693
static constexpr SkRect MakeWH(float w, float h)
Definition SkRect.h:609
SkScalar fTop
smaller y-axis bounds
Definition extension.cpp:15