1 2 /* 3 * Copyright 2011 Google Inc. 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 #include "Test.h" 9 #if SK_SUPPORT_GPU 10 #include "GrReducedClip.h" 11 #endif 12 #include "SkClipStack.h" 13 #include "SkPath.h" 14 #include "SkRandom.h" 15 #include "SkRect.h" 16 #include "SkRegion.h" 17 18 19 static void test_assign_and_comparison(skiatest::Reporter* reporter) { 20 SkClipStack s; 21 bool doAA = false; 22 23 REPORTER_ASSERT(reporter, 0 == s.getSaveCount()); 24 25 // Build up a clip stack with a path, an empty clip, and a rect. 26 s.save(); 27 REPORTER_ASSERT(reporter, 1 == s.getSaveCount()); 28 29 SkPath p; 30 p.moveTo(5, 6); 31 p.lineTo(7, 8); 32 p.lineTo(5, 9); 33 p.close(); 34 s.clipDevPath(p, SkRegion::kIntersect_Op, doAA); 35 36 s.save(); 37 REPORTER_ASSERT(reporter, 2 == s.getSaveCount()); 38 39 SkRect r = SkRect::MakeLTRB(1, 2, 3, 4); 40 s.clipDevRect(r, SkRegion::kIntersect_Op, doAA); 41 r = SkRect::MakeLTRB(10, 11, 12, 13); 42 s.clipDevRect(r, SkRegion::kIntersect_Op, doAA); 43 44 s.save(); 45 REPORTER_ASSERT(reporter, 3 == s.getSaveCount()); 46 47 r = SkRect::MakeLTRB(14, 15, 16, 17); 48 s.clipDevRect(r, SkRegion::kUnion_Op, doAA); 49 50 // Test that assignment works. 51 SkClipStack copy = s; 52 REPORTER_ASSERT(reporter, s == copy); 53 54 // Test that different save levels triggers not equal. 55 s.restore(); 56 REPORTER_ASSERT(reporter, 2 == s.getSaveCount()); 57 REPORTER_ASSERT(reporter, s != copy); 58 59 // Test that an equal, but not copied version is equal. 60 s.save(); 61 REPORTER_ASSERT(reporter, 3 == s.getSaveCount()); 62 63 r = SkRect::MakeLTRB(14, 15, 16, 17); 64 s.clipDevRect(r, SkRegion::kUnion_Op, doAA); 65 REPORTER_ASSERT(reporter, s == copy); 66 67 // Test that a different op on one level triggers not equal. 68 s.restore(); 69 REPORTER_ASSERT(reporter, 2 == s.getSaveCount()); 70 s.save(); 71 REPORTER_ASSERT(reporter, 3 == s.getSaveCount()); 72 73 r = SkRect::MakeLTRB(14, 15, 16, 17); 74 s.clipDevRect(r, SkRegion::kIntersect_Op, doAA); 75 REPORTER_ASSERT(reporter, s != copy); 76 77 // Test that different state (clip type) triggers not equal. 78 // NO LONGER VALID: if a path contains only a rect, we turn 79 // it into a bare rect for performance reasons (working 80 // around Chromium/JavaScript bad pattern). 81 /* 82 s.restore(); 83 s.save(); 84 SkPath rp; 85 rp.addRect(r); 86 s.clipDevPath(rp, SkRegion::kUnion_Op, doAA); 87 REPORTER_ASSERT(reporter, s != copy); 88 */ 89 90 // Test that different rects triggers not equal. 91 s.restore(); 92 REPORTER_ASSERT(reporter, 2 == s.getSaveCount()); 93 s.save(); 94 REPORTER_ASSERT(reporter, 3 == s.getSaveCount()); 95 96 r = SkRect::MakeLTRB(24, 25, 26, 27); 97 s.clipDevRect(r, SkRegion::kUnion_Op, doAA); 98 REPORTER_ASSERT(reporter, s != copy); 99 100 // Sanity check 101 s.restore(); 102 REPORTER_ASSERT(reporter, 2 == s.getSaveCount()); 103 104 copy.restore(); 105 REPORTER_ASSERT(reporter, 2 == copy.getSaveCount()); 106 REPORTER_ASSERT(reporter, s == copy); 107 s.restore(); 108 REPORTER_ASSERT(reporter, 1 == s.getSaveCount()); 109 copy.restore(); 110 REPORTER_ASSERT(reporter, 1 == copy.getSaveCount()); 111 REPORTER_ASSERT(reporter, s == copy); 112 113 // Test that different paths triggers not equal. 114 s.restore(); 115 REPORTER_ASSERT(reporter, 0 == s.getSaveCount()); 116 s.save(); 117 REPORTER_ASSERT(reporter, 1 == s.getSaveCount()); 118 119 p.addRect(r); 120 s.clipDevPath(p, SkRegion::kIntersect_Op, doAA); 121 REPORTER_ASSERT(reporter, s != copy); 122 } 123 124 static void assert_count(skiatest::Reporter* reporter, const SkClipStack& stack, 125 int count) { 126 SkClipStack::B2TIter iter(stack); 127 int counter = 0; 128 while (iter.next()) { 129 counter += 1; 130 } 131 REPORTER_ASSERT(reporter, count == counter); 132 } 133 134 // Exercise the SkClipStack's bottom to top and bidirectional iterators 135 // (including the skipToTopmost functionality) 136 static void test_iterators(skiatest::Reporter* reporter) { 137 SkClipStack stack; 138 139 static const SkRect gRects[] = { 140 { 0, 0, 40, 40 }, 141 { 60, 0, 100, 40 }, 142 { 0, 60, 40, 100 }, 143 { 60, 60, 100, 100 } 144 }; 145 146 for (size_t i = 0; i < SK_ARRAY_COUNT(gRects); i++) { 147 // the union op will prevent these from being fused together 148 stack.clipDevRect(gRects[i], SkRegion::kUnion_Op, false); 149 } 150 151 assert_count(reporter, stack, 4); 152 153 // bottom to top iteration 154 { 155 const SkClipStack::Element* element = NULL; 156 157 SkClipStack::B2TIter iter(stack); 158 int i; 159 160 for (i = 0, element = iter.next(); element; ++i, element = iter.next()) { 161 REPORTER_ASSERT(reporter, SkClipStack::Element::kRect_Type == element->getType()); 162 REPORTER_ASSERT(reporter, element->getRect() == gRects[i]); 163 } 164 165 SkASSERT(i == 4); 166 } 167 168 // top to bottom iteration 169 { 170 const SkClipStack::Element* element = NULL; 171 172 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart); 173 int i; 174 175 for (i = 3, element = iter.prev(); element; --i, element = iter.prev()) { 176 REPORTER_ASSERT(reporter, SkClipStack::Element::kRect_Type == element->getType()); 177 REPORTER_ASSERT(reporter, element->getRect() == gRects[i]); 178 } 179 180 SkASSERT(i == -1); 181 } 182 183 // skipToTopmost 184 { 185 const SkClipStack::Element* element = NULL; 186 187 SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart); 188 189 element = iter.skipToTopmost(SkRegion::kUnion_Op); 190 REPORTER_ASSERT(reporter, SkClipStack::Element::kRect_Type == element->getType()); 191 REPORTER_ASSERT(reporter, element->getRect() == gRects[3]); 192 } 193 } 194 195 // Exercise the SkClipStack's getConservativeBounds computation 196 static void test_bounds(skiatest::Reporter* reporter, bool useRects) { 197 198 static const int gNumCases = 20; 199 static const SkRect gAnswerRectsBW[gNumCases] = { 200 // A op B 201 { 40, 40, 50, 50 }, 202 { 10, 10, 50, 50 }, 203 { 10, 10, 80, 80 }, 204 { 10, 10, 80, 80 }, 205 { 40, 40, 80, 80 }, 206 207 // invA op B 208 { 40, 40, 80, 80 }, 209 { 0, 0, 100, 100 }, 210 { 0, 0, 100, 100 }, 211 { 0, 0, 100, 100 }, 212 { 40, 40, 50, 50 }, 213 214 // A op invB 215 { 10, 10, 50, 50 }, 216 { 40, 40, 50, 50 }, 217 { 0, 0, 100, 100 }, 218 { 0, 0, 100, 100 }, 219 { 0, 0, 100, 100 }, 220 221 // invA op invB 222 { 0, 0, 100, 100 }, 223 { 40, 40, 80, 80 }, 224 { 0, 0, 100, 100 }, 225 { 10, 10, 80, 80 }, 226 { 10, 10, 50, 50 }, 227 }; 228 229 static const SkRegion::Op gOps[] = { 230 SkRegion::kIntersect_Op, 231 SkRegion::kDifference_Op, 232 SkRegion::kUnion_Op, 233 SkRegion::kXOR_Op, 234 SkRegion::kReverseDifference_Op 235 }; 236 237 SkRect rectA, rectB; 238 239 rectA.iset(10, 10, 50, 50); 240 rectB.iset(40, 40, 80, 80); 241 242 SkPath clipA, clipB; 243 244 clipA.addRoundRect(rectA, SkIntToScalar(5), SkIntToScalar(5)); 245 clipB.addRoundRect(rectB, SkIntToScalar(5), SkIntToScalar(5)); 246 247 SkClipStack stack; 248 SkRect devClipBound; 249 bool isIntersectionOfRects = false; 250 251 int testCase = 0; 252 int numBitTests = useRects ? 1 : 4; 253 for (int invBits = 0; invBits < numBitTests; ++invBits) { 254 for (size_t op = 0; op < SK_ARRAY_COUNT(gOps); ++op) { 255 256 stack.save(); 257 bool doInvA = SkToBool(invBits & 1); 258 bool doInvB = SkToBool(invBits & 2); 259 260 clipA.setFillType(doInvA ? SkPath::kInverseEvenOdd_FillType : 261 SkPath::kEvenOdd_FillType); 262 clipB.setFillType(doInvB ? SkPath::kInverseEvenOdd_FillType : 263 SkPath::kEvenOdd_FillType); 264 265 if (useRects) { 266 stack.clipDevRect(rectA, SkRegion::kIntersect_Op, false); 267 stack.clipDevRect(rectB, gOps[op], false); 268 } else { 269 stack.clipDevPath(clipA, SkRegion::kIntersect_Op, false); 270 stack.clipDevPath(clipB, gOps[op], false); 271 } 272 273 REPORTER_ASSERT(reporter, !stack.isWideOpen()); 274 275 stack.getConservativeBounds(0, 0, 100, 100, &devClipBound, 276 &isIntersectionOfRects); 277 278 if (useRects) { 279 REPORTER_ASSERT(reporter, isIntersectionOfRects == 280 (gOps[op] == SkRegion::kIntersect_Op)); 281 } else { 282 REPORTER_ASSERT(reporter, !isIntersectionOfRects); 283 } 284 285 SkASSERT(testCase < gNumCases); 286 REPORTER_ASSERT(reporter, devClipBound == gAnswerRectsBW[testCase]); 287 ++testCase; 288 289 stack.restore(); 290 } 291 } 292 } 293 294 // Test out 'isWideOpen' entry point 295 static void test_isWideOpen(skiatest::Reporter* reporter) { 296 297 SkRect rectA, rectB; 298 299 rectA.iset(10, 10, 40, 40); 300 rectB.iset(50, 50, 80, 80); 301 302 // Stack should initially be wide open 303 { 304 SkClipStack stack; 305 306 REPORTER_ASSERT(reporter, stack.isWideOpen()); 307 } 308 309 // Test out case where the user specifies a union that includes everything 310 { 311 SkClipStack stack; 312 313 SkPath clipA, clipB; 314 315 clipA.addRoundRect(rectA, SkIntToScalar(5), SkIntToScalar(5)); 316 clipA.setFillType(SkPath::kInverseEvenOdd_FillType); 317 318 clipB.addRoundRect(rectB, SkIntToScalar(5), SkIntToScalar(5)); 319 clipB.setFillType(SkPath::kInverseEvenOdd_FillType); 320 321 stack.clipDevPath(clipA, SkRegion::kReplace_Op, false); 322 stack.clipDevPath(clipB, SkRegion::kUnion_Op, false); 323 324 REPORTER_ASSERT(reporter, stack.isWideOpen()); 325 } 326 327 // Test out union w/ a wide open clip 328 { 329 SkClipStack stack; 330 331 stack.clipDevRect(rectA, SkRegion::kUnion_Op, false); 332 333 REPORTER_ASSERT(reporter, stack.isWideOpen()); 334 } 335 336 // Test out empty difference from a wide open clip 337 { 338 SkClipStack stack; 339 340 SkRect emptyRect; 341 emptyRect.setEmpty(); 342 343 stack.clipDevRect(emptyRect, SkRegion::kDifference_Op, false); 344 345 REPORTER_ASSERT(reporter, stack.isWideOpen()); 346 } 347 348 // Test out return to wide open 349 { 350 SkClipStack stack; 351 352 stack.save(); 353 354 stack.clipDevRect(rectA, SkRegion::kReplace_Op, false); 355 356 REPORTER_ASSERT(reporter, !stack.isWideOpen()); 357 358 stack.restore(); 359 360 REPORTER_ASSERT(reporter, stack.isWideOpen()); 361 } 362 } 363 364 static int count(const SkClipStack& stack) { 365 366 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart); 367 368 const SkClipStack::Element* element = NULL; 369 int count = 0; 370 371 for (element = iter.prev(); element; element = iter.prev(), ++count) { 372 ; 373 } 374 375 return count; 376 } 377 378 static void test_rect_inverse_fill(skiatest::Reporter* reporter) { 379 // non-intersecting rectangles 380 SkRect rect = SkRect::MakeLTRB(0, 0, 10, 10); 381 382 SkPath path; 383 path.addRect(rect); 384 path.toggleInverseFillType(); 385 SkClipStack stack; 386 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 387 388 SkRect bounds; 389 SkClipStack::BoundsType boundsType; 390 stack.getBounds(&bounds, &boundsType); 391 REPORTER_ASSERT(reporter, SkClipStack::kInsideOut_BoundsType == boundsType); 392 REPORTER_ASSERT(reporter, bounds == rect); 393 } 394 395 // Test out SkClipStack's merging of rect clips. In particular exercise 396 // merging of aa vs. bw rects. 397 static void test_rect_merging(skiatest::Reporter* reporter) { 398 399 SkRect overlapLeft = SkRect::MakeLTRB(10, 10, 50, 50); 400 SkRect overlapRight = SkRect::MakeLTRB(40, 40, 80, 80); 401 402 SkRect nestedParent = SkRect::MakeLTRB(10, 10, 90, 90); 403 SkRect nestedChild = SkRect::MakeLTRB(40, 40, 60, 60); 404 405 SkRect bound; 406 SkClipStack::BoundsType type; 407 bool isIntersectionOfRects; 408 409 // all bw overlapping - should merge 410 { 411 SkClipStack stack; 412 413 stack.clipDevRect(overlapLeft, SkRegion::kReplace_Op, false); 414 415 stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, false); 416 417 REPORTER_ASSERT(reporter, 1 == count(stack)); 418 419 stack.getBounds(&bound, &type, &isIntersectionOfRects); 420 421 REPORTER_ASSERT(reporter, isIntersectionOfRects); 422 } 423 424 // all aa overlapping - should merge 425 { 426 SkClipStack stack; 427 428 stack.clipDevRect(overlapLeft, SkRegion::kReplace_Op, true); 429 430 stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, true); 431 432 REPORTER_ASSERT(reporter, 1 == count(stack)); 433 434 stack.getBounds(&bound, &type, &isIntersectionOfRects); 435 436 REPORTER_ASSERT(reporter, isIntersectionOfRects); 437 } 438 439 // mixed overlapping - should _not_ merge 440 { 441 SkClipStack stack; 442 443 stack.clipDevRect(overlapLeft, SkRegion::kReplace_Op, true); 444 445 stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, false); 446 447 REPORTER_ASSERT(reporter, 2 == count(stack)); 448 449 stack.getBounds(&bound, &type, &isIntersectionOfRects); 450 451 REPORTER_ASSERT(reporter, !isIntersectionOfRects); 452 } 453 454 // mixed nested (bw inside aa) - should merge 455 { 456 SkClipStack stack; 457 458 stack.clipDevRect(nestedParent, SkRegion::kReplace_Op, true); 459 460 stack.clipDevRect(nestedChild, SkRegion::kIntersect_Op, false); 461 462 REPORTER_ASSERT(reporter, 1 == count(stack)); 463 464 stack.getBounds(&bound, &type, &isIntersectionOfRects); 465 466 REPORTER_ASSERT(reporter, isIntersectionOfRects); 467 } 468 469 // mixed nested (aa inside bw) - should merge 470 { 471 SkClipStack stack; 472 473 stack.clipDevRect(nestedParent, SkRegion::kReplace_Op, false); 474 475 stack.clipDevRect(nestedChild, SkRegion::kIntersect_Op, true); 476 477 REPORTER_ASSERT(reporter, 1 == count(stack)); 478 479 stack.getBounds(&bound, &type, &isIntersectionOfRects); 480 481 REPORTER_ASSERT(reporter, isIntersectionOfRects); 482 } 483 484 // reverse nested (aa inside bw) - should _not_ merge 485 { 486 SkClipStack stack; 487 488 stack.clipDevRect(nestedChild, SkRegion::kReplace_Op, false); 489 490 stack.clipDevRect(nestedParent, SkRegion::kIntersect_Op, true); 491 492 REPORTER_ASSERT(reporter, 2 == count(stack)); 493 494 stack.getBounds(&bound, &type, &isIntersectionOfRects); 495 496 REPORTER_ASSERT(reporter, !isIntersectionOfRects); 497 } 498 } 499 500 static void test_quickContains(skiatest::Reporter* reporter) { 501 SkRect testRect = SkRect::MakeLTRB(10, 10, 40, 40); 502 SkRect insideRect = SkRect::MakeLTRB(20, 20, 30, 30); 503 SkRect intersectingRect = SkRect::MakeLTRB(25, 25, 50, 50); 504 SkRect outsideRect = SkRect::MakeLTRB(0, 0, 50, 50); 505 SkRect nonIntersectingRect = SkRect::MakeLTRB(100, 100, 110, 110); 506 507 SkPath insideCircle; 508 insideCircle.addCircle(25, 25, 5); 509 SkPath intersectingCircle; 510 intersectingCircle.addCircle(25, 40, 10); 511 SkPath outsideCircle; 512 outsideCircle.addCircle(25, 25, 50); 513 SkPath nonIntersectingCircle; 514 nonIntersectingCircle.addCircle(100, 100, 5); 515 516 { 517 SkClipStack stack; 518 stack.clipDevRect(outsideRect, SkRegion::kDifference_Op, false); 519 // return false because quickContains currently does not care for kDifference_Op 520 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 521 } 522 523 // Replace Op tests 524 { 525 SkClipStack stack; 526 stack.clipDevRect(outsideRect, SkRegion::kReplace_Op, false); 527 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 528 } 529 530 { 531 SkClipStack stack; 532 stack.clipDevRect(insideRect, SkRegion::kIntersect_Op, false); 533 stack.save(); // To prevent in-place substitution by replace OP 534 stack.clipDevRect(outsideRect, SkRegion::kReplace_Op, false); 535 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 536 stack.restore(); 537 } 538 539 { 540 SkClipStack stack; 541 stack.clipDevRect(outsideRect, SkRegion::kIntersect_Op, false); 542 stack.save(); // To prevent in-place substitution by replace OP 543 stack.clipDevRect(insideRect, SkRegion::kReplace_Op, false); 544 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 545 stack.restore(); 546 } 547 548 // Verify proper traversal of multi-element clip 549 { 550 SkClipStack stack; 551 stack.clipDevRect(insideRect, SkRegion::kIntersect_Op, false); 552 // Use a path for second clip to prevent in-place intersection 553 stack.clipDevPath(outsideCircle, SkRegion::kIntersect_Op, false); 554 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 555 } 556 557 // Intersect Op tests with rectangles 558 { 559 SkClipStack stack; 560 stack.clipDevRect(outsideRect, SkRegion::kIntersect_Op, false); 561 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 562 } 563 564 { 565 SkClipStack stack; 566 stack.clipDevRect(insideRect, SkRegion::kIntersect_Op, false); 567 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 568 } 569 570 { 571 SkClipStack stack; 572 stack.clipDevRect(intersectingRect, SkRegion::kIntersect_Op, false); 573 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 574 } 575 576 { 577 SkClipStack stack; 578 stack.clipDevRect(nonIntersectingRect, SkRegion::kIntersect_Op, false); 579 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 580 } 581 582 // Intersect Op tests with circle paths 583 { 584 SkClipStack stack; 585 stack.clipDevPath(outsideCircle, SkRegion::kIntersect_Op, false); 586 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 587 } 588 589 { 590 SkClipStack stack; 591 stack.clipDevPath(insideCircle, SkRegion::kIntersect_Op, false); 592 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 593 } 594 595 { 596 SkClipStack stack; 597 stack.clipDevPath(intersectingCircle, SkRegion::kIntersect_Op, false); 598 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 599 } 600 601 { 602 SkClipStack stack; 603 stack.clipDevPath(nonIntersectingCircle, SkRegion::kIntersect_Op, false); 604 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 605 } 606 607 // Intersect Op tests with inverse filled rectangles 608 { 609 SkClipStack stack; 610 SkPath path; 611 path.addRect(outsideRect); 612 path.toggleInverseFillType(); 613 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 614 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 615 } 616 617 { 618 SkClipStack stack; 619 SkPath path; 620 path.addRect(insideRect); 621 path.toggleInverseFillType(); 622 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 623 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 624 } 625 626 { 627 SkClipStack stack; 628 SkPath path; 629 path.addRect(intersectingRect); 630 path.toggleInverseFillType(); 631 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 632 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 633 } 634 635 { 636 SkClipStack stack; 637 SkPath path; 638 path.addRect(nonIntersectingRect); 639 path.toggleInverseFillType(); 640 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 641 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 642 } 643 644 // Intersect Op tests with inverse filled circles 645 { 646 SkClipStack stack; 647 SkPath path = outsideCircle; 648 path.toggleInverseFillType(); 649 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 650 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 651 } 652 653 { 654 SkClipStack stack; 655 SkPath path = insideCircle; 656 path.toggleInverseFillType(); 657 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 658 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 659 } 660 661 { 662 SkClipStack stack; 663 SkPath path = intersectingCircle; 664 path.toggleInverseFillType(); 665 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 666 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 667 } 668 669 { 670 SkClipStack stack; 671 SkPath path = nonIntersectingCircle; 672 path.toggleInverseFillType(); 673 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 674 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 675 } 676 } 677 678 /////////////////////////////////////////////////////////////////////////////////////////////////// 679 680 #if SK_SUPPORT_GPU 681 // Functions that add a shape to the clip stack. The shape is computed from a rectangle. 682 // AA is always disabled since the clip stack reducer can cause changes in aa rasterization of the 683 // stack. A fractional edge repeated in different elements may be rasterized fewer times using the 684 // reduced stack. 685 typedef void (*AddElementFunc) (const SkRect& rect, 686 bool invert, 687 SkRegion::Op op, 688 SkClipStack* stack); 689 690 static void add_round_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) { 691 SkPath path; 692 SkScalar rx = rect.width() / 10; 693 SkScalar ry = rect.height() / 20; 694 path.addRoundRect(rect, rx, ry); 695 if (invert) { 696 path.setFillType(SkPath::kInverseWinding_FillType); 697 } 698 stack->clipDevPath(path, op, false); 699 }; 700 701 static void add_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) { 702 if (invert) { 703 SkPath path; 704 path.addRect(rect); 705 path.setFillType(SkPath::kInverseWinding_FillType); 706 stack->clipDevPath(path, op, false); 707 } else { 708 stack->clipDevRect(rect, op, false); 709 } 710 }; 711 712 static void add_oval(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) { 713 SkPath path; 714 path.addOval(rect); 715 if (invert) { 716 path.setFillType(SkPath::kInverseWinding_FillType); 717 } 718 stack->clipDevPath(path, op, false); 719 }; 720 721 static void add_elem_to_stack(const SkClipStack::Element& element, SkClipStack* stack) { 722 switch (element.getType()) { 723 case SkClipStack::Element::kRect_Type: 724 stack->clipDevRect(element.getRect(), element.getOp(), element.isAA()); 725 break; 726 case SkClipStack::Element::kPath_Type: 727 stack->clipDevPath(element.getPath(), element.getOp(), element.isAA()); 728 break; 729 case SkClipStack::Element::kEmpty_Type: 730 SkDEBUGFAIL("Why did the reducer produce an explicit empty."); 731 stack->clipEmpty(); 732 break; 733 } 734 } 735 736 static void add_elem_to_region(const SkClipStack::Element& element, 737 const SkIRect& bounds, 738 SkRegion* region) { 739 SkRegion elemRegion; 740 SkRegion boundsRgn(bounds); 741 742 switch (element.getType()) { 743 case SkClipStack::Element::kRect_Type: { 744 SkPath path; 745 path.addRect(element.getRect()); 746 elemRegion.setPath(path, boundsRgn); 747 break; 748 } 749 case SkClipStack::Element::kPath_Type: 750 elemRegion.setPath(element.getPath(), boundsRgn); 751 break; 752 case SkClipStack::Element::kEmpty_Type: 753 // 754 region->setEmpty(); 755 return; 756 } 757 region->op(elemRegion, element.getOp()); 758 } 759 760 // This can assist with debugging the clip stack reduction code when the test below fails. 761 static inline void print_clip(const SkClipStack::Element& element) { 762 static const char* kOpStrs[] = { 763 "DF", 764 "IS", 765 "UN", 766 "XR", 767 "RD", 768 "RP", 769 }; 770 if (SkClipStack::Element::kEmpty_Type != element.getType()) { 771 const SkRect& bounds = element.getBounds(); 772 bool isRect = SkClipStack::Element::kRect_Type == element.getType(); 773 SkDebugf("%s %s %s [%f %f] x [%f %f]\n", 774 kOpStrs[element.getOp()], 775 (isRect ? "R" : "P"), 776 (element.isInverseFilled() ? "I" : " "), 777 bounds.fLeft, bounds.fRight, bounds.fTop, bounds.fBottom); 778 } else { 779 SkDebugf("EM\n"); 780 } 781 } 782 783 static void test_reduced_clip_stack(skiatest::Reporter* reporter) { 784 // We construct random clip stacks, reduce them, and then rasterize both versions to verify that 785 // they are equal. 786 787 // All the clip elements will be contained within these bounds. 788 static const SkRect kBounds = SkRect::MakeWH(100, 100); 789 790 enum { 791 kNumTests = 200, 792 kMinElemsPerTest = 1, 793 kMaxElemsPerTest = 50, 794 }; 795 796 // min/max size of a clip element as a fraction of kBounds. 797 static const SkScalar kMinElemSizeFrac = SK_Scalar1 / 5; 798 static const SkScalar kMaxElemSizeFrac = SK_Scalar1; 799 800 static const SkRegion::Op kOps[] = { 801 SkRegion::kDifference_Op, 802 SkRegion::kIntersect_Op, 803 SkRegion::kUnion_Op, 804 SkRegion::kXOR_Op, 805 SkRegion::kReverseDifference_Op, 806 SkRegion::kReplace_Op, 807 }; 808 809 // Replace operations short-circuit the optimizer. We want to make sure that we test this code 810 // path a little bit but we don't want it to prevent us from testing many longer traversals in 811 // the optimizer. 812 static const int kReplaceDiv = 4 * kMaxElemsPerTest; 813 814 // We want to test inverse fills. However, they are quite rare in practice so don't over do it. 815 static const SkScalar kFractionInverted = SK_Scalar1 / kMaxElemsPerTest; 816 817 static const AddElementFunc kElementFuncs[] = { 818 add_rect, 819 add_round_rect, 820 add_oval, 821 }; 822 823 SkRandom r; 824 825 for (int i = 0; i < kNumTests; ++i) { 826 // Randomly generate a clip stack. 827 SkClipStack stack; 828 int numElems = r.nextRangeU(kMinElemsPerTest, kMaxElemsPerTest); 829 for (int e = 0; e < numElems; ++e) { 830 SkRegion::Op op = kOps[r.nextULessThan(SK_ARRAY_COUNT(kOps))]; 831 if (op == SkRegion::kReplace_Op) { 832 if (r.nextU() % kReplaceDiv) { 833 --e; 834 continue; 835 } 836 } 837 838 // saves can change the clip stack behavior when an element is added. 839 bool doSave = r.nextBool(); 840 841 SkSize size = SkSize::Make( 842 SkScalarFloorToScalar(SkScalarMul(kBounds.width(), r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac))), 843 SkScalarFloorToScalar(SkScalarMul(kBounds.height(), r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac)))); 844 845 SkPoint xy = {SkScalarFloorToScalar(r.nextRangeScalar(kBounds.fLeft, kBounds.fRight - size.fWidth)), 846 SkScalarFloorToScalar(r.nextRangeScalar(kBounds.fTop, kBounds.fBottom - size.fHeight))}; 847 848 SkRect rect = SkRect::MakeXYWH(xy.fX, xy.fY, size.fWidth, size.fHeight); 849 850 bool invert = r.nextBiasedBool(kFractionInverted); 851 kElementFuncs[r.nextULessThan(SK_ARRAY_COUNT(kElementFuncs))](rect, invert, op, &stack); 852 if (doSave) { 853 stack.save(); 854 } 855 } 856 857 SkRect inflatedBounds = kBounds; 858 inflatedBounds.outset(kBounds.width() / 2, kBounds.height() / 2); 859 SkIRect inflatedIBounds; 860 inflatedBounds.roundOut(&inflatedIBounds); 861 862 typedef GrReducedClip::ElementList ElementList; 863 // Get the reduced version of the stack. 864 ElementList reducedClips; 865 866 GrReducedClip::InitialState initial; 867 SkIRect tBounds; 868 SkIRect* tightBounds = r.nextBool() ? &tBounds : NULL; 869 GrReducedClip::ReduceClipStack(stack, 870 inflatedIBounds, 871 &reducedClips, 872 &initial, 873 tightBounds); 874 875 // Build a new clip stack based on the reduced clip elements 876 SkClipStack reducedStack; 877 if (GrReducedClip::kAllOut_InitialState == initial) { 878 // whether the result is bounded or not, the whole plane should start outside the clip. 879 reducedStack.clipEmpty(); 880 } 881 for (ElementList::Iter iter = reducedClips.headIter(); NULL != iter.get(); iter.next()) { 882 add_elem_to_stack(*iter.get(), &reducedStack); 883 } 884 885 // GrReducedClipStack assumes that the final result is clipped to the returned bounds 886 if (NULL != tightBounds) { 887 reducedStack.clipDevRect(*tightBounds, SkRegion::kIntersect_Op); 888 } 889 890 // convert both the original stack and reduced stack to SkRegions and see if they're equal 891 SkRegion region; 892 SkRegion reducedRegion; 893 894 region.setRect(inflatedIBounds); 895 const SkClipStack::Element* element; 896 SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart); 897 while ((element = iter.next())) { 898 add_elem_to_region(*element, inflatedIBounds, ®ion); 899 } 900 901 reducedRegion.setRect(inflatedIBounds); 902 iter.reset(reducedStack, SkClipStack::Iter::kBottom_IterStart); 903 while ((element = iter.next())) { 904 add_elem_to_region(*element, inflatedIBounds, &reducedRegion); 905 } 906 907 REPORTER_ASSERT(reporter, region == reducedRegion); 908 } 909 } 910 911 #endif 912 /////////////////////////////////////////////////////////////////////////////////////////////////// 913 914 static void TestClipStack(skiatest::Reporter* reporter) { 915 SkClipStack stack; 916 917 REPORTER_ASSERT(reporter, 0 == stack.getSaveCount()); 918 assert_count(reporter, stack, 0); 919 920 static const SkIRect gRects[] = { 921 { 0, 0, 100, 100 }, 922 { 25, 25, 125, 125 }, 923 { 0, 0, 1000, 1000 }, 924 { 0, 0, 75, 75 } 925 }; 926 for (size_t i = 0; i < SK_ARRAY_COUNT(gRects); i++) { 927 stack.clipDevRect(gRects[i], SkRegion::kIntersect_Op); 928 } 929 930 // all of the above rects should have been intersected, leaving only 1 rect 931 SkClipStack::B2TIter iter(stack); 932 const SkClipStack::Element* element = iter.next(); 933 SkRect answer; 934 answer.iset(25, 25, 75, 75); 935 936 REPORTER_ASSERT(reporter, NULL != element); 937 REPORTER_ASSERT(reporter, SkClipStack::Element::kRect_Type == element->getType()); 938 REPORTER_ASSERT(reporter, SkRegion::kIntersect_Op == element->getOp()); 939 REPORTER_ASSERT(reporter, element->getRect() == answer); 940 // now check that we only had one in our iterator 941 REPORTER_ASSERT(reporter, !iter.next()); 942 943 stack.reset(); 944 REPORTER_ASSERT(reporter, 0 == stack.getSaveCount()); 945 assert_count(reporter, stack, 0); 946 947 test_assign_and_comparison(reporter); 948 test_iterators(reporter); 949 test_bounds(reporter, true); // once with rects 950 test_bounds(reporter, false); // once with paths 951 test_isWideOpen(reporter); 952 test_rect_merging(reporter); 953 test_rect_inverse_fill(reporter); 954 test_quickContains(reporter); 955 #if SK_SUPPORT_GPU 956 test_reduced_clip_stack(reporter); 957 #endif 958 } 959 960 #include "TestClassDef.h" 961 DEFINE_TESTCLASS("ClipStack", TestClipStackClass, TestClipStack) 962