1 /* 2 * Copyright 2011 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #include "Test.h" 9 #include "TestClassDef.h" 10 #if SK_SUPPORT_GPU 11 #include "GrReducedClip.h" 12 #endif 13 #include "SkClipStack.h" 14 #include "SkPath.h" 15 #include "SkRandom.h" 16 #include "SkRect.h" 17 #include "SkRegion.h" 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 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID != stack.getTopmostGenID()); 275 276 stack.getConservativeBounds(0, 0, 100, 100, &devClipBound, 277 &isIntersectionOfRects); 278 279 if (useRects) { 280 REPORTER_ASSERT(reporter, isIntersectionOfRects == 281 (gOps[op] == SkRegion::kIntersect_Op)); 282 } else { 283 REPORTER_ASSERT(reporter, !isIntersectionOfRects); 284 } 285 286 SkASSERT(testCase < gNumCases); 287 REPORTER_ASSERT(reporter, devClipBound == gAnswerRectsBW[testCase]); 288 ++testCase; 289 290 stack.restore(); 291 } 292 } 293 } 294 295 // Test out 'isWideOpen' entry point 296 static void test_isWideOpen(skiatest::Reporter* reporter) { 297 { 298 // Empty stack is wide open. Wide open stack means that gen id is wide open. 299 SkClipStack stack; 300 REPORTER_ASSERT(reporter, stack.isWideOpen()); 301 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID()); 302 } 303 304 SkRect rectA, rectB; 305 306 rectA.iset(10, 10, 40, 40); 307 rectB.iset(50, 50, 80, 80); 308 309 // Stack should initially be wide open 310 { 311 SkClipStack stack; 312 313 REPORTER_ASSERT(reporter, stack.isWideOpen()); 314 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID()); 315 } 316 317 // Test out case where the user specifies a union that includes everything 318 { 319 SkClipStack stack; 320 321 SkPath clipA, clipB; 322 323 clipA.addRoundRect(rectA, SkIntToScalar(5), SkIntToScalar(5)); 324 clipA.setFillType(SkPath::kInverseEvenOdd_FillType); 325 326 clipB.addRoundRect(rectB, SkIntToScalar(5), SkIntToScalar(5)); 327 clipB.setFillType(SkPath::kInverseEvenOdd_FillType); 328 329 stack.clipDevPath(clipA, SkRegion::kReplace_Op, false); 330 stack.clipDevPath(clipB, SkRegion::kUnion_Op, false); 331 332 REPORTER_ASSERT(reporter, stack.isWideOpen()); 333 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID()); 334 } 335 336 // Test out union w/ a wide open clip 337 { 338 SkClipStack stack; 339 340 stack.clipDevRect(rectA, SkRegion::kUnion_Op, false); 341 342 REPORTER_ASSERT(reporter, stack.isWideOpen()); 343 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID()); 344 } 345 346 // Test out empty difference from a wide open clip 347 { 348 SkClipStack stack; 349 350 SkRect emptyRect; 351 emptyRect.setEmpty(); 352 353 stack.clipDevRect(emptyRect, SkRegion::kDifference_Op, false); 354 355 REPORTER_ASSERT(reporter, stack.isWideOpen()); 356 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID()); 357 } 358 359 // Test out return to wide open 360 { 361 SkClipStack stack; 362 363 stack.save(); 364 365 stack.clipDevRect(rectA, SkRegion::kReplace_Op, false); 366 367 REPORTER_ASSERT(reporter, !stack.isWideOpen()); 368 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID != stack.getTopmostGenID()); 369 370 stack.restore(); 371 372 REPORTER_ASSERT(reporter, stack.isWideOpen()); 373 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID()); 374 } 375 } 376 377 static int count(const SkClipStack& stack) { 378 379 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart); 380 381 const SkClipStack::Element* element = NULL; 382 int count = 0; 383 384 for (element = iter.prev(); element; element = iter.prev(), ++count) { 385 ; 386 } 387 388 return count; 389 } 390 391 static void test_rect_inverse_fill(skiatest::Reporter* reporter) { 392 // non-intersecting rectangles 393 SkRect rect = SkRect::MakeLTRB(0, 0, 10, 10); 394 395 SkPath path; 396 path.addRect(rect); 397 path.toggleInverseFillType(); 398 SkClipStack stack; 399 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 400 401 SkRect bounds; 402 SkClipStack::BoundsType boundsType; 403 stack.getBounds(&bounds, &boundsType); 404 REPORTER_ASSERT(reporter, SkClipStack::kInsideOut_BoundsType == boundsType); 405 REPORTER_ASSERT(reporter, bounds == rect); 406 } 407 408 static void test_rect_replace(skiatest::Reporter* reporter) { 409 SkRect rect = SkRect::MakeWH(100, 100); 410 SkRect rect2 = SkRect::MakeXYWH(50, 50, 100, 100); 411 412 SkRect bound; 413 SkClipStack::BoundsType type; 414 bool isIntersectionOfRects; 415 416 // Adding a new rect with the replace operator should not increase 417 // the stack depth. BW replacing BW. 418 { 419 SkClipStack stack; 420 REPORTER_ASSERT(reporter, 0 == count(stack)); 421 stack.clipDevRect(rect, SkRegion::kReplace_Op, false); 422 REPORTER_ASSERT(reporter, 1 == count(stack)); 423 stack.clipDevRect(rect, SkRegion::kReplace_Op, false); 424 REPORTER_ASSERT(reporter, 1 == count(stack)); 425 } 426 427 // Adding a new rect with the replace operator should not increase 428 // the stack depth. AA replacing AA. 429 { 430 SkClipStack stack; 431 REPORTER_ASSERT(reporter, 0 == count(stack)); 432 stack.clipDevRect(rect, SkRegion::kReplace_Op, true); 433 REPORTER_ASSERT(reporter, 1 == count(stack)); 434 stack.clipDevRect(rect, SkRegion::kReplace_Op, true); 435 REPORTER_ASSERT(reporter, 1 == count(stack)); 436 } 437 438 // Adding a new rect with the replace operator should not increase 439 // the stack depth. BW replacing AA replacing BW. 440 { 441 SkClipStack stack; 442 REPORTER_ASSERT(reporter, 0 == count(stack)); 443 stack.clipDevRect(rect, SkRegion::kReplace_Op, false); 444 REPORTER_ASSERT(reporter, 1 == count(stack)); 445 stack.clipDevRect(rect, SkRegion::kReplace_Op, true); 446 REPORTER_ASSERT(reporter, 1 == count(stack)); 447 stack.clipDevRect(rect, SkRegion::kReplace_Op, false); 448 REPORTER_ASSERT(reporter, 1 == count(stack)); 449 } 450 451 // Make sure replace clip rects don't collapse too much. 452 { 453 SkClipStack stack; 454 stack.clipDevRect(rect, SkRegion::kReplace_Op, false); 455 stack.clipDevRect(rect2, SkRegion::kIntersect_Op, false); 456 REPORTER_ASSERT(reporter, 1 == count(stack)); 457 458 stack.save(); 459 stack.clipDevRect(rect, SkRegion::kReplace_Op, false); 460 REPORTER_ASSERT(reporter, 2 == count(stack)); 461 stack.getBounds(&bound, &type, &isIntersectionOfRects); 462 REPORTER_ASSERT(reporter, bound == rect); 463 stack.restore(); 464 REPORTER_ASSERT(reporter, 1 == count(stack)); 465 466 stack.save(); 467 stack.clipDevRect(rect, SkRegion::kReplace_Op, false); 468 stack.clipDevRect(rect, SkRegion::kReplace_Op, false); 469 REPORTER_ASSERT(reporter, 2 == count(stack)); 470 stack.restore(); 471 REPORTER_ASSERT(reporter, 1 == count(stack)); 472 473 stack.save(); 474 stack.clipDevRect(rect, SkRegion::kReplace_Op, false); 475 stack.clipDevRect(rect2, SkRegion::kIntersect_Op, false); 476 stack.clipDevRect(rect, SkRegion::kReplace_Op, false); 477 REPORTER_ASSERT(reporter, 2 == count(stack)); 478 stack.restore(); 479 REPORTER_ASSERT(reporter, 1 == count(stack)); 480 } 481 } 482 483 // Simplified path-based version of test_rect_replace. 484 static void test_path_replace(skiatest::Reporter* reporter) { 485 SkRect rect = SkRect::MakeWH(100, 100); 486 SkPath path; 487 path.addCircle(50, 50, 50); 488 489 // Replace operation doesn't grow the stack. 490 { 491 SkClipStack stack; 492 REPORTER_ASSERT(reporter, 0 == count(stack)); 493 stack.clipDevPath(path, SkRegion::kReplace_Op, false); 494 REPORTER_ASSERT(reporter, 1 == count(stack)); 495 stack.clipDevPath(path, SkRegion::kReplace_Op, false); 496 REPORTER_ASSERT(reporter, 1 == count(stack)); 497 } 498 499 // Replacing rect with path. 500 { 501 SkClipStack stack; 502 stack.clipDevRect(rect, SkRegion::kReplace_Op, true); 503 REPORTER_ASSERT(reporter, 1 == count(stack)); 504 stack.clipDevPath(path, SkRegion::kReplace_Op, true); 505 REPORTER_ASSERT(reporter, 1 == count(stack)); 506 } 507 } 508 509 // Test out SkClipStack's merging of rect clips. In particular exercise 510 // merging of aa vs. bw rects. 511 static void test_rect_merging(skiatest::Reporter* reporter) { 512 513 SkRect overlapLeft = SkRect::MakeLTRB(10, 10, 50, 50); 514 SkRect overlapRight = SkRect::MakeLTRB(40, 40, 80, 80); 515 516 SkRect nestedParent = SkRect::MakeLTRB(10, 10, 90, 90); 517 SkRect nestedChild = SkRect::MakeLTRB(40, 40, 60, 60); 518 519 SkRect bound; 520 SkClipStack::BoundsType type; 521 bool isIntersectionOfRects; 522 523 // all bw overlapping - should merge 524 { 525 SkClipStack stack; 526 527 stack.clipDevRect(overlapLeft, SkRegion::kReplace_Op, false); 528 529 stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, false); 530 531 REPORTER_ASSERT(reporter, 1 == count(stack)); 532 533 stack.getBounds(&bound, &type, &isIntersectionOfRects); 534 535 REPORTER_ASSERT(reporter, isIntersectionOfRects); 536 } 537 538 // all aa overlapping - should merge 539 { 540 SkClipStack stack; 541 542 stack.clipDevRect(overlapLeft, SkRegion::kReplace_Op, true); 543 544 stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, true); 545 546 REPORTER_ASSERT(reporter, 1 == count(stack)); 547 548 stack.getBounds(&bound, &type, &isIntersectionOfRects); 549 550 REPORTER_ASSERT(reporter, isIntersectionOfRects); 551 } 552 553 // mixed overlapping - should _not_ merge 554 { 555 SkClipStack stack; 556 557 stack.clipDevRect(overlapLeft, SkRegion::kReplace_Op, true); 558 559 stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, false); 560 561 REPORTER_ASSERT(reporter, 2 == count(stack)); 562 563 stack.getBounds(&bound, &type, &isIntersectionOfRects); 564 565 REPORTER_ASSERT(reporter, !isIntersectionOfRects); 566 } 567 568 // mixed nested (bw inside aa) - should merge 569 { 570 SkClipStack stack; 571 572 stack.clipDevRect(nestedParent, SkRegion::kReplace_Op, true); 573 574 stack.clipDevRect(nestedChild, SkRegion::kIntersect_Op, false); 575 576 REPORTER_ASSERT(reporter, 1 == count(stack)); 577 578 stack.getBounds(&bound, &type, &isIntersectionOfRects); 579 580 REPORTER_ASSERT(reporter, isIntersectionOfRects); 581 } 582 583 // mixed nested (aa inside bw) - should merge 584 { 585 SkClipStack stack; 586 587 stack.clipDevRect(nestedParent, SkRegion::kReplace_Op, false); 588 589 stack.clipDevRect(nestedChild, SkRegion::kIntersect_Op, true); 590 591 REPORTER_ASSERT(reporter, 1 == count(stack)); 592 593 stack.getBounds(&bound, &type, &isIntersectionOfRects); 594 595 REPORTER_ASSERT(reporter, isIntersectionOfRects); 596 } 597 598 // reverse nested (aa inside bw) - should _not_ merge 599 { 600 SkClipStack stack; 601 602 stack.clipDevRect(nestedChild, SkRegion::kReplace_Op, false); 603 604 stack.clipDevRect(nestedParent, SkRegion::kIntersect_Op, true); 605 606 REPORTER_ASSERT(reporter, 2 == count(stack)); 607 608 stack.getBounds(&bound, &type, &isIntersectionOfRects); 609 610 REPORTER_ASSERT(reporter, !isIntersectionOfRects); 611 } 612 } 613 614 static void test_quickContains(skiatest::Reporter* reporter) { 615 SkRect testRect = SkRect::MakeLTRB(10, 10, 40, 40); 616 SkRect insideRect = SkRect::MakeLTRB(20, 20, 30, 30); 617 SkRect intersectingRect = SkRect::MakeLTRB(25, 25, 50, 50); 618 SkRect outsideRect = SkRect::MakeLTRB(0, 0, 50, 50); 619 SkRect nonIntersectingRect = SkRect::MakeLTRB(100, 100, 110, 110); 620 621 SkPath insideCircle; 622 insideCircle.addCircle(25, 25, 5); 623 SkPath intersectingCircle; 624 intersectingCircle.addCircle(25, 40, 10); 625 SkPath outsideCircle; 626 outsideCircle.addCircle(25, 25, 50); 627 SkPath nonIntersectingCircle; 628 nonIntersectingCircle.addCircle(100, 100, 5); 629 630 { 631 SkClipStack stack; 632 stack.clipDevRect(outsideRect, SkRegion::kDifference_Op, false); 633 // return false because quickContains currently does not care for kDifference_Op 634 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 635 } 636 637 // Replace Op tests 638 { 639 SkClipStack stack; 640 stack.clipDevRect(outsideRect, SkRegion::kReplace_Op, false); 641 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 642 } 643 644 { 645 SkClipStack stack; 646 stack.clipDevRect(insideRect, SkRegion::kIntersect_Op, false); 647 stack.save(); // To prevent in-place substitution by replace OP 648 stack.clipDevRect(outsideRect, SkRegion::kReplace_Op, false); 649 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 650 stack.restore(); 651 } 652 653 { 654 SkClipStack stack; 655 stack.clipDevRect(outsideRect, SkRegion::kIntersect_Op, false); 656 stack.save(); // To prevent in-place substitution by replace OP 657 stack.clipDevRect(insideRect, SkRegion::kReplace_Op, false); 658 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 659 stack.restore(); 660 } 661 662 // Verify proper traversal of multi-element clip 663 { 664 SkClipStack stack; 665 stack.clipDevRect(insideRect, SkRegion::kIntersect_Op, false); 666 // Use a path for second clip to prevent in-place intersection 667 stack.clipDevPath(outsideCircle, SkRegion::kIntersect_Op, false); 668 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 669 } 670 671 // Intersect Op tests with rectangles 672 { 673 SkClipStack stack; 674 stack.clipDevRect(outsideRect, SkRegion::kIntersect_Op, false); 675 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 676 } 677 678 { 679 SkClipStack stack; 680 stack.clipDevRect(insideRect, SkRegion::kIntersect_Op, false); 681 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 682 } 683 684 { 685 SkClipStack stack; 686 stack.clipDevRect(intersectingRect, SkRegion::kIntersect_Op, false); 687 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 688 } 689 690 { 691 SkClipStack stack; 692 stack.clipDevRect(nonIntersectingRect, SkRegion::kIntersect_Op, false); 693 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 694 } 695 696 // Intersect Op tests with circle paths 697 { 698 SkClipStack stack; 699 stack.clipDevPath(outsideCircle, SkRegion::kIntersect_Op, false); 700 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 701 } 702 703 { 704 SkClipStack stack; 705 stack.clipDevPath(insideCircle, SkRegion::kIntersect_Op, false); 706 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 707 } 708 709 { 710 SkClipStack stack; 711 stack.clipDevPath(intersectingCircle, SkRegion::kIntersect_Op, false); 712 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 713 } 714 715 { 716 SkClipStack stack; 717 stack.clipDevPath(nonIntersectingCircle, SkRegion::kIntersect_Op, false); 718 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 719 } 720 721 // Intersect Op tests with inverse filled rectangles 722 { 723 SkClipStack stack; 724 SkPath path; 725 path.addRect(outsideRect); 726 path.toggleInverseFillType(); 727 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 728 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 729 } 730 731 { 732 SkClipStack stack; 733 SkPath path; 734 path.addRect(insideRect); 735 path.toggleInverseFillType(); 736 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 737 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 738 } 739 740 { 741 SkClipStack stack; 742 SkPath path; 743 path.addRect(intersectingRect); 744 path.toggleInverseFillType(); 745 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 746 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 747 } 748 749 { 750 SkClipStack stack; 751 SkPath path; 752 path.addRect(nonIntersectingRect); 753 path.toggleInverseFillType(); 754 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 755 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 756 } 757 758 // Intersect Op tests with inverse filled circles 759 { 760 SkClipStack stack; 761 SkPath path = outsideCircle; 762 path.toggleInverseFillType(); 763 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 764 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 765 } 766 767 { 768 SkClipStack stack; 769 SkPath path = insideCircle; 770 path.toggleInverseFillType(); 771 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 772 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 773 } 774 775 { 776 SkClipStack stack; 777 SkPath path = intersectingCircle; 778 path.toggleInverseFillType(); 779 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 780 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 781 } 782 783 { 784 SkClipStack stack; 785 SkPath path = nonIntersectingCircle; 786 path.toggleInverseFillType(); 787 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 788 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 789 } 790 } 791 792 /////////////////////////////////////////////////////////////////////////////////////////////////// 793 794 #if SK_SUPPORT_GPU 795 // Functions that add a shape to the clip stack. The shape is computed from a rectangle. 796 // AA is always disabled since the clip stack reducer can cause changes in aa rasterization of the 797 // stack. A fractional edge repeated in different elements may be rasterized fewer times using the 798 // reduced stack. 799 typedef void (*AddElementFunc) (const SkRect& rect, 800 bool invert, 801 SkRegion::Op op, 802 SkClipStack* stack); 803 804 static void add_round_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) { 805 SkPath path; 806 SkScalar rx = rect.width() / 10; 807 SkScalar ry = rect.height() / 20; 808 path.addRoundRect(rect, rx, ry); 809 if (invert) { 810 path.setFillType(SkPath::kInverseWinding_FillType); 811 } 812 stack->clipDevPath(path, op, false); 813 }; 814 815 static void add_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) { 816 if (invert) { 817 SkPath path; 818 path.addRect(rect); 819 path.setFillType(SkPath::kInverseWinding_FillType); 820 stack->clipDevPath(path, op, false); 821 } else { 822 stack->clipDevRect(rect, op, false); 823 } 824 }; 825 826 static void add_oval(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) { 827 SkPath path; 828 path.addOval(rect); 829 if (invert) { 830 path.setFillType(SkPath::kInverseWinding_FillType); 831 } 832 stack->clipDevPath(path, op, false); 833 }; 834 835 static void add_elem_to_stack(const SkClipStack::Element& element, SkClipStack* stack) { 836 switch (element.getType()) { 837 case SkClipStack::Element::kRect_Type: 838 stack->clipDevRect(element.getRect(), element.getOp(), element.isAA()); 839 break; 840 case SkClipStack::Element::kPath_Type: 841 stack->clipDevPath(element.getPath(), element.getOp(), element.isAA()); 842 break; 843 case SkClipStack::Element::kEmpty_Type: 844 SkDEBUGFAIL("Why did the reducer produce an explicit empty."); 845 stack->clipEmpty(); 846 break; 847 } 848 } 849 850 static void add_elem_to_region(const SkClipStack::Element& element, 851 const SkIRect& bounds, 852 SkRegion* region) { 853 SkRegion elemRegion; 854 SkRegion boundsRgn(bounds); 855 856 switch (element.getType()) { 857 case SkClipStack::Element::kRect_Type: { 858 SkPath path; 859 path.addRect(element.getRect()); 860 elemRegion.setPath(path, boundsRgn); 861 break; 862 } 863 case SkClipStack::Element::kPath_Type: 864 elemRegion.setPath(element.getPath(), boundsRgn); 865 break; 866 case SkClipStack::Element::kEmpty_Type: 867 // 868 region->setEmpty(); 869 return; 870 } 871 region->op(elemRegion, element.getOp()); 872 } 873 874 static void test_reduced_clip_stack(skiatest::Reporter* reporter) { 875 // We construct random clip stacks, reduce them, and then rasterize both versions to verify that 876 // they are equal. 877 878 // All the clip elements will be contained within these bounds. 879 static const SkRect kBounds = SkRect::MakeWH(100, 100); 880 881 enum { 882 kNumTests = 200, 883 kMinElemsPerTest = 1, 884 kMaxElemsPerTest = 50, 885 }; 886 887 // min/max size of a clip element as a fraction of kBounds. 888 static const SkScalar kMinElemSizeFrac = SK_Scalar1 / 5; 889 static const SkScalar kMaxElemSizeFrac = SK_Scalar1; 890 891 static const SkRegion::Op kOps[] = { 892 SkRegion::kDifference_Op, 893 SkRegion::kIntersect_Op, 894 SkRegion::kUnion_Op, 895 SkRegion::kXOR_Op, 896 SkRegion::kReverseDifference_Op, 897 SkRegion::kReplace_Op, 898 }; 899 900 // Replace operations short-circuit the optimizer. We want to make sure that we test this code 901 // path a little bit but we don't want it to prevent us from testing many longer traversals in 902 // the optimizer. 903 static const int kReplaceDiv = 4 * kMaxElemsPerTest; 904 905 // We want to test inverse fills. However, they are quite rare in practice so don't over do it. 906 static const SkScalar kFractionInverted = SK_Scalar1 / kMaxElemsPerTest; 907 908 static const AddElementFunc kElementFuncs[] = { 909 add_rect, 910 add_round_rect, 911 add_oval, 912 }; 913 914 SkRandom r; 915 916 for (int i = 0; i < kNumTests; ++i) { 917 // Randomly generate a clip stack. 918 SkClipStack stack; 919 int numElems = r.nextRangeU(kMinElemsPerTest, kMaxElemsPerTest); 920 for (int e = 0; e < numElems; ++e) { 921 SkRegion::Op op = kOps[r.nextULessThan(SK_ARRAY_COUNT(kOps))]; 922 if (op == SkRegion::kReplace_Op) { 923 if (r.nextU() % kReplaceDiv) { 924 --e; 925 continue; 926 } 927 } 928 929 // saves can change the clip stack behavior when an element is added. 930 bool doSave = r.nextBool(); 931 932 SkSize size = SkSize::Make( 933 SkScalarFloorToScalar(SkScalarMul(kBounds.width(), r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac))), 934 SkScalarFloorToScalar(SkScalarMul(kBounds.height(), r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac)))); 935 936 SkPoint xy = {SkScalarFloorToScalar(r.nextRangeScalar(kBounds.fLeft, kBounds.fRight - size.fWidth)), 937 SkScalarFloorToScalar(r.nextRangeScalar(kBounds.fTop, kBounds.fBottom - size.fHeight))}; 938 939 SkRect rect = SkRect::MakeXYWH(xy.fX, xy.fY, size.fWidth, size.fHeight); 940 941 bool invert = r.nextBiasedBool(kFractionInverted); 942 kElementFuncs[r.nextULessThan(SK_ARRAY_COUNT(kElementFuncs))](rect, invert, op, &stack); 943 if (doSave) { 944 stack.save(); 945 } 946 } 947 948 SkRect inflatedBounds = kBounds; 949 inflatedBounds.outset(kBounds.width() / 2, kBounds.height() / 2); 950 SkIRect inflatedIBounds; 951 inflatedBounds.roundOut(&inflatedIBounds); 952 953 typedef GrReducedClip::ElementList ElementList; 954 // Get the reduced version of the stack. 955 ElementList reducedClips; 956 int32_t reducedGenID; 957 GrReducedClip::InitialState initial; 958 SkIRect tBounds(inflatedIBounds); 959 SkIRect* tightBounds = r.nextBool() ? &tBounds : NULL; 960 GrReducedClip::ReduceClipStack(stack, 961 inflatedIBounds, 962 &reducedClips, 963 &reducedGenID, 964 &initial, 965 tightBounds); 966 967 REPORTER_ASSERT(reporter, SkClipStack::kInvalidGenID != reducedGenID); 968 969 // Build a new clip stack based on the reduced clip elements 970 SkClipStack reducedStack; 971 if (GrReducedClip::kAllOut_InitialState == initial) { 972 // whether the result is bounded or not, the whole plane should start outside the clip. 973 reducedStack.clipEmpty(); 974 } 975 for (ElementList::Iter iter = reducedClips.headIter(); NULL != iter.get(); iter.next()) { 976 add_elem_to_stack(*iter.get(), &reducedStack); 977 } 978 979 // GrReducedClipStack assumes that the final result is clipped to the returned bounds 980 if (NULL != tightBounds) { 981 reducedStack.clipDevRect(*tightBounds, SkRegion::kIntersect_Op); 982 } 983 984 // convert both the original stack and reduced stack to SkRegions and see if they're equal 985 SkRegion region; 986 SkRegion reducedRegion; 987 988 region.setRect(inflatedIBounds); 989 const SkClipStack::Element* element; 990 SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart); 991 while ((element = iter.next())) { 992 add_elem_to_region(*element, inflatedIBounds, ®ion); 993 } 994 995 reducedRegion.setRect(inflatedIBounds); 996 iter.reset(reducedStack, SkClipStack::Iter::kBottom_IterStart); 997 while ((element = iter.next())) { 998 add_elem_to_region(*element, inflatedIBounds, &reducedRegion); 999 } 1000 1001 REPORTER_ASSERT(reporter, region == reducedRegion); 1002 } 1003 } 1004 1005 #if defined(WIN32) 1006 #define SUPPRESS_VISIBILITY_WARNING 1007 #else 1008 #define SUPPRESS_VISIBILITY_WARNING __attribute__((visibility("hidden"))) 1009 #endif 1010 1011 static void test_reduced_clip_stack_genid(skiatest::Reporter* reporter) { 1012 { 1013 SkClipStack stack; 1014 stack.clipDevRect(SkRect::MakeXYWH(0, 0, 100, 100), SkRegion::kReplace_Op, true); 1015 stack.clipDevRect(SkRect::MakeXYWH(0, 0, SkScalar(50.3), SkScalar(50.3)), SkRegion::kReplace_Op, true); 1016 SkIRect inflatedIBounds = SkIRect::MakeXYWH(0, 0, 100, 100); 1017 1018 GrReducedClip::ElementList reducedClips; 1019 int32_t reducedGenID; 1020 GrReducedClip::InitialState initial; 1021 SkIRect tightBounds; 1022 1023 GrReducedClip::ReduceClipStack(stack, 1024 inflatedIBounds, 1025 &reducedClips, 1026 &reducedGenID, 1027 &initial, 1028 &tightBounds); 1029 1030 REPORTER_ASSERT(reporter, reducedClips.count() == 1); 1031 // Clips will be cached based on the generation id. Make sure the gen id is valid. 1032 REPORTER_ASSERT(reporter, SkClipStack::kInvalidGenID != reducedGenID); 1033 } 1034 { 1035 SkClipStack stack; 1036 1037 // Create a clip with following 25.3, 25.3 boxes which are 25 apart: 1038 // A B 1039 // C D 1040 1041 stack.clipDevRect(SkRect::MakeXYWH(0, 0, SkScalar(25.3), SkScalar(25.3)), SkRegion::kReplace_Op, true); 1042 int32_t genIDA = stack.getTopmostGenID(); 1043 stack.clipDevRect(SkRect::MakeXYWH(50, 0, SkScalar(25.3), SkScalar(25.3)), SkRegion::kUnion_Op, true); 1044 int32_t genIDB = stack.getTopmostGenID(); 1045 stack.clipDevRect(SkRect::MakeXYWH(0, 50, SkScalar(25.3), SkScalar(25.3)), SkRegion::kUnion_Op, true); 1046 int32_t genIDC = stack.getTopmostGenID(); 1047 stack.clipDevRect(SkRect::MakeXYWH(50, 50, SkScalar(25.3), SkScalar(25.3)), SkRegion::kUnion_Op, true); 1048 int32_t genIDD = stack.getTopmostGenID(); 1049 1050 1051 #define XYWH SkIRect::MakeXYWH 1052 1053 SkIRect unused; 1054 unused.setEmpty(); 1055 SkIRect stackBounds = XYWH(0, 0, 76, 76); 1056 1057 // The base test is to test each rect in two ways: 1058 // 1) The box dimensions. (Should reduce to "all in", no elements). 1059 // 2) A bit over the box dimensions. 1060 // In the case 2, test that the generation id is what is expected. 1061 // The rects are of fractional size so that case 2 never gets optimized to an empty element 1062 // list. 1063 1064 // Not passing in tighter bounds is tested for consistency. 1065 static const struct SUPPRESS_VISIBILITY_WARNING { 1066 SkIRect testBounds; 1067 int reducedClipCount; 1068 int32_t reducedGenID; 1069 GrReducedClip::InitialState initialState; 1070 SkIRect tighterBounds; // If this is empty, the query will not pass tighter bounds 1071 // parameter. 1072 } testCases[] = { 1073 // Rect A. 1074 { XYWH(0, 0, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, XYWH(0, 0, 25, 25) }, 1075 { XYWH(0, 0, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, unused }, 1076 { XYWH(0, 0, 27, 27), 1, genIDA, GrReducedClip::kAllOut_InitialState, XYWH(0, 0, 27, 27)}, 1077 { XYWH(0, 0, 27, 27), 1, genIDA, GrReducedClip::kAllOut_InitialState, unused }, 1078 1079 // Rect B. 1080 { XYWH(50, 0, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, XYWH(50, 0, 25, 25) }, 1081 { XYWH(50, 0, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, unused }, 1082 { XYWH(50, 0, 27, 27), 1, genIDB, GrReducedClip::kAllOut_InitialState, XYWH(50, 0, 26, 27) }, 1083 { XYWH(50, 0, 27, 27), 1, genIDB, GrReducedClip::kAllOut_InitialState, unused }, 1084 1085 // Rect C. 1086 { XYWH(0, 50, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, XYWH(0, 50, 25, 25) }, 1087 { XYWH(0, 50, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, unused }, 1088 { XYWH(0, 50, 27, 27), 1, genIDC, GrReducedClip::kAllOut_InitialState, XYWH(0, 50, 27, 26) }, 1089 { XYWH(0, 50, 27, 27), 1, genIDC, GrReducedClip::kAllOut_InitialState, unused }, 1090 1091 // Rect D. 1092 { XYWH(50, 50, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, unused }, 1093 { XYWH(50, 50, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, XYWH(50, 50, 25, 25)}, 1094 { XYWH(50, 50, 27, 27), 1, genIDD, GrReducedClip::kAllOut_InitialState, unused }, 1095 { XYWH(50, 50, 27, 27), 1, genIDD, GrReducedClip::kAllOut_InitialState, XYWH(50, 50, 26, 26)}, 1096 1097 // Other tests: 1098 { XYWH(0, 0, 100, 100), 4, genIDD, GrReducedClip::kAllOut_InitialState, unused }, 1099 { XYWH(0, 0, 100, 100), 4, genIDD, GrReducedClip::kAllOut_InitialState, stackBounds }, 1100 1101 // Rect in the middle, touches none. 1102 { XYWH(26, 26, 24, 24), 0, SkClipStack::kEmptyGenID, GrReducedClip::kAllOut_InitialState, unused }, 1103 { XYWH(26, 26, 24, 24), 0, SkClipStack::kEmptyGenID, GrReducedClip::kAllOut_InitialState, XYWH(26, 26, 24, 24) }, 1104 1105 // Rect in the middle, touches all the rects. GenID is the last rect. 1106 { XYWH(24, 24, 27, 27), 4, genIDD, GrReducedClip::kAllOut_InitialState, unused }, 1107 { XYWH(24, 24, 27, 27), 4, genIDD, GrReducedClip::kAllOut_InitialState, XYWH(24, 24, 27, 27) }, 1108 }; 1109 1110 #undef XYWH 1111 1112 for (size_t i = 0; i < SK_ARRAY_COUNT(testCases); ++i) { 1113 GrReducedClip::ElementList reducedClips; 1114 int32_t reducedGenID; 1115 GrReducedClip::InitialState initial; 1116 SkIRect tightBounds; 1117 1118 GrReducedClip::ReduceClipStack(stack, 1119 testCases[i].testBounds, 1120 &reducedClips, 1121 &reducedGenID, 1122 &initial, 1123 testCases[i].tighterBounds.isEmpty() ? NULL : &tightBounds); 1124 1125 REPORTER_ASSERT(reporter, reducedClips.count() == testCases[i].reducedClipCount); 1126 SkASSERT(reducedClips.count() == testCases[i].reducedClipCount); 1127 REPORTER_ASSERT(reporter, reducedGenID == testCases[i].reducedGenID); 1128 SkASSERT(reducedGenID == testCases[i].reducedGenID); 1129 REPORTER_ASSERT(reporter, initial == testCases[i].initialState); 1130 SkASSERT(initial == testCases[i].initialState); 1131 if (!testCases[i].tighterBounds.isEmpty()) { 1132 REPORTER_ASSERT(reporter, tightBounds == testCases[i].tighterBounds); 1133 SkASSERT(tightBounds == testCases[i].tighterBounds); 1134 } 1135 } 1136 } 1137 } 1138 1139 static void test_reduced_clip_stack_no_aa_crash(skiatest::Reporter* reporter) { 1140 SkClipStack stack; 1141 stack.clipDevRect(SkIRect::MakeXYWH(0, 0, 100, 100), SkRegion::kReplace_Op); 1142 stack.clipDevRect(SkIRect::MakeXYWH(0, 0, 50, 50), SkRegion::kReplace_Op); 1143 SkIRect inflatedIBounds = SkIRect::MakeXYWH(0, 0, 100, 100); 1144 1145 GrReducedClip::ElementList reducedClips; 1146 int32_t reducedGenID; 1147 GrReducedClip::InitialState initial; 1148 SkIRect tightBounds; 1149 1150 // At the time, this would crash. 1151 GrReducedClip::ReduceClipStack(stack, 1152 inflatedIBounds, 1153 &reducedClips, 1154 &reducedGenID, 1155 &initial, 1156 &tightBounds); 1157 1158 REPORTER_ASSERT(reporter, 0 == reducedClips.count()); 1159 } 1160 1161 #endif 1162 1163 DEF_TEST(ClipStack, reporter) { 1164 SkClipStack stack; 1165 1166 REPORTER_ASSERT(reporter, 0 == stack.getSaveCount()); 1167 assert_count(reporter, stack, 0); 1168 1169 static const SkIRect gRects[] = { 1170 { 0, 0, 100, 100 }, 1171 { 25, 25, 125, 125 }, 1172 { 0, 0, 1000, 1000 }, 1173 { 0, 0, 75, 75 } 1174 }; 1175 for (size_t i = 0; i < SK_ARRAY_COUNT(gRects); i++) { 1176 stack.clipDevRect(gRects[i], SkRegion::kIntersect_Op); 1177 } 1178 1179 // all of the above rects should have been intersected, leaving only 1 rect 1180 SkClipStack::B2TIter iter(stack); 1181 const SkClipStack::Element* element = iter.next(); 1182 SkRect answer; 1183 answer.iset(25, 25, 75, 75); 1184 1185 REPORTER_ASSERT(reporter, NULL != element); 1186 REPORTER_ASSERT(reporter, SkClipStack::Element::kRect_Type == element->getType()); 1187 REPORTER_ASSERT(reporter, SkRegion::kIntersect_Op == element->getOp()); 1188 REPORTER_ASSERT(reporter, element->getRect() == answer); 1189 // now check that we only had one in our iterator 1190 REPORTER_ASSERT(reporter, !iter.next()); 1191 1192 stack.reset(); 1193 REPORTER_ASSERT(reporter, 0 == stack.getSaveCount()); 1194 assert_count(reporter, stack, 0); 1195 1196 test_assign_and_comparison(reporter); 1197 test_iterators(reporter); 1198 test_bounds(reporter, true); // once with rects 1199 test_bounds(reporter, false); // once with paths 1200 test_isWideOpen(reporter); 1201 test_rect_merging(reporter); 1202 test_rect_replace(reporter); 1203 test_rect_inverse_fill(reporter); 1204 test_path_replace(reporter); 1205 test_quickContains(reporter); 1206 #if SK_SUPPORT_GPU 1207 test_reduced_clip_stack(reporter); 1208 test_reduced_clip_stack_genid(reporter); 1209 test_reduced_clip_stack_no_aa_crash(reporter); 1210 #endif 1211 } 1212