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