ClipStackTest.cpp revision e0e7cfe44bb9d66d76120a79e5275c294bacaa22
1d70d1c4aa6b9b2f4713d79f442dbf66a3f702c9bDmitry V. Levin 2d70d1c4aa6b9b2f4713d79f442dbf66a3f702c9bDmitry V. Levin/* 3d70d1c4aa6b9b2f4713d79f442dbf66a3f702c9bDmitry V. Levin * Copyright 2011 Google Inc. 4d70d1c4aa6b9b2f4713d79f442dbf66a3f702c9bDmitry V. Levin * 5d70d1c4aa6b9b2f4713d79f442dbf66a3f702c9bDmitry V. Levin * Use of this source code is governed by a BSD-style license that can be 6d70d1c4aa6b9b2f4713d79f442dbf66a3f702c9bDmitry V. Levin * found in the LICENSE file. 7d70d1c4aa6b9b2f4713d79f442dbf66a3f702c9bDmitry V. Levin */ 85105d4ac960b8cd2bfa5f9f907806aa388235889Dmitry V. Levin#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 19static 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 124static 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) 136static 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 196static 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 295static 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 364static 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 378static 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 395static void test_rect_replace(skiatest::Reporter* reporter) { 396 SkRect rect = SkRect::MakeWH(100, 100); 397 SkRect rect2 = SkRect::MakeXYWH(50, 50, 100, 100); 398 399 SkRect bound; 400 SkClipStack::BoundsType type; 401 bool isIntersectionOfRects; 402 403 // Adding a new rect with the replace operator should not increase 404 // the stack depth. BW replacing BW. 405 { 406 SkClipStack stack; 407 REPORTER_ASSERT(reporter, 0 == count(stack)); 408 stack.clipDevRect(rect, SkRegion::kReplace_Op, false); 409 REPORTER_ASSERT(reporter, 1 == count(stack)); 410 stack.clipDevRect(rect, SkRegion::kReplace_Op, false); 411 REPORTER_ASSERT(reporter, 1 == count(stack)); 412 } 413 414 // Adding a new rect with the replace operator should not increase 415 // the stack depth. AA replacing AA. 416 { 417 SkClipStack stack; 418 REPORTER_ASSERT(reporter, 0 == count(stack)); 419 stack.clipDevRect(rect, SkRegion::kReplace_Op, true); 420 REPORTER_ASSERT(reporter, 1 == count(stack)); 421 stack.clipDevRect(rect, SkRegion::kReplace_Op, true); 422 REPORTER_ASSERT(reporter, 1 == count(stack)); 423 } 424 425 // Adding a new rect with the replace operator should not increase 426 // the stack depth. BW replacing AA replacing BW. 427 { 428 SkClipStack stack; 429 REPORTER_ASSERT(reporter, 0 == count(stack)); 430 stack.clipDevRect(rect, SkRegion::kReplace_Op, false); 431 REPORTER_ASSERT(reporter, 1 == count(stack)); 432 stack.clipDevRect(rect, SkRegion::kReplace_Op, true); 433 REPORTER_ASSERT(reporter, 1 == count(stack)); 434 stack.clipDevRect(rect, SkRegion::kReplace_Op, false); 435 REPORTER_ASSERT(reporter, 1 == count(stack)); 436 } 437 438 // Make sure replace clip rects don't collapse too much. 439 { 440 SkClipStack stack; 441 stack.clipDevRect(rect, SkRegion::kReplace_Op, false); 442 stack.clipDevRect(rect2, SkRegion::kIntersect_Op, false); 443 REPORTER_ASSERT(reporter, 1 == count(stack)); 444 445 stack.save(); 446 stack.clipDevRect(rect, SkRegion::kReplace_Op, false); 447 REPORTER_ASSERT(reporter, 2 == count(stack)); 448 stack.getBounds(&bound, &type, &isIntersectionOfRects); 449 REPORTER_ASSERT(reporter, bound == rect); 450 stack.restore(); 451 REPORTER_ASSERT(reporter, 1 == count(stack)); 452 453 stack.save(); 454 stack.clipDevRect(rect, SkRegion::kReplace_Op, false); 455 stack.clipDevRect(rect, SkRegion::kReplace_Op, false); 456 REPORTER_ASSERT(reporter, 2 == count(stack)); 457 stack.restore(); 458 REPORTER_ASSERT(reporter, 1 == count(stack)); 459 460 stack.save(); 461 stack.clipDevRect(rect, SkRegion::kReplace_Op, false); 462 stack.clipDevRect(rect2, SkRegion::kIntersect_Op, false); 463 stack.clipDevRect(rect, SkRegion::kReplace_Op, false); 464 REPORTER_ASSERT(reporter, 2 == count(stack)); 465 stack.restore(); 466 REPORTER_ASSERT(reporter, 1 == count(stack)); 467 } 468} 469 470// Simplified path-based version of test_rect_replace. 471static void test_path_replace(skiatest::Reporter* reporter) { 472 SkRect rect = SkRect::MakeWH(100, 100); 473 SkPath path; 474 path.addCircle(50, 50, 50); 475 476 // Replace operation doesn't grow the stack. 477 { 478 SkClipStack stack; 479 REPORTER_ASSERT(reporter, 0 == count(stack)); 480 stack.clipDevPath(path, SkRegion::kReplace_Op, false); 481 REPORTER_ASSERT(reporter, 1 == count(stack)); 482 stack.clipDevPath(path, SkRegion::kReplace_Op, false); 483 REPORTER_ASSERT(reporter, 1 == count(stack)); 484 } 485 486 // Replacing rect with path. 487 { 488 SkClipStack stack; 489 stack.clipDevRect(rect, SkRegion::kReplace_Op, true); 490 REPORTER_ASSERT(reporter, 1 == count(stack)); 491 stack.clipDevPath(path, SkRegion::kReplace_Op, true); 492 REPORTER_ASSERT(reporter, 1 == count(stack)); 493 } 494} 495 496// Test out SkClipStack's merging of rect clips. In particular exercise 497// merging of aa vs. bw rects. 498static void test_rect_merging(skiatest::Reporter* reporter) { 499 500 SkRect overlapLeft = SkRect::MakeLTRB(10, 10, 50, 50); 501 SkRect overlapRight = SkRect::MakeLTRB(40, 40, 80, 80); 502 503 SkRect nestedParent = SkRect::MakeLTRB(10, 10, 90, 90); 504 SkRect nestedChild = SkRect::MakeLTRB(40, 40, 60, 60); 505 506 SkRect bound; 507 SkClipStack::BoundsType type; 508 bool isIntersectionOfRects; 509 510 // all bw overlapping - should merge 511 { 512 SkClipStack stack; 513 514 stack.clipDevRect(overlapLeft, SkRegion::kReplace_Op, false); 515 516 stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, false); 517 518 REPORTER_ASSERT(reporter, 1 == count(stack)); 519 520 stack.getBounds(&bound, &type, &isIntersectionOfRects); 521 522 REPORTER_ASSERT(reporter, isIntersectionOfRects); 523 } 524 525 // all aa overlapping - should merge 526 { 527 SkClipStack stack; 528 529 stack.clipDevRect(overlapLeft, SkRegion::kReplace_Op, true); 530 531 stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, true); 532 533 REPORTER_ASSERT(reporter, 1 == count(stack)); 534 535 stack.getBounds(&bound, &type, &isIntersectionOfRects); 536 537 REPORTER_ASSERT(reporter, isIntersectionOfRects); 538 } 539 540 // mixed overlapping - should _not_ merge 541 { 542 SkClipStack stack; 543 544 stack.clipDevRect(overlapLeft, SkRegion::kReplace_Op, true); 545 546 stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, false); 547 548 REPORTER_ASSERT(reporter, 2 == count(stack)); 549 550 stack.getBounds(&bound, &type, &isIntersectionOfRects); 551 552 REPORTER_ASSERT(reporter, !isIntersectionOfRects); 553 } 554 555 // mixed nested (bw inside aa) - should merge 556 { 557 SkClipStack stack; 558 559 stack.clipDevRect(nestedParent, SkRegion::kReplace_Op, true); 560 561 stack.clipDevRect(nestedChild, SkRegion::kIntersect_Op, false); 562 563 REPORTER_ASSERT(reporter, 1 == count(stack)); 564 565 stack.getBounds(&bound, &type, &isIntersectionOfRects); 566 567 REPORTER_ASSERT(reporter, isIntersectionOfRects); 568 } 569 570 // mixed nested (aa inside bw) - should merge 571 { 572 SkClipStack stack; 573 574 stack.clipDevRect(nestedParent, SkRegion::kReplace_Op, false); 575 576 stack.clipDevRect(nestedChild, SkRegion::kIntersect_Op, true); 577 578 REPORTER_ASSERT(reporter, 1 == count(stack)); 579 580 stack.getBounds(&bound, &type, &isIntersectionOfRects); 581 582 REPORTER_ASSERT(reporter, isIntersectionOfRects); 583 } 584 585 // reverse nested (aa inside bw) - should _not_ merge 586 { 587 SkClipStack stack; 588 589 stack.clipDevRect(nestedChild, SkRegion::kReplace_Op, false); 590 591 stack.clipDevRect(nestedParent, SkRegion::kIntersect_Op, true); 592 593 REPORTER_ASSERT(reporter, 2 == count(stack)); 594 595 stack.getBounds(&bound, &type, &isIntersectionOfRects); 596 597 REPORTER_ASSERT(reporter, !isIntersectionOfRects); 598 } 599} 600 601static void test_quickContains(skiatest::Reporter* reporter) { 602 SkRect testRect = SkRect::MakeLTRB(10, 10, 40, 40); 603 SkRect insideRect = SkRect::MakeLTRB(20, 20, 30, 30); 604 SkRect intersectingRect = SkRect::MakeLTRB(25, 25, 50, 50); 605 SkRect outsideRect = SkRect::MakeLTRB(0, 0, 50, 50); 606 SkRect nonIntersectingRect = SkRect::MakeLTRB(100, 100, 110, 110); 607 608 SkPath insideCircle; 609 insideCircle.addCircle(25, 25, 5); 610 SkPath intersectingCircle; 611 intersectingCircle.addCircle(25, 40, 10); 612 SkPath outsideCircle; 613 outsideCircle.addCircle(25, 25, 50); 614 SkPath nonIntersectingCircle; 615 nonIntersectingCircle.addCircle(100, 100, 5); 616 617 { 618 SkClipStack stack; 619 stack.clipDevRect(outsideRect, SkRegion::kDifference_Op, false); 620 // return false because quickContains currently does not care for kDifference_Op 621 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 622 } 623 624 // Replace Op tests 625 { 626 SkClipStack stack; 627 stack.clipDevRect(outsideRect, SkRegion::kReplace_Op, false); 628 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 629 } 630 631 { 632 SkClipStack stack; 633 stack.clipDevRect(insideRect, SkRegion::kIntersect_Op, false); 634 stack.save(); // To prevent in-place substitution by replace OP 635 stack.clipDevRect(outsideRect, SkRegion::kReplace_Op, false); 636 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 637 stack.restore(); 638 } 639 640 { 641 SkClipStack stack; 642 stack.clipDevRect(outsideRect, SkRegion::kIntersect_Op, false); 643 stack.save(); // To prevent in-place substitution by replace OP 644 stack.clipDevRect(insideRect, SkRegion::kReplace_Op, false); 645 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 646 stack.restore(); 647 } 648 649 // Verify proper traversal of multi-element clip 650 { 651 SkClipStack stack; 652 stack.clipDevRect(insideRect, SkRegion::kIntersect_Op, false); 653 // Use a path for second clip to prevent in-place intersection 654 stack.clipDevPath(outsideCircle, SkRegion::kIntersect_Op, false); 655 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 656 } 657 658 // Intersect Op tests with rectangles 659 { 660 SkClipStack stack; 661 stack.clipDevRect(outsideRect, SkRegion::kIntersect_Op, false); 662 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 663 } 664 665 { 666 SkClipStack stack; 667 stack.clipDevRect(insideRect, SkRegion::kIntersect_Op, false); 668 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 669 } 670 671 { 672 SkClipStack stack; 673 stack.clipDevRect(intersectingRect, SkRegion::kIntersect_Op, false); 674 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 675 } 676 677 { 678 SkClipStack stack; 679 stack.clipDevRect(nonIntersectingRect, SkRegion::kIntersect_Op, false); 680 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 681 } 682 683 // Intersect Op tests with circle paths 684 { 685 SkClipStack stack; 686 stack.clipDevPath(outsideCircle, SkRegion::kIntersect_Op, false); 687 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 688 } 689 690 { 691 SkClipStack stack; 692 stack.clipDevPath(insideCircle, SkRegion::kIntersect_Op, false); 693 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 694 } 695 696 { 697 SkClipStack stack; 698 stack.clipDevPath(intersectingCircle, SkRegion::kIntersect_Op, false); 699 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 700 } 701 702 { 703 SkClipStack stack; 704 stack.clipDevPath(nonIntersectingCircle, SkRegion::kIntersect_Op, false); 705 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 706 } 707 708 // Intersect Op tests with inverse filled rectangles 709 { 710 SkClipStack stack; 711 SkPath path; 712 path.addRect(outsideRect); 713 path.toggleInverseFillType(); 714 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 715 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 716 } 717 718 { 719 SkClipStack stack; 720 SkPath path; 721 path.addRect(insideRect); 722 path.toggleInverseFillType(); 723 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 724 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 725 } 726 727 { 728 SkClipStack stack; 729 SkPath path; 730 path.addRect(intersectingRect); 731 path.toggleInverseFillType(); 732 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 733 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 734 } 735 736 { 737 SkClipStack stack; 738 SkPath path; 739 path.addRect(nonIntersectingRect); 740 path.toggleInverseFillType(); 741 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 742 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 743 } 744 745 // Intersect Op tests with inverse filled circles 746 { 747 SkClipStack stack; 748 SkPath path = outsideCircle; 749 path.toggleInverseFillType(); 750 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 751 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 752 } 753 754 { 755 SkClipStack stack; 756 SkPath path = insideCircle; 757 path.toggleInverseFillType(); 758 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 759 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 760 } 761 762 { 763 SkClipStack stack; 764 SkPath path = intersectingCircle; 765 path.toggleInverseFillType(); 766 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 767 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 768 } 769 770 { 771 SkClipStack stack; 772 SkPath path = nonIntersectingCircle; 773 path.toggleInverseFillType(); 774 stack.clipDevPath(path, SkRegion::kIntersect_Op, false); 775 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 776 } 777} 778 779/////////////////////////////////////////////////////////////////////////////////////////////////// 780 781#if SK_SUPPORT_GPU 782// Functions that add a shape to the clip stack. The shape is computed from a rectangle. 783// AA is always disabled since the clip stack reducer can cause changes in aa rasterization of the 784// stack. A fractional edge repeated in different elements may be rasterized fewer times using the 785// reduced stack. 786typedef void (*AddElementFunc) (const SkRect& rect, 787 bool invert, 788 SkRegion::Op op, 789 SkClipStack* stack); 790 791static void add_round_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) { 792 SkPath path; 793 SkScalar rx = rect.width() / 10; 794 SkScalar ry = rect.height() / 20; 795 path.addRoundRect(rect, rx, ry); 796 if (invert) { 797 path.setFillType(SkPath::kInverseWinding_FillType); 798 } 799 stack->clipDevPath(path, op, false); 800}; 801 802static void add_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) { 803 if (invert) { 804 SkPath path; 805 path.addRect(rect); 806 path.setFillType(SkPath::kInverseWinding_FillType); 807 stack->clipDevPath(path, op, false); 808 } else { 809 stack->clipDevRect(rect, op, false); 810 } 811}; 812 813static void add_oval(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) { 814 SkPath path; 815 path.addOval(rect); 816 if (invert) { 817 path.setFillType(SkPath::kInverseWinding_FillType); 818 } 819 stack->clipDevPath(path, op, false); 820}; 821 822static void add_elem_to_stack(const SkClipStack::Element& element, SkClipStack* stack) { 823 switch (element.getType()) { 824 case SkClipStack::Element::kRect_Type: 825 stack->clipDevRect(element.getRect(), element.getOp(), element.isAA()); 826 break; 827 case SkClipStack::Element::kPath_Type: 828 stack->clipDevPath(element.getPath(), element.getOp(), element.isAA()); 829 break; 830 case SkClipStack::Element::kEmpty_Type: 831 SkDEBUGFAIL("Why did the reducer produce an explicit empty."); 832 stack->clipEmpty(); 833 break; 834 } 835} 836 837static void add_elem_to_region(const SkClipStack::Element& element, 838 const SkIRect& bounds, 839 SkRegion* region) { 840 SkRegion elemRegion; 841 SkRegion boundsRgn(bounds); 842 843 switch (element.getType()) { 844 case SkClipStack::Element::kRect_Type: { 845 SkPath path; 846 path.addRect(element.getRect()); 847 elemRegion.setPath(path, boundsRgn); 848 break; 849 } 850 case SkClipStack::Element::kPath_Type: 851 elemRegion.setPath(element.getPath(), boundsRgn); 852 break; 853 case SkClipStack::Element::kEmpty_Type: 854 // 855 region->setEmpty(); 856 return; 857 } 858 region->op(elemRegion, element.getOp()); 859} 860 861// This can assist with debugging the clip stack reduction code when the test below fails. 862static inline void print_clip(const SkClipStack::Element& element) { 863 static const char* kOpStrs[] = { 864 "DF", 865 "IS", 866 "UN", 867 "XR", 868 "RD", 869 "RP", 870 }; 871 if (SkClipStack::Element::kEmpty_Type != element.getType()) { 872 const SkRect& bounds = element.getBounds(); 873 bool isRect = SkClipStack::Element::kRect_Type == element.getType(); 874 SkDebugf("%s %s %s [%f %f] x [%f %f]\n", 875 kOpStrs[element.getOp()], 876 (isRect ? "R" : "P"), 877 (element.isInverseFilled() ? "I" : " "), 878 bounds.fLeft, bounds.fRight, bounds.fTop, bounds.fBottom); 879 } else { 880 SkDebugf("EM\n"); 881 } 882} 883 884static void test_reduced_clip_stack(skiatest::Reporter* reporter) { 885 // We construct random clip stacks, reduce them, and then rasterize both versions to verify that 886 // they are equal. 887 888 // All the clip elements will be contained within these bounds. 889 static const SkRect kBounds = SkRect::MakeWH(100, 100); 890 891 enum { 892 kNumTests = 200, 893 kMinElemsPerTest = 1, 894 kMaxElemsPerTest = 50, 895 }; 896 897 // min/max size of a clip element as a fraction of kBounds. 898 static const SkScalar kMinElemSizeFrac = SK_Scalar1 / 5; 899 static const SkScalar kMaxElemSizeFrac = SK_Scalar1; 900 901 static const SkRegion::Op kOps[] = { 902 SkRegion::kDifference_Op, 903 SkRegion::kIntersect_Op, 904 SkRegion::kUnion_Op, 905 SkRegion::kXOR_Op, 906 SkRegion::kReverseDifference_Op, 907 SkRegion::kReplace_Op, 908 }; 909 910 // Replace operations short-circuit the optimizer. We want to make sure that we test this code 911 // path a little bit but we don't want it to prevent us from testing many longer traversals in 912 // the optimizer. 913 static const int kReplaceDiv = 4 * kMaxElemsPerTest; 914 915 // We want to test inverse fills. However, they are quite rare in practice so don't over do it. 916 static const SkScalar kFractionInverted = SK_Scalar1 / kMaxElemsPerTest; 917 918 static const AddElementFunc kElementFuncs[] = { 919 add_rect, 920 add_round_rect, 921 add_oval, 922 }; 923 924 SkRandom r; 925 926 for (int i = 0; i < kNumTests; ++i) { 927 // Randomly generate a clip stack. 928 SkClipStack stack; 929 int numElems = r.nextRangeU(kMinElemsPerTest, kMaxElemsPerTest); 930 for (int e = 0; e < numElems; ++e) { 931 SkRegion::Op op = kOps[r.nextULessThan(SK_ARRAY_COUNT(kOps))]; 932 if (op == SkRegion::kReplace_Op) { 933 if (r.nextU() % kReplaceDiv) { 934 --e; 935 continue; 936 } 937 } 938 939 // saves can change the clip stack behavior when an element is added. 940 bool doSave = r.nextBool(); 941 942 SkSize size = SkSize::Make( 943 SkScalarFloorToScalar(SkScalarMul(kBounds.width(), r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac))), 944 SkScalarFloorToScalar(SkScalarMul(kBounds.height(), r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac)))); 945 946 SkPoint xy = {SkScalarFloorToScalar(r.nextRangeScalar(kBounds.fLeft, kBounds.fRight - size.fWidth)), 947 SkScalarFloorToScalar(r.nextRangeScalar(kBounds.fTop, kBounds.fBottom - size.fHeight))}; 948 949 SkRect rect = SkRect::MakeXYWH(xy.fX, xy.fY, size.fWidth, size.fHeight); 950 951 bool invert = r.nextBiasedBool(kFractionInverted); 952 kElementFuncs[r.nextULessThan(SK_ARRAY_COUNT(kElementFuncs))](rect, invert, op, &stack); 953 if (doSave) { 954 stack.save(); 955 } 956 } 957 958 SkRect inflatedBounds = kBounds; 959 inflatedBounds.outset(kBounds.width() / 2, kBounds.height() / 2); 960 SkIRect inflatedIBounds; 961 inflatedBounds.roundOut(&inflatedIBounds); 962 963 typedef GrReducedClip::ElementList ElementList; 964 // Get the reduced version of the stack. 965 ElementList reducedClips; 966 967 GrReducedClip::InitialState initial; 968 SkIRect tBounds(inflatedIBounds); 969 SkIRect* tightBounds = r.nextBool() ? &tBounds : NULL; 970 GrReducedClip::ReduceClipStack(stack, 971 inflatedIBounds, 972 &reducedClips, 973 &initial, 974 tightBounds); 975 976 // Build a new clip stack based on the reduced clip elements 977 SkClipStack reducedStack; 978 if (GrReducedClip::kAllOut_InitialState == initial) { 979 // whether the result is bounded or not, the whole plane should start outside the clip. 980 reducedStack.clipEmpty(); 981 } 982 for (ElementList::Iter iter = reducedClips.headIter(); NULL != iter.get(); iter.next()) { 983 add_elem_to_stack(*iter.get(), &reducedStack); 984 } 985 986 // GrReducedClipStack assumes that the final result is clipped to the returned bounds 987 if (NULL != tightBounds) { 988 reducedStack.clipDevRect(*tightBounds, SkRegion::kIntersect_Op); 989 } 990 991 // convert both the original stack and reduced stack to SkRegions and see if they're equal 992 SkRegion region; 993 SkRegion reducedRegion; 994 995 region.setRect(inflatedIBounds); 996 const SkClipStack::Element* element; 997 SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart); 998 while ((element = iter.next())) { 999 add_elem_to_region(*element, inflatedIBounds, ®ion); 1000 } 1001 1002 reducedRegion.setRect(inflatedIBounds); 1003 iter.reset(reducedStack, SkClipStack::Iter::kBottom_IterStart); 1004 while ((element = iter.next())) { 1005 add_elem_to_region(*element, inflatedIBounds, &reducedRegion); 1006 } 1007 1008 REPORTER_ASSERT(reporter, region == reducedRegion); 1009 } 1010} 1011 1012#endif 1013/////////////////////////////////////////////////////////////////////////////////////////////////// 1014 1015static void TestClipStack(skiatest::Reporter* reporter) { 1016 SkClipStack stack; 1017 1018 REPORTER_ASSERT(reporter, 0 == stack.getSaveCount()); 1019 assert_count(reporter, stack, 0); 1020 1021 static const SkIRect gRects[] = { 1022 { 0, 0, 100, 100 }, 1023 { 25, 25, 125, 125 }, 1024 { 0, 0, 1000, 1000 }, 1025 { 0, 0, 75, 75 } 1026 }; 1027 for (size_t i = 0; i < SK_ARRAY_COUNT(gRects); i++) { 1028 stack.clipDevRect(gRects[i], SkRegion::kIntersect_Op); 1029 } 1030 1031 // all of the above rects should have been intersected, leaving only 1 rect 1032 SkClipStack::B2TIter iter(stack); 1033 const SkClipStack::Element* element = iter.next(); 1034 SkRect answer; 1035 answer.iset(25, 25, 75, 75); 1036 1037 REPORTER_ASSERT(reporter, NULL != element); 1038 REPORTER_ASSERT(reporter, SkClipStack::Element::kRect_Type == element->getType()); 1039 REPORTER_ASSERT(reporter, SkRegion::kIntersect_Op == element->getOp()); 1040 REPORTER_ASSERT(reporter, element->getRect() == answer); 1041 // now check that we only had one in our iterator 1042 REPORTER_ASSERT(reporter, !iter.next()); 1043 1044 stack.reset(); 1045 REPORTER_ASSERT(reporter, 0 == stack.getSaveCount()); 1046 assert_count(reporter, stack, 0); 1047 1048 test_assign_and_comparison(reporter); 1049 test_iterators(reporter); 1050 test_bounds(reporter, true); // once with rects 1051 test_bounds(reporter, false); // once with paths 1052 test_isWideOpen(reporter); 1053 test_rect_merging(reporter); 1054 test_rect_replace(reporter); 1055 test_rect_inverse_fill(reporter); 1056 test_path_replace(reporter); 1057 test_quickContains(reporter); 1058#if SK_SUPPORT_GPU 1059 test_reduced_clip_stack(reporter); 1060#endif 1061} 1062 1063#include "TestClassDef.h" 1064DEFINE_TESTCLASS("ClipStack", TestClipStackClass, TestClipStack) 1065