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 "SkClipStack.h" 10 #include "SkPath.h" 11 #include "SkRandom.h" 12 #include "SkRect.h" 13 #include "SkRegion.h" 14 15 #if SK_SUPPORT_GPU 16 #include "GrClipStackClip.h" 17 #include "GrReducedClip.h" 18 #include "GrResourceCache.h" 19 #include "GrSurfaceProxyPriv.h" 20 #include "GrTexture.h" 21 #include "GrTextureProxy.h" 22 typedef GrReducedClip::ElementList ElementList; 23 typedef GrReducedClip::InitialState InitialState; 24 #endif 25 26 static void test_assign_and_comparison(skiatest::Reporter* reporter) { 27 SkClipStack s; 28 bool doAA = false; 29 30 REPORTER_ASSERT(reporter, 0 == s.getSaveCount()); 31 32 // Build up a clip stack with a path, an empty clip, and a rect. 33 s.save(); 34 REPORTER_ASSERT(reporter, 1 == s.getSaveCount()); 35 36 SkPath p; 37 p.moveTo(5, 6); 38 p.lineTo(7, 8); 39 p.lineTo(5, 9); 40 p.close(); 41 s.clipPath(p, SkMatrix::I(), kIntersect_SkClipOp, doAA); 42 43 s.save(); 44 REPORTER_ASSERT(reporter, 2 == s.getSaveCount()); 45 46 SkRect r = SkRect::MakeLTRB(1, 2, 3, 4); 47 s.clipRect(r, SkMatrix::I(), kIntersect_SkClipOp, doAA); 48 r = SkRect::MakeLTRB(10, 11, 12, 13); 49 s.clipRect(r, SkMatrix::I(), kIntersect_SkClipOp, doAA); 50 51 s.save(); 52 REPORTER_ASSERT(reporter, 3 == s.getSaveCount()); 53 54 r = SkRect::MakeLTRB(14, 15, 16, 17); 55 s.clipRect(r, SkMatrix::I(), kUnion_SkClipOp, doAA); 56 57 // Test that assignment works. 58 SkClipStack copy = s; 59 REPORTER_ASSERT(reporter, s == copy); 60 61 // Test that different save levels triggers not equal. 62 s.restore(); 63 REPORTER_ASSERT(reporter, 2 == s.getSaveCount()); 64 REPORTER_ASSERT(reporter, s != copy); 65 66 // Test that an equal, but not copied version is equal. 67 s.save(); 68 REPORTER_ASSERT(reporter, 3 == s.getSaveCount()); 69 r = SkRect::MakeLTRB(14, 15, 16, 17); 70 s.clipRect(r, SkMatrix::I(), kUnion_SkClipOp, doAA); 71 REPORTER_ASSERT(reporter, s == copy); 72 73 // Test that a different op on one level triggers not equal. 74 s.restore(); 75 REPORTER_ASSERT(reporter, 2 == s.getSaveCount()); 76 s.save(); 77 REPORTER_ASSERT(reporter, 3 == s.getSaveCount()); 78 r = SkRect::MakeLTRB(14, 15, 16, 17); 79 s.clipRect(r, SkMatrix::I(), kIntersect_SkClipOp, doAA); 80 REPORTER_ASSERT(reporter, s != copy); 81 82 // Test that version constructed with rect-path rather than a rect is still considered equal. 83 s.restore(); 84 s.save(); 85 SkPath rp; 86 rp.addRect(r); 87 s.clipPath(rp, SkMatrix::I(), kUnion_SkClipOp, doAA); 88 REPORTER_ASSERT(reporter, s == copy); 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.clipRect(r, SkMatrix::I(), kUnion_SkClipOp, 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.clipPath(p, SkMatrix::I(), kIntersect_SkClipOp, 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.clipRect(gRects[i], SkMatrix::I(), kUnion_SkClipOp, false); 149 } 150 151 assert_count(reporter, stack, 4); 152 153 // bottom to top iteration 154 { 155 const SkClipStack::Element* element = nullptr; 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 = nullptr; 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 = nullptr; 186 187 SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart); 188 189 element = iter.skipToTopmost(kUnion_SkClipOp); 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, SkClipStack::Element::Type primType) { 197 static const int gNumCases = 20; 198 static const SkRect gAnswerRectsBW[gNumCases] = { 199 // A op B 200 { 40, 40, 50, 50 }, 201 { 10, 10, 50, 50 }, 202 { 10, 10, 80, 80 }, 203 { 10, 10, 80, 80 }, 204 { 40, 40, 80, 80 }, 205 206 // invA op B 207 { 40, 40, 80, 80 }, 208 { 0, 0, 100, 100 }, 209 { 0, 0, 100, 100 }, 210 { 0, 0, 100, 100 }, 211 { 40, 40, 50, 50 }, 212 213 // A op invB 214 { 10, 10, 50, 50 }, 215 { 40, 40, 50, 50 }, 216 { 0, 0, 100, 100 }, 217 { 0, 0, 100, 100 }, 218 { 0, 0, 100, 100 }, 219 220 // invA op invB 221 { 0, 0, 100, 100 }, 222 { 40, 40, 80, 80 }, 223 { 0, 0, 100, 100 }, 224 { 10, 10, 80, 80 }, 225 { 10, 10, 50, 50 }, 226 }; 227 228 static const SkClipOp gOps[] = { 229 kIntersect_SkClipOp, 230 kDifference_SkClipOp, 231 kUnion_SkClipOp, 232 kXOR_SkClipOp, 233 kReverseDifference_SkClipOp 234 }; 235 236 SkRect rectA, rectB; 237 238 rectA.iset(10, 10, 50, 50); 239 rectB.iset(40, 40, 80, 80); 240 241 SkRRect rrectA, rrectB; 242 rrectA.setOval(rectA); 243 rrectB.setRectXY(rectB, SkIntToScalar(1), SkIntToScalar(2)); 244 245 SkPath pathA, pathB; 246 247 pathA.addRoundRect(rectA, SkIntToScalar(5), SkIntToScalar(5)); 248 pathB.addRoundRect(rectB, SkIntToScalar(5), SkIntToScalar(5)); 249 250 SkClipStack stack; 251 SkRect devClipBound; 252 bool isIntersectionOfRects = false; 253 254 int testCase = 0; 255 int numBitTests = SkClipStack::Element::kPath_Type == primType ? 4 : 1; 256 for (int invBits = 0; invBits < numBitTests; ++invBits) { 257 for (size_t op = 0; op < SK_ARRAY_COUNT(gOps); ++op) { 258 259 stack.save(); 260 bool doInvA = SkToBool(invBits & 1); 261 bool doInvB = SkToBool(invBits & 2); 262 263 pathA.setFillType(doInvA ? SkPath::kInverseEvenOdd_FillType : 264 SkPath::kEvenOdd_FillType); 265 pathB.setFillType(doInvB ? SkPath::kInverseEvenOdd_FillType : 266 SkPath::kEvenOdd_FillType); 267 268 switch (primType) { 269 case SkClipStack::Element::kEmpty_Type: 270 SkDEBUGFAIL("Don't call this with kEmpty."); 271 break; 272 case SkClipStack::Element::kRect_Type: 273 stack.clipRect(rectA, SkMatrix::I(), kIntersect_SkClipOp, false); 274 stack.clipRect(rectB, SkMatrix::I(), gOps[op], false); 275 break; 276 case SkClipStack::Element::kRRect_Type: 277 stack.clipRRect(rrectA, SkMatrix::I(), kIntersect_SkClipOp, false); 278 stack.clipRRect(rrectB, SkMatrix::I(), gOps[op], false); 279 break; 280 case SkClipStack::Element::kPath_Type: 281 stack.clipPath(pathA, SkMatrix::I(), kIntersect_SkClipOp, false); 282 stack.clipPath(pathB, SkMatrix::I(), gOps[op], false); 283 break; 284 } 285 286 REPORTER_ASSERT(reporter, !stack.isWideOpen()); 287 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID != stack.getTopmostGenID()); 288 289 stack.getConservativeBounds(0, 0, 100, 100, &devClipBound, 290 &isIntersectionOfRects); 291 292 if (SkClipStack::Element::kRect_Type == primType) { 293 REPORTER_ASSERT(reporter, isIntersectionOfRects == 294 (gOps[op] == kIntersect_SkClipOp)); 295 } else { 296 REPORTER_ASSERT(reporter, !isIntersectionOfRects); 297 } 298 299 SkASSERT(testCase < gNumCases); 300 REPORTER_ASSERT(reporter, devClipBound == gAnswerRectsBW[testCase]); 301 ++testCase; 302 303 stack.restore(); 304 } 305 } 306 } 307 308 // Test out 'isWideOpen' entry point 309 static void test_isWideOpen(skiatest::Reporter* reporter) { 310 { 311 // Empty stack is wide open. Wide open stack means that gen id is wide open. 312 SkClipStack stack; 313 REPORTER_ASSERT(reporter, stack.isWideOpen()); 314 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID()); 315 } 316 317 SkRect rectA, rectB; 318 319 rectA.iset(10, 10, 40, 40); 320 rectB.iset(50, 50, 80, 80); 321 322 // Stack should initially be wide open 323 { 324 SkClipStack stack; 325 326 REPORTER_ASSERT(reporter, stack.isWideOpen()); 327 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID()); 328 } 329 330 // Test out case where the user specifies a union that includes everything 331 { 332 SkClipStack stack; 333 334 SkPath clipA, clipB; 335 336 clipA.addRoundRect(rectA, SkIntToScalar(5), SkIntToScalar(5)); 337 clipA.setFillType(SkPath::kInverseEvenOdd_FillType); 338 339 clipB.addRoundRect(rectB, SkIntToScalar(5), SkIntToScalar(5)); 340 clipB.setFillType(SkPath::kInverseEvenOdd_FillType); 341 342 stack.clipPath(clipA, SkMatrix::I(), kReplace_SkClipOp, false); 343 stack.clipPath(clipB, SkMatrix::I(), kUnion_SkClipOp, false); 344 345 REPORTER_ASSERT(reporter, stack.isWideOpen()); 346 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID()); 347 } 348 349 // Test out union w/ a wide open clip 350 { 351 SkClipStack stack; 352 353 stack.clipRect(rectA, SkMatrix::I(), kUnion_SkClipOp, false); 354 355 REPORTER_ASSERT(reporter, stack.isWideOpen()); 356 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID()); 357 } 358 359 // Test out empty difference from a wide open clip 360 { 361 SkClipStack stack; 362 363 SkRect emptyRect; 364 emptyRect.setEmpty(); 365 366 stack.clipRect(emptyRect, SkMatrix::I(), kDifference_SkClipOp, false); 367 368 REPORTER_ASSERT(reporter, stack.isWideOpen()); 369 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID()); 370 } 371 372 // Test out return to wide open 373 { 374 SkClipStack stack; 375 376 stack.save(); 377 378 stack.clipRect(rectA, SkMatrix::I(), kReplace_SkClipOp, false); 379 380 REPORTER_ASSERT(reporter, !stack.isWideOpen()); 381 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID != stack.getTopmostGenID()); 382 383 stack.restore(); 384 385 REPORTER_ASSERT(reporter, stack.isWideOpen()); 386 REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID()); 387 } 388 } 389 390 static int count(const SkClipStack& stack) { 391 392 SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart); 393 394 const SkClipStack::Element* element = nullptr; 395 int count = 0; 396 397 for (element = iter.prev(); element; element = iter.prev(), ++count) { 398 ; 399 } 400 401 return count; 402 } 403 404 static void test_rect_inverse_fill(skiatest::Reporter* reporter) { 405 // non-intersecting rectangles 406 SkRect rect = SkRect::MakeLTRB(0, 0, 10, 10); 407 408 SkPath path; 409 path.addRect(rect); 410 path.toggleInverseFillType(); 411 SkClipStack stack; 412 stack.clipPath(path, SkMatrix::I(), kIntersect_SkClipOp, false); 413 414 SkRect bounds; 415 SkClipStack::BoundsType boundsType; 416 stack.getBounds(&bounds, &boundsType); 417 REPORTER_ASSERT(reporter, SkClipStack::kInsideOut_BoundsType == boundsType); 418 REPORTER_ASSERT(reporter, bounds == rect); 419 } 420 421 static void test_rect_replace(skiatest::Reporter* reporter) { 422 SkRect rect = SkRect::MakeWH(100, 100); 423 SkRect rect2 = SkRect::MakeXYWH(50, 50, 100, 100); 424 425 SkRect bound; 426 SkClipStack::BoundsType type; 427 bool isIntersectionOfRects; 428 429 // Adding a new rect with the replace operator should not increase 430 // the stack depth. BW replacing BW. 431 { 432 SkClipStack stack; 433 REPORTER_ASSERT(reporter, 0 == count(stack)); 434 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, false); 435 REPORTER_ASSERT(reporter, 1 == count(stack)); 436 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, false); 437 REPORTER_ASSERT(reporter, 1 == count(stack)); 438 } 439 440 // Adding a new rect with the replace operator should not increase 441 // the stack depth. AA replacing AA. 442 { 443 SkClipStack stack; 444 REPORTER_ASSERT(reporter, 0 == count(stack)); 445 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, true); 446 REPORTER_ASSERT(reporter, 1 == count(stack)); 447 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, true); 448 REPORTER_ASSERT(reporter, 1 == count(stack)); 449 } 450 451 // Adding a new rect with the replace operator should not increase 452 // the stack depth. BW replacing AA replacing BW. 453 { 454 SkClipStack stack; 455 REPORTER_ASSERT(reporter, 0 == count(stack)); 456 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, false); 457 REPORTER_ASSERT(reporter, 1 == count(stack)); 458 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, true); 459 REPORTER_ASSERT(reporter, 1 == count(stack)); 460 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, false); 461 REPORTER_ASSERT(reporter, 1 == count(stack)); 462 } 463 464 // Make sure replace clip rects don't collapse too much. 465 { 466 SkClipStack stack; 467 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, false); 468 stack.clipRect(rect2, SkMatrix::I(), kIntersect_SkClipOp, false); 469 REPORTER_ASSERT(reporter, 1 == count(stack)); 470 471 stack.save(); 472 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, false); 473 REPORTER_ASSERT(reporter, 2 == count(stack)); 474 stack.getBounds(&bound, &type, &isIntersectionOfRects); 475 REPORTER_ASSERT(reporter, bound == rect); 476 stack.restore(); 477 REPORTER_ASSERT(reporter, 1 == count(stack)); 478 479 stack.save(); 480 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, false); 481 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, false); 482 REPORTER_ASSERT(reporter, 2 == count(stack)); 483 stack.restore(); 484 REPORTER_ASSERT(reporter, 1 == count(stack)); 485 486 stack.save(); 487 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, false); 488 stack.clipRect(rect2, SkMatrix::I(), kIntersect_SkClipOp, false); 489 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, false); 490 REPORTER_ASSERT(reporter, 2 == count(stack)); 491 stack.restore(); 492 REPORTER_ASSERT(reporter, 1 == count(stack)); 493 } 494 } 495 496 // Simplified path-based version of test_rect_replace. 497 static void test_path_replace(skiatest::Reporter* reporter) { 498 SkRect rect = SkRect::MakeWH(100, 100); 499 SkPath path; 500 path.addCircle(50, 50, 50); 501 502 // Replace operation doesn't grow the stack. 503 { 504 SkClipStack stack; 505 REPORTER_ASSERT(reporter, 0 == count(stack)); 506 stack.clipPath(path, SkMatrix::I(), kReplace_SkClipOp, false); 507 REPORTER_ASSERT(reporter, 1 == count(stack)); 508 stack.clipPath(path, SkMatrix::I(), kReplace_SkClipOp, false); 509 REPORTER_ASSERT(reporter, 1 == count(stack)); 510 } 511 512 // Replacing rect with path. 513 { 514 SkClipStack stack; 515 stack.clipRect(rect, SkMatrix::I(), kReplace_SkClipOp, true); 516 REPORTER_ASSERT(reporter, 1 == count(stack)); 517 stack.clipPath(path, SkMatrix::I(), kReplace_SkClipOp, true); 518 REPORTER_ASSERT(reporter, 1 == count(stack)); 519 } 520 } 521 522 // Test out SkClipStack's merging of rect clips. In particular exercise 523 // merging of aa vs. bw rects. 524 static void test_rect_merging(skiatest::Reporter* reporter) { 525 526 SkRect overlapLeft = SkRect::MakeLTRB(10, 10, 50, 50); 527 SkRect overlapRight = SkRect::MakeLTRB(40, 40, 80, 80); 528 529 SkRect nestedParent = SkRect::MakeLTRB(10, 10, 90, 90); 530 SkRect nestedChild = SkRect::MakeLTRB(40, 40, 60, 60); 531 532 SkRect bound; 533 SkClipStack::BoundsType type; 534 bool isIntersectionOfRects; 535 536 // all bw overlapping - should merge 537 { 538 SkClipStack stack; 539 540 stack.clipRect(overlapLeft, SkMatrix::I(), kReplace_SkClipOp, false); 541 542 stack.clipRect(overlapRight, SkMatrix::I(), kIntersect_SkClipOp, false); 543 544 REPORTER_ASSERT(reporter, 1 == count(stack)); 545 546 stack.getBounds(&bound, &type, &isIntersectionOfRects); 547 548 REPORTER_ASSERT(reporter, isIntersectionOfRects); 549 } 550 551 // all aa overlapping - should merge 552 { 553 SkClipStack stack; 554 555 stack.clipRect(overlapLeft, SkMatrix::I(), kReplace_SkClipOp, true); 556 557 stack.clipRect(overlapRight, SkMatrix::I(), kIntersect_SkClipOp, true); 558 559 REPORTER_ASSERT(reporter, 1 == count(stack)); 560 561 stack.getBounds(&bound, &type, &isIntersectionOfRects); 562 563 REPORTER_ASSERT(reporter, isIntersectionOfRects); 564 } 565 566 // mixed overlapping - should _not_ merge 567 { 568 SkClipStack stack; 569 570 stack.clipRect(overlapLeft, SkMatrix::I(), kReplace_SkClipOp, true); 571 572 stack.clipRect(overlapRight, SkMatrix::I(), kIntersect_SkClipOp, false); 573 574 REPORTER_ASSERT(reporter, 2 == count(stack)); 575 576 stack.getBounds(&bound, &type, &isIntersectionOfRects); 577 578 REPORTER_ASSERT(reporter, !isIntersectionOfRects); 579 } 580 581 // mixed nested (bw inside aa) - should merge 582 { 583 SkClipStack stack; 584 585 stack.clipRect(nestedParent, SkMatrix::I(), kReplace_SkClipOp, true); 586 587 stack.clipRect(nestedChild, SkMatrix::I(), kIntersect_SkClipOp, false); 588 589 REPORTER_ASSERT(reporter, 1 == count(stack)); 590 591 stack.getBounds(&bound, &type, &isIntersectionOfRects); 592 593 REPORTER_ASSERT(reporter, isIntersectionOfRects); 594 } 595 596 // mixed nested (aa inside bw) - should merge 597 { 598 SkClipStack stack; 599 600 stack.clipRect(nestedParent, SkMatrix::I(), kReplace_SkClipOp, false); 601 602 stack.clipRect(nestedChild, SkMatrix::I(), kIntersect_SkClipOp, true); 603 604 REPORTER_ASSERT(reporter, 1 == count(stack)); 605 606 stack.getBounds(&bound, &type, &isIntersectionOfRects); 607 608 REPORTER_ASSERT(reporter, isIntersectionOfRects); 609 } 610 611 // reverse nested (aa inside bw) - should _not_ merge 612 { 613 SkClipStack stack; 614 615 stack.clipRect(nestedChild, SkMatrix::I(), kReplace_SkClipOp, false); 616 617 stack.clipRect(nestedParent, SkMatrix::I(), kIntersect_SkClipOp, true); 618 619 REPORTER_ASSERT(reporter, 2 == count(stack)); 620 621 stack.getBounds(&bound, &type, &isIntersectionOfRects); 622 623 REPORTER_ASSERT(reporter, !isIntersectionOfRects); 624 } 625 } 626 627 static void test_quickContains(skiatest::Reporter* reporter) { 628 SkRect testRect = SkRect::MakeLTRB(10, 10, 40, 40); 629 SkRect insideRect = SkRect::MakeLTRB(20, 20, 30, 30); 630 SkRect intersectingRect = SkRect::MakeLTRB(25, 25, 50, 50); 631 SkRect outsideRect = SkRect::MakeLTRB(0, 0, 50, 50); 632 SkRect nonIntersectingRect = SkRect::MakeLTRB(100, 100, 110, 110); 633 634 SkPath insideCircle; 635 insideCircle.addCircle(25, 25, 5); 636 SkPath intersectingCircle; 637 intersectingCircle.addCircle(25, 40, 10); 638 SkPath outsideCircle; 639 outsideCircle.addCircle(25, 25, 50); 640 SkPath nonIntersectingCircle; 641 nonIntersectingCircle.addCircle(100, 100, 5); 642 643 { 644 SkClipStack stack; 645 stack.clipRect(outsideRect, SkMatrix::I(), kDifference_SkClipOp, false); 646 // return false because quickContains currently does not care for kDifference_SkClipOp 647 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 648 } 649 650 // Replace Op tests 651 { 652 SkClipStack stack; 653 stack.clipRect(outsideRect, SkMatrix::I(), kReplace_SkClipOp, false); 654 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 655 } 656 657 { 658 SkClipStack stack; 659 stack.clipRect(insideRect, SkMatrix::I(), kIntersect_SkClipOp, false); 660 stack.save(); // To prevent in-place substitution by replace OP 661 stack.clipRect(outsideRect, SkMatrix::I(), kReplace_SkClipOp, false); 662 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 663 stack.restore(); 664 } 665 666 { 667 SkClipStack stack; 668 stack.clipRect(outsideRect, SkMatrix::I(), kIntersect_SkClipOp, false); 669 stack.save(); // To prevent in-place substitution by replace OP 670 stack.clipRect(insideRect, SkMatrix::I(), kReplace_SkClipOp, false); 671 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 672 stack.restore(); 673 } 674 675 // Verify proper traversal of multi-element clip 676 { 677 SkClipStack stack; 678 stack.clipRect(insideRect, SkMatrix::I(), kIntersect_SkClipOp, false); 679 // Use a path for second clip to prevent in-place intersection 680 stack.clipPath(outsideCircle, SkMatrix::I(), kIntersect_SkClipOp, false); 681 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 682 } 683 684 // Intersect Op tests with rectangles 685 { 686 SkClipStack stack; 687 stack.clipRect(outsideRect, SkMatrix::I(), kIntersect_SkClipOp, false); 688 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 689 } 690 691 { 692 SkClipStack stack; 693 stack.clipRect(insideRect, SkMatrix::I(), kIntersect_SkClipOp, false); 694 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 695 } 696 697 { 698 SkClipStack stack; 699 stack.clipRect(intersectingRect, SkMatrix::I(), kIntersect_SkClipOp, false); 700 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 701 } 702 703 { 704 SkClipStack stack; 705 stack.clipRect(nonIntersectingRect, SkMatrix::I(), kIntersect_SkClipOp, false); 706 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 707 } 708 709 // Intersect Op tests with circle paths 710 { 711 SkClipStack stack; 712 stack.clipPath(outsideCircle, SkMatrix::I(), kIntersect_SkClipOp, false); 713 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 714 } 715 716 { 717 SkClipStack stack; 718 stack.clipPath(insideCircle, SkMatrix::I(), kIntersect_SkClipOp, false); 719 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 720 } 721 722 { 723 SkClipStack stack; 724 stack.clipPath(intersectingCircle, SkMatrix::I(), kIntersect_SkClipOp, false); 725 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 726 } 727 728 { 729 SkClipStack stack; 730 stack.clipPath(nonIntersectingCircle, SkMatrix::I(), kIntersect_SkClipOp, false); 731 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 732 } 733 734 // Intersect Op tests with inverse filled rectangles 735 { 736 SkClipStack stack; 737 SkPath path; 738 path.addRect(outsideRect); 739 path.toggleInverseFillType(); 740 stack.clipPath(path, SkMatrix::I(), kIntersect_SkClipOp, false); 741 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 742 } 743 744 { 745 SkClipStack stack; 746 SkPath path; 747 path.addRect(insideRect); 748 path.toggleInverseFillType(); 749 stack.clipPath(path, SkMatrix::I(), kIntersect_SkClipOp, false); 750 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 751 } 752 753 { 754 SkClipStack stack; 755 SkPath path; 756 path.addRect(intersectingRect); 757 path.toggleInverseFillType(); 758 stack.clipPath(path, SkMatrix::I(), kIntersect_SkClipOp, false); 759 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 760 } 761 762 { 763 SkClipStack stack; 764 SkPath path; 765 path.addRect(nonIntersectingRect); 766 path.toggleInverseFillType(); 767 stack.clipPath(path, SkMatrix::I(), kIntersect_SkClipOp, false); 768 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 769 } 770 771 // Intersect Op tests with inverse filled circles 772 { 773 SkClipStack stack; 774 SkPath path = outsideCircle; 775 path.toggleInverseFillType(); 776 stack.clipPath(path, SkMatrix::I(), kIntersect_SkClipOp, false); 777 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 778 } 779 780 { 781 SkClipStack stack; 782 SkPath path = insideCircle; 783 path.toggleInverseFillType(); 784 stack.clipPath(path, SkMatrix::I(), kIntersect_SkClipOp, false); 785 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 786 } 787 788 { 789 SkClipStack stack; 790 SkPath path = intersectingCircle; 791 path.toggleInverseFillType(); 792 stack.clipPath(path, SkMatrix::I(), kIntersect_SkClipOp, false); 793 REPORTER_ASSERT(reporter, false == stack.quickContains(testRect)); 794 } 795 796 { 797 SkClipStack stack; 798 SkPath path = nonIntersectingCircle; 799 path.toggleInverseFillType(); 800 stack.clipPath(path, SkMatrix::I(), kIntersect_SkClipOp, false); 801 REPORTER_ASSERT(reporter, true == stack.quickContains(testRect)); 802 } 803 } 804 805 static void set_region_to_stack(const SkClipStack& stack, const SkIRect& bounds, SkRegion* region) { 806 region->setRect(bounds); 807 SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart); 808 while (const SkClipStack::Element *element = iter.next()) { 809 SkRegion elemRegion; 810 SkRegion boundsRgn(bounds); 811 SkPath path; 812 813 switch (element->getType()) { 814 case SkClipStack::Element::kEmpty_Type: 815 elemRegion.setEmpty(); 816 break; 817 default: 818 element->asPath(&path); 819 elemRegion.setPath(path, boundsRgn); 820 break; 821 } 822 region->op(elemRegion, (SkRegion::Op)element->getOp()); 823 } 824 } 825 826 static void test_invfill_diff_bug(skiatest::Reporter* reporter) { 827 SkClipStack stack; 828 stack.clipRect({10, 10, 20, 20}, SkMatrix::I(), kIntersect_SkClipOp, false); 829 830 SkPath path; 831 path.addRect({30, 10, 40, 20}); 832 path.setFillType(SkPath::kInverseWinding_FillType); 833 stack.clipPath(path, SkMatrix::I(), kDifference_SkClipOp, false); 834 835 REPORTER_ASSERT(reporter, SkClipStack::kEmptyGenID == stack.getTopmostGenID()); 836 837 SkRect stackBounds; 838 SkClipStack::BoundsType stackBoundsType; 839 stack.getBounds(&stackBounds, &stackBoundsType); 840 841 REPORTER_ASSERT(reporter, stackBounds.isEmpty()); 842 REPORTER_ASSERT(reporter, SkClipStack::kNormal_BoundsType == stackBoundsType); 843 844 SkRegion region; 845 set_region_to_stack(stack, {0, 0, 50, 30}, ®ion); 846 847 REPORTER_ASSERT(reporter, region.isEmpty()); 848 } 849 850 /////////////////////////////////////////////////////////////////////////////////////////////////// 851 852 #if SK_SUPPORT_GPU 853 // Functions that add a shape to the clip stack. The shape is computed from a rectangle. 854 // AA is always disabled since the clip stack reducer can cause changes in aa rasterization of the 855 // stack. A fractional edge repeated in different elements may be rasterized fewer times using the 856 // reduced stack. 857 typedef void (*AddElementFunc) (const SkRect& rect, 858 bool invert, 859 SkClipOp op, 860 SkClipStack* stack, 861 bool doAA); 862 863 static void add_round_rect(const SkRect& rect, bool invert, SkClipOp op, SkClipStack* stack, 864 bool doAA) { 865 SkScalar rx = rect.width() / 10; 866 SkScalar ry = rect.height() / 20; 867 if (invert) { 868 SkPath path; 869 path.addRoundRect(rect, rx, ry); 870 path.setFillType(SkPath::kInverseWinding_FillType); 871 stack->clipPath(path, SkMatrix::I(), op, doAA); 872 } else { 873 SkRRect rrect; 874 rrect.setRectXY(rect, rx, ry); 875 stack->clipRRect(rrect, SkMatrix::I(), op, doAA); 876 } 877 }; 878 879 static void add_rect(const SkRect& rect, bool invert, SkClipOp op, SkClipStack* stack, 880 bool doAA) { 881 if (invert) { 882 SkPath path; 883 path.addRect(rect); 884 path.setFillType(SkPath::kInverseWinding_FillType); 885 stack->clipPath(path, SkMatrix::I(), op, doAA); 886 } else { 887 stack->clipRect(rect, SkMatrix::I(), op, doAA); 888 } 889 }; 890 891 static void add_oval(const SkRect& rect, bool invert, SkClipOp op, SkClipStack* stack, 892 bool doAA) { 893 SkPath path; 894 path.addOval(rect); 895 if (invert) { 896 path.setFillType(SkPath::kInverseWinding_FillType); 897 } 898 stack->clipPath(path, SkMatrix::I(), op, doAA); 899 }; 900 901 static void add_elem_to_stack(const SkClipStack::Element& element, SkClipStack* stack) { 902 switch (element.getType()) { 903 case SkClipStack::Element::kRect_Type: 904 stack->clipRect(element.getRect(), SkMatrix::I(), element.getOp(), element.isAA()); 905 break; 906 case SkClipStack::Element::kRRect_Type: 907 stack->clipRRect(element.getRRect(), SkMatrix::I(), element.getOp(), element.isAA()); 908 break; 909 case SkClipStack::Element::kPath_Type: 910 stack->clipPath(element.getPath(), SkMatrix::I(), element.getOp(), element.isAA()); 911 break; 912 case SkClipStack::Element::kEmpty_Type: 913 SkDEBUGFAIL("Why did the reducer produce an explicit empty."); 914 stack->clipEmpty(); 915 break; 916 } 917 } 918 919 static void test_reduced_clip_stack(skiatest::Reporter* reporter) { 920 // We construct random clip stacks, reduce them, and then rasterize both versions to verify that 921 // they are equal. 922 923 // All the clip elements will be contained within these bounds. 924 static const SkIRect kIBounds = SkIRect::MakeWH(100, 100); 925 static const SkRect kBounds = SkRect::Make(kIBounds); 926 927 enum { 928 kNumTests = 250, 929 kMinElemsPerTest = 1, 930 kMaxElemsPerTest = 50, 931 }; 932 933 // min/max size of a clip element as a fraction of kBounds. 934 static const SkScalar kMinElemSizeFrac = SK_Scalar1 / 5; 935 static const SkScalar kMaxElemSizeFrac = SK_Scalar1; 936 937 static const SkClipOp kOps[] = { 938 kDifference_SkClipOp, 939 kIntersect_SkClipOp, 940 kUnion_SkClipOp, 941 kXOR_SkClipOp, 942 kReverseDifference_SkClipOp, 943 kReplace_SkClipOp, 944 }; 945 946 // Replace operations short-circuit the optimizer. We want to make sure that we test this code 947 // path a little bit but we don't want it to prevent us from testing many longer traversals in 948 // the optimizer. 949 static const int kReplaceDiv = 4 * kMaxElemsPerTest; 950 951 // We want to test inverse fills. However, they are quite rare in practice so don't over do it. 952 static const SkScalar kFractionInverted = SK_Scalar1 / kMaxElemsPerTest; 953 954 static const SkScalar kFractionAntialiased = 0.25; 955 956 static const AddElementFunc kElementFuncs[] = { 957 add_rect, 958 add_round_rect, 959 add_oval, 960 }; 961 962 SkRandom r; 963 964 for (int i = 0; i < kNumTests; ++i) { 965 SkString testCase; 966 testCase.printf("Iteration %d", i); 967 968 // Randomly generate a clip stack. 969 SkClipStack stack; 970 int numElems = r.nextRangeU(kMinElemsPerTest, kMaxElemsPerTest); 971 bool doAA = r.nextBiasedBool(kFractionAntialiased); 972 for (int e = 0; e < numElems; ++e) { 973 SkClipOp op = kOps[r.nextULessThan(SK_ARRAY_COUNT(kOps))]; 974 if (op == kReplace_SkClipOp) { 975 if (r.nextU() % kReplaceDiv) { 976 --e; 977 continue; 978 } 979 } 980 981 // saves can change the clip stack behavior when an element is added. 982 bool doSave = r.nextBool(); 983 984 SkSize size = SkSize::Make( 985 kBounds.width() * r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac), 986 kBounds.height() * r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac)); 987 988 SkPoint xy = {r.nextRangeScalar(kBounds.fLeft, kBounds.fRight - size.fWidth), 989 r.nextRangeScalar(kBounds.fTop, kBounds.fBottom - size.fHeight)}; 990 991 SkRect rect; 992 if (doAA) { 993 rect.setXYWH(xy.fX, xy.fY, size.fWidth, size.fHeight); 994 if (GrClip::IsPixelAligned(rect)) { 995 // Don't create an element that may accidentally become not antialiased. 996 rect.outset(0.5f, 0.5f); 997 } 998 SkASSERT(!GrClip::IsPixelAligned(rect)); 999 } else { 1000 rect.setXYWH(SkScalarFloorToScalar(xy.fX), 1001 SkScalarFloorToScalar(xy.fY), 1002 SkScalarCeilToScalar(size.fWidth), 1003 SkScalarCeilToScalar(size.fHeight)); 1004 } 1005 1006 bool invert = r.nextBiasedBool(kFractionInverted); 1007 1008 kElementFuncs[r.nextULessThan(SK_ARRAY_COUNT(kElementFuncs))](rect, invert, op, &stack, 1009 doAA); 1010 if (doSave) { 1011 stack.save(); 1012 } 1013 } 1014 1015 // Zero the memory we will new the GrReducedClip into. This ensures the elements gen ID 1016 // will be kInvalidGenID if left uninitialized. 1017 SkAlignedSTStorage<1, GrReducedClip> storage; 1018 memset(storage.get(), 0, sizeof(GrReducedClip)); 1019 GR_STATIC_ASSERT(0 == SkClipStack::kInvalidGenID); 1020 1021 // Get the reduced version of the stack. 1022 SkRect queryBounds = kBounds; 1023 queryBounds.outset(kBounds.width() / 2, kBounds.height() / 2); 1024 const GrReducedClip* reduced = new (storage.get()) GrReducedClip(stack, queryBounds); 1025 1026 REPORTER_ASSERT_MESSAGE(reporter, 1027 reduced->elements().isEmpty() || 1028 SkClipStack::kInvalidGenID != reduced->elementsGenID(), 1029 testCase.c_str()); 1030 1031 if (!reduced->elements().isEmpty()) { 1032 REPORTER_ASSERT_MESSAGE(reporter, reduced->hasIBounds(), testCase.c_str()); 1033 SkRect stackBounds; 1034 SkClipStack::BoundsType stackBoundsType; 1035 stack.getBounds(&stackBounds, &stackBoundsType); 1036 if (SkClipStack::kNormal_BoundsType == stackBoundsType) { 1037 // Unless GrReducedClip starts doing some heroic tightening of the clip bounds, this 1038 // will be true since the stack bounds are completely contained inside the query. 1039 REPORTER_ASSERT_MESSAGE(reporter, 1040 GrClip::IsInsideClip(reduced->ibounds(), stackBounds), 1041 testCase.c_str()); 1042 } 1043 REPORTER_ASSERT_MESSAGE(reporter, reduced->requiresAA() == doAA, testCase.c_str()); 1044 } 1045 1046 // Build a new clip stack based on the reduced clip elements 1047 SkClipStack reducedStack; 1048 if (GrReducedClip::InitialState::kAllOut == reduced->initialState()) { 1049 // whether the result is bounded or not, the whole plane should start outside the clip. 1050 reducedStack.clipEmpty(); 1051 } 1052 for (ElementList::Iter iter(reduced->elements()); iter.get(); iter.next()) { 1053 add_elem_to_stack(*iter.get(), &reducedStack); 1054 } 1055 1056 SkIRect ibounds = reduced->hasIBounds() ? reduced->ibounds() : kIBounds; 1057 1058 // GrReducedClipStack assumes that the final result is clipped to the returned bounds 1059 reducedStack.clipDevRect(ibounds, kIntersect_SkClipOp); 1060 stack.clipDevRect(ibounds, kIntersect_SkClipOp); 1061 1062 // convert both the original stack and reduced stack to SkRegions and see if they're equal 1063 SkRegion region; 1064 set_region_to_stack(stack, ibounds, ®ion); 1065 1066 SkRegion reducedRegion; 1067 set_region_to_stack(reducedStack, ibounds, &reducedRegion); 1068 1069 REPORTER_ASSERT_MESSAGE(reporter, region == reducedRegion, testCase.c_str()); 1070 1071 reduced->~GrReducedClip(); 1072 } 1073 } 1074 1075 #ifdef SK_BUILD_FOR_WIN 1076 #define SUPPRESS_VISIBILITY_WARNING 1077 #else 1078 #define SUPPRESS_VISIBILITY_WARNING __attribute__((visibility("hidden"))) 1079 #endif 1080 1081 static void test_reduced_clip_stack_genid(skiatest::Reporter* reporter) { 1082 { 1083 SkClipStack stack; 1084 stack.clipRect(SkRect::MakeXYWH(0, 0, 100, 100), SkMatrix::I(), kReplace_SkClipOp, 1085 true); 1086 stack.clipRect(SkRect::MakeXYWH(0, 0, SkScalar(50.3), SkScalar(50.3)), SkMatrix::I(), 1087 kReplace_SkClipOp, true); 1088 SkRect bounds = SkRect::MakeXYWH(0, 0, 100, 100); 1089 1090 SkAlignedSTStorage<1, GrReducedClip> storage; 1091 memset(storage.get(), 0, sizeof(GrReducedClip)); 1092 GR_STATIC_ASSERT(0 == SkClipStack::kInvalidGenID); 1093 const GrReducedClip* reduced = new (storage.get()) GrReducedClip(stack, bounds); 1094 1095 REPORTER_ASSERT(reporter, reduced->elements().count() == 1); 1096 // Clips will be cached based on the generation id. Make sure the gen id is valid. 1097 REPORTER_ASSERT(reporter, SkClipStack::kInvalidGenID != reduced->elementsGenID()); 1098 1099 reduced->~GrReducedClip(); 1100 } 1101 { 1102 SkClipStack stack; 1103 1104 // Create a clip with following 25.3, 25.3 boxes which are 25 apart: 1105 // A B 1106 // C D 1107 1108 stack.clipRect(SkRect::MakeXYWH(0, 0, SkScalar(25.3), SkScalar(25.3)), SkMatrix::I(), 1109 kReplace_SkClipOp, true); 1110 uint32_t genIDA = stack.getTopmostGenID(); 1111 stack.clipRect(SkRect::MakeXYWH(50, 0, SkScalar(25.3), SkScalar(25.3)), SkMatrix::I(), 1112 kUnion_SkClipOp, true); 1113 uint32_t genIDB = stack.getTopmostGenID(); 1114 stack.clipRect(SkRect::MakeXYWH(0, 50, SkScalar(25.3), SkScalar(25.3)), SkMatrix::I(), 1115 kUnion_SkClipOp, true); 1116 uint32_t genIDC = stack.getTopmostGenID(); 1117 stack.clipRect(SkRect::MakeXYWH(50, 50, SkScalar(25.3), SkScalar(25.3)), SkMatrix::I(), 1118 kUnion_SkClipOp, true); 1119 uint32_t genIDD = stack.getTopmostGenID(); 1120 1121 1122 #define IXYWH SkIRect::MakeXYWH 1123 #define XYWH SkRect::MakeXYWH 1124 1125 SkIRect stackBounds = IXYWH(0, 0, 76, 76); 1126 1127 // The base test is to test each rect in two ways: 1128 // 1) The box dimensions. (Should reduce to "all in", no elements). 1129 // 2) A bit over the box dimensions. 1130 // In the case 2, test that the generation id is what is expected. 1131 // The rects are of fractional size so that case 2 never gets optimized to an empty element 1132 // list. 1133 1134 // Not passing in tighter bounds is tested for consistency. 1135 static const struct SUPPRESS_VISIBILITY_WARNING { 1136 SkRect testBounds; 1137 int reducedClipCount; 1138 uint32_t reducedGenID; 1139 InitialState initialState; 1140 SkIRect clipIRect; 1141 // parameter. 1142 } testCases[] = { 1143 1144 // Rect A. 1145 { XYWH(0, 0, 25, 25), 0, SkClipStack::kInvalidGenID, GrReducedClip::InitialState::kAllIn, IXYWH(0, 0, 25, 25) }, 1146 { XYWH(0.1f, 0.1f, 25.1f, 25.1f), 0, SkClipStack::kInvalidGenID, GrReducedClip::InitialState::kAllIn, IXYWH(0, 0, 26, 26) }, 1147 { XYWH(0, 0, 27, 27), 1, genIDA, GrReducedClip::InitialState::kAllOut, IXYWH(0, 0, 27, 27)}, 1148 1149 // Rect B. 1150 { XYWH(50, 0, 25, 25), 0, SkClipStack::kInvalidGenID, GrReducedClip::InitialState::kAllIn, IXYWH(50, 0, 25, 25) }, 1151 { XYWH(50, 0, 25.3f, 25.3f), 0, SkClipStack::kInvalidGenID, GrReducedClip::InitialState::kAllIn, IXYWH(50, 0, 26, 26) }, 1152 { XYWH(50, 0, 27, 27), 1, genIDB, GrReducedClip::InitialState::kAllOut, IXYWH(50, 0, 26, 27) }, 1153 1154 // Rect C. 1155 { XYWH(0, 50, 25, 25), 0, SkClipStack::kInvalidGenID, GrReducedClip::InitialState::kAllIn, IXYWH(0, 50, 25, 25) }, 1156 { XYWH(0.2f, 50.1f, 25.1f, 25.2f), 0, SkClipStack::kInvalidGenID, GrReducedClip::InitialState::kAllIn, IXYWH(0, 50, 26, 26) }, 1157 { XYWH(0, 50, 27, 27), 1, genIDC, GrReducedClip::InitialState::kAllOut, IXYWH(0, 50, 27, 26) }, 1158 1159 // Rect D. 1160 { XYWH(50, 50, 25, 25), 0, SkClipStack::kInvalidGenID, GrReducedClip::InitialState::kAllIn, IXYWH(50, 50, 25, 25)}, 1161 { XYWH(50.3f, 50.3f, 25, 25), 0, SkClipStack::kInvalidGenID, GrReducedClip::InitialState::kAllIn, IXYWH(50, 50, 26, 26)}, 1162 { XYWH(50, 50, 27, 27), 1, genIDD, GrReducedClip::InitialState::kAllOut, IXYWH(50, 50, 26, 26)}, 1163 1164 // Other tests: 1165 { XYWH(0, 0, 100, 100), 4, genIDD, GrReducedClip::InitialState::kAllOut, stackBounds }, 1166 1167 // Rect in the middle, touches none. 1168 { XYWH(26, 26, 24, 24), 0, SkClipStack::kInvalidGenID, GrReducedClip::InitialState::kAllOut, IXYWH(26, 26, 24, 24) }, 1169 1170 // Rect in the middle, touches all the rects. GenID is the last rect. 1171 { XYWH(24, 24, 27, 27), 4, genIDD, GrReducedClip::InitialState::kAllOut, IXYWH(24, 24, 27, 27) }, 1172 }; 1173 1174 #undef XYWH 1175 #undef IXYWH 1176 1177 for (size_t i = 0; i < SK_ARRAY_COUNT(testCases); ++i) { 1178 const GrReducedClip reduced(stack, testCases[i].testBounds); 1179 REPORTER_ASSERT(reporter, reduced.elements().count() == testCases[i].reducedClipCount); 1180 SkASSERT(reduced.elements().count() == testCases[i].reducedClipCount); 1181 if (reduced.elements().count()) { 1182 REPORTER_ASSERT(reporter, reduced.elementsGenID() == testCases[i].reducedGenID); 1183 SkASSERT(reduced.elementsGenID() == testCases[i].reducedGenID); 1184 } 1185 REPORTER_ASSERT(reporter, reduced.initialState() == testCases[i].initialState); 1186 SkASSERT(reduced.initialState() == testCases[i].initialState); 1187 REPORTER_ASSERT(reporter, reduced.hasIBounds()); 1188 SkASSERT(reduced.hasIBounds()); 1189 REPORTER_ASSERT(reporter, reduced.ibounds() == testCases[i].clipIRect); 1190 SkASSERT(reduced.ibounds() == testCases[i].clipIRect); 1191 } 1192 } 1193 } 1194 1195 static void test_reduced_clip_stack_no_aa_crash(skiatest::Reporter* reporter) { 1196 SkClipStack stack; 1197 stack.clipDevRect(SkIRect::MakeXYWH(0, 0, 100, 100), kReplace_SkClipOp); 1198 stack.clipDevRect(SkIRect::MakeXYWH(0, 0, 50, 50), kReplace_SkClipOp); 1199 SkRect bounds = SkRect::MakeXYWH(0, 0, 100, 100); 1200 1201 // At the time, this would crash. 1202 const GrReducedClip reduced(stack, bounds); 1203 REPORTER_ASSERT(reporter, reduced.elements().isEmpty()); 1204 } 1205 1206 enum class ClipMethod { 1207 kSkipDraw, 1208 kIgnoreClip, 1209 kScissor, 1210 kAAElements 1211 }; 1212 1213 static void test_aa_query(skiatest::Reporter* reporter, const SkString& testName, 1214 const SkClipStack& stack, const SkMatrix& queryXform, 1215 const SkRect& preXformQuery, ClipMethod expectedMethod, 1216 int numExpectedElems = 0) { 1217 SkRect queryBounds; 1218 queryXform.mapRect(&queryBounds, preXformQuery); 1219 const GrReducedClip reduced(stack, queryBounds); 1220 1221 SkClipStack::BoundsType stackBoundsType; 1222 SkRect stackBounds; 1223 stack.getBounds(&stackBounds, &stackBoundsType); 1224 1225 switch (expectedMethod) { 1226 case ClipMethod::kSkipDraw: 1227 SkASSERT(0 == numExpectedElems); 1228 REPORTER_ASSERT_MESSAGE(reporter, reduced.elements().isEmpty(), testName.c_str()); 1229 REPORTER_ASSERT_MESSAGE(reporter, 1230 GrReducedClip::InitialState::kAllOut == reduced.initialState(), 1231 testName.c_str()); 1232 return; 1233 case ClipMethod::kIgnoreClip: 1234 SkASSERT(0 == numExpectedElems); 1235 REPORTER_ASSERT_MESSAGE(reporter, 1236 !reduced.hasIBounds() || 1237 GrClip::IsInsideClip(reduced.ibounds(), queryBounds), 1238 testName.c_str()); 1239 REPORTER_ASSERT_MESSAGE(reporter, reduced.elements().isEmpty(), testName.c_str()); 1240 REPORTER_ASSERT_MESSAGE(reporter, 1241 GrReducedClip::InitialState::kAllIn == reduced.initialState(), 1242 testName.c_str()); 1243 return; 1244 case ClipMethod::kScissor: { 1245 SkASSERT(SkClipStack::kNormal_BoundsType == stackBoundsType); 1246 SkASSERT(0 == numExpectedElems); 1247 SkIRect expectedScissor; 1248 stackBounds.round(&expectedScissor); 1249 REPORTER_ASSERT_MESSAGE(reporter, reduced.elements().isEmpty(), testName.c_str()); 1250 REPORTER_ASSERT_MESSAGE(reporter, reduced.hasIBounds(), testName.c_str()); 1251 REPORTER_ASSERT_MESSAGE(reporter, expectedScissor == reduced.ibounds(), 1252 testName.c_str()); 1253 REPORTER_ASSERT_MESSAGE(reporter, 1254 GrReducedClip::InitialState::kAllIn == reduced.initialState(), 1255 testName.c_str()); 1256 return; 1257 } 1258 case ClipMethod::kAAElements: { 1259 SkIRect expectedClipIBounds = GrClip::GetPixelIBounds(queryBounds); 1260 if (SkClipStack::kNormal_BoundsType == stackBoundsType) { 1261 SkAssertResult(expectedClipIBounds.intersect(GrClip::GetPixelIBounds(stackBounds))); 1262 } 1263 REPORTER_ASSERT_MESSAGE(reporter, numExpectedElems == reduced.elements().count(), 1264 testName.c_str()); 1265 REPORTER_ASSERT_MESSAGE(reporter, reduced.hasIBounds(), testName.c_str()); 1266 REPORTER_ASSERT_MESSAGE(reporter, expectedClipIBounds == reduced.ibounds(), 1267 testName.c_str()); 1268 REPORTER_ASSERT_MESSAGE(reporter, reduced.requiresAA() == !reduced.elements().isEmpty(), 1269 testName.c_str()); 1270 break; 1271 } 1272 } 1273 } 1274 1275 static void test_reduced_clip_stack_aa(skiatest::Reporter* reporter) { 1276 constexpr SkScalar IL = 2, IT = 1, IR = 6, IB = 7; // Pixel aligned rect. 1277 constexpr SkScalar L = 2.2f, T = 1.7f, R = 5.8f, B = 7.3f; // Generic rect. 1278 constexpr SkScalar l = 3.3f, t = 2.8f, r = 4.7f, b = 6.2f; // Small rect contained in R. 1279 1280 SkRect alignedRect = {IL, IT, IR, IB}; 1281 SkRect rect = {L, T, R, B}; 1282 SkRect innerRect = {l, t, r, b}; 1283 1284 SkMatrix m; 1285 m.setIdentity(); 1286 1287 constexpr SkScalar kMinScale = 2.0001f; 1288 constexpr SkScalar kMaxScale = 3; 1289 constexpr int kNumIters = 8; 1290 1291 SkString name; 1292 SkRandom rand; 1293 1294 for (int i = 0; i < kNumIters; ++i) { 1295 // Pixel-aligned rect (iior=true). 1296 name.printf("Pixel-aligned rect test, iter %i", i); 1297 SkClipStack stack; 1298 stack.clipRect(alignedRect, SkMatrix::I(), kIntersect_SkClipOp, true); 1299 test_aa_query(reporter, name, stack, m, {IL, IT, IR, IB}, ClipMethod::kIgnoreClip); 1300 test_aa_query(reporter, name, stack, m, {IL, IT-1, IR, IT}, ClipMethod::kSkipDraw); 1301 test_aa_query(reporter, name, stack, m, {IL, IT-2, IR, IB}, ClipMethod::kScissor); 1302 1303 // Rect (iior=true). 1304 name.printf("Rect test, iter %i", i); 1305 stack.reset(); 1306 stack.clipRect(rect, SkMatrix::I(), kIntersect_SkClipOp, true); 1307 test_aa_query(reporter, name, stack, m, {L, T, R, B}, ClipMethod::kIgnoreClip); 1308 test_aa_query(reporter, name, stack, m, {L-.1f, T, L, B}, ClipMethod::kSkipDraw); 1309 test_aa_query(reporter, name, stack, m, {L-.1f, T, L+.1f, B}, ClipMethod::kAAElements, 1); 1310 1311 // Difference rect (iior=false, inside-out bounds). 1312 name.printf("Difference rect test, iter %i", i); 1313 stack.reset(); 1314 stack.clipRect(rect, SkMatrix::I(), kDifference_SkClipOp, true); 1315 test_aa_query(reporter, name, stack, m, {L, T, R, B}, ClipMethod::kSkipDraw); 1316 test_aa_query(reporter, name, stack, m, {L, T-.1f, R, T}, ClipMethod::kIgnoreClip); 1317 test_aa_query(reporter, name, stack, m, {L, T-.1f, R, T+.1f}, ClipMethod::kAAElements, 1); 1318 1319 // Complex clip (iior=false, normal bounds). 1320 name.printf("Complex clip test, iter %i", i); 1321 stack.reset(); 1322 stack.clipRect(rect, SkMatrix::I(), kIntersect_SkClipOp, true); 1323 stack.clipRect(innerRect, SkMatrix::I(), kXOR_SkClipOp, true); 1324 test_aa_query(reporter, name, stack, m, {l, t, r, b}, ClipMethod::kSkipDraw); 1325 test_aa_query(reporter, name, stack, m, {r-.1f, t, R, b}, ClipMethod::kAAElements, 1); 1326 test_aa_query(reporter, name, stack, m, {r-.1f, t, R+.1f, b}, ClipMethod::kAAElements, 2); 1327 test_aa_query(reporter, name, stack, m, {r, t, R+.1f, b}, ClipMethod::kAAElements, 1); 1328 test_aa_query(reporter, name, stack, m, {r, t, R, b}, ClipMethod::kIgnoreClip); 1329 test_aa_query(reporter, name, stack, m, {R, T, R+.1f, B}, ClipMethod::kSkipDraw); 1330 1331 // Complex clip where outer rect is pixel aligned (iior=false, normal bounds). 1332 name.printf("Aligned Complex clip test, iter %i", i); 1333 stack.reset(); 1334 stack.clipRect(alignedRect, SkMatrix::I(), kIntersect_SkClipOp, true); 1335 stack.clipRect(innerRect, SkMatrix::I(), kXOR_SkClipOp, true); 1336 test_aa_query(reporter, name, stack, m, {l, t, r, b}, ClipMethod::kSkipDraw); 1337 test_aa_query(reporter, name, stack, m, {l, b-.1f, r, IB}, ClipMethod::kAAElements, 1); 1338 test_aa_query(reporter, name, stack, m, {l, b-.1f, r, IB+.1f}, ClipMethod::kAAElements, 1); 1339 test_aa_query(reporter, name, stack, m, {l, b, r, IB+.1f}, ClipMethod::kAAElements, 0); 1340 test_aa_query(reporter, name, stack, m, {l, b, r, IB}, ClipMethod::kIgnoreClip); 1341 test_aa_query(reporter, name, stack, m, {IL, IB, IR, IB+.1f}, ClipMethod::kSkipDraw); 1342 1343 // Apply random transforms and try again. This ensures the clip stack reduction is hardened 1344 // against FP rounding error. 1345 SkScalar sx = rand.nextRangeScalar(kMinScale, kMaxScale); 1346 sx = SkScalarFloorToScalar(sx * alignedRect.width()) / alignedRect.width(); 1347 SkScalar sy = rand.nextRangeScalar(kMinScale, kMaxScale); 1348 sy = SkScalarFloorToScalar(sy * alignedRect.height()) / alignedRect.height(); 1349 SkScalar tx = SkScalarRoundToScalar(sx * alignedRect.x()) - sx * alignedRect.x(); 1350 SkScalar ty = SkScalarRoundToScalar(sy * alignedRect.y()) - sy * alignedRect.y(); 1351 1352 SkMatrix xform = SkMatrix::MakeScale(sx, sy); 1353 xform.postTranslate(tx, ty); 1354 xform.mapRect(&alignedRect); 1355 xform.mapRect(&rect); 1356 xform.mapRect(&innerRect); 1357 m.postConcat(xform); 1358 } 1359 } 1360 1361 static void test_tiny_query_bounds_assertion_bug(skiatest::Reporter* reporter) { 1362 // https://bugs.chromium.org/p/skia/issues/detail?id=5990 1363 const SkRect clipBounds = SkRect::MakeXYWH(1.5f, 100, 1000, 1000); 1364 1365 SkClipStack rectStack; 1366 rectStack.clipRect(clipBounds, SkMatrix::I(), kIntersect_SkClipOp, true); 1367 1368 SkPath clipPath; 1369 clipPath.moveTo(clipBounds.left(), clipBounds.top()); 1370 clipPath.quadTo(clipBounds.right(), clipBounds.top(), 1371 clipBounds.right(), clipBounds.bottom()); 1372 clipPath.quadTo(clipBounds.left(), clipBounds.bottom(), 1373 clipBounds.left(), clipBounds.top()); 1374 SkClipStack pathStack; 1375 pathStack.clipPath(clipPath, SkMatrix::I(), kIntersect_SkClipOp, true); 1376 1377 for (const SkClipStack& stack : {rectStack, pathStack}) { 1378 for (SkRect queryBounds : {SkRect::MakeXYWH(53, 60, GrClip::kBoundsTolerance, 1000), 1379 SkRect::MakeXYWH(53, 60, GrClip::kBoundsTolerance/2, 1000), 1380 SkRect::MakeXYWH(53, 160, 1000, GrClip::kBoundsTolerance), 1381 SkRect::MakeXYWH(53, 160, 1000, GrClip::kBoundsTolerance/2)}) { 1382 const GrReducedClip reduced(stack, queryBounds); 1383 REPORTER_ASSERT(reporter, !reduced.hasIBounds()); 1384 REPORTER_ASSERT(reporter, reduced.elements().isEmpty()); 1385 REPORTER_ASSERT(reporter, 1386 GrReducedClip::InitialState::kAllOut == reduced.initialState()); 1387 } 1388 } 1389 } 1390 1391 #endif 1392 1393 DEF_TEST(ClipStack, reporter) { 1394 SkClipStack stack; 1395 1396 REPORTER_ASSERT(reporter, 0 == stack.getSaveCount()); 1397 assert_count(reporter, stack, 0); 1398 1399 static const SkIRect gRects[] = { 1400 { 0, 0, 100, 100 }, 1401 { 25, 25, 125, 125 }, 1402 { 0, 0, 1000, 1000 }, 1403 { 0, 0, 75, 75 } 1404 }; 1405 for (size_t i = 0; i < SK_ARRAY_COUNT(gRects); i++) { 1406 stack.clipDevRect(gRects[i], kIntersect_SkClipOp); 1407 } 1408 1409 // all of the above rects should have been intersected, leaving only 1 rect 1410 SkClipStack::B2TIter iter(stack); 1411 const SkClipStack::Element* element = iter.next(); 1412 SkRect answer; 1413 answer.iset(25, 25, 75, 75); 1414 1415 REPORTER_ASSERT(reporter, element); 1416 REPORTER_ASSERT(reporter, SkClipStack::Element::kRect_Type == element->getType()); 1417 REPORTER_ASSERT(reporter, kIntersect_SkClipOp == element->getOp()); 1418 REPORTER_ASSERT(reporter, element->getRect() == answer); 1419 // now check that we only had one in our iterator 1420 REPORTER_ASSERT(reporter, !iter.next()); 1421 1422 stack.reset(); 1423 REPORTER_ASSERT(reporter, 0 == stack.getSaveCount()); 1424 assert_count(reporter, stack, 0); 1425 1426 test_assign_and_comparison(reporter); 1427 test_iterators(reporter); 1428 test_bounds(reporter, SkClipStack::Element::kRect_Type); 1429 test_bounds(reporter, SkClipStack::Element::kRRect_Type); 1430 test_bounds(reporter, SkClipStack::Element::kPath_Type); 1431 test_isWideOpen(reporter); 1432 test_rect_merging(reporter); 1433 test_rect_replace(reporter); 1434 test_rect_inverse_fill(reporter); 1435 test_path_replace(reporter); 1436 test_quickContains(reporter); 1437 test_invfill_diff_bug(reporter); 1438 #if SK_SUPPORT_GPU 1439 test_reduced_clip_stack(reporter); 1440 test_reduced_clip_stack_genid(reporter); 1441 test_reduced_clip_stack_no_aa_crash(reporter); 1442 test_reduced_clip_stack_aa(reporter); 1443 test_tiny_query_bounds_assertion_bug(reporter); 1444 #endif 1445 } 1446 1447 ////////////////////////////////////////////////////////////////////////////// 1448 1449 #if SK_SUPPORT_GPU 1450 sk_sp<GrTextureProxy> GrClipStackClip::testingOnly_createClipMask(GrContext* context) const { 1451 const GrReducedClip reducedClip(*fStack, SkRect::MakeWH(512, 512), 0); 1452 return this->createSoftwareClipMask(context, reducedClip); 1453 } 1454 1455 // Verify that clip masks are freed up when the clip state that generated them goes away. 1456 DEF_GPUTEST_FOR_ALL_CONTEXTS(ClipMaskCache, reporter, ctxInfo) { 1457 // This test uses resource key tags which only function in debug builds. 1458 #ifdef SK_DEBUG 1459 GrContext* context = ctxInfo.grContext(); 1460 SkClipStack stack; 1461 1462 SkPath path; 1463 path.addCircle(10, 10, 8); 1464 path.addCircle(15, 15, 8); 1465 path.setFillType(SkPath::kEvenOdd_FillType); 1466 1467 static const char* kTag = GrClipStackClip::kMaskTestTag; 1468 GrResourceCache* cache = context->getResourceCache(); 1469 1470 static constexpr int kN = 5; 1471 1472 for (int i = 0; i < kN; ++i) { 1473 SkMatrix m; 1474 m.setTranslate(0.5, 0.5); 1475 stack.save(); 1476 stack.clipPath(path, m, SkClipOp::kIntersect, true); 1477 sk_sp<GrTextureProxy> mask = GrClipStackClip(&stack).testingOnly_createClipMask(context); 1478 mask->instantiate(context->resourceProvider()); 1479 GrTexture* tex = mask->priv().peekTexture(); 1480 REPORTER_ASSERT(reporter, 0 == strcmp(tex->getUniqueKey().tag(), kTag)); 1481 // Make sure mask isn't pinned in cache. 1482 mask.reset(nullptr); 1483 context->flush(); 1484 REPORTER_ASSERT(reporter, i + 1 == cache->countUniqueKeysWithTag(kTag)); 1485 } 1486 1487 for (int i = 0; i < kN; ++i) { 1488 stack.restore(); 1489 cache->purgeAsNeeded(); 1490 REPORTER_ASSERT(reporter, kN - (i + 1) == cache->countUniqueKeysWithTag(kTag)); 1491 } 1492 #endif 1493 } 1494 1495 #include "SkSurface.h" 1496 DEF_GPUTEST_FOR_ALL_CONTEXTS(canvas_private_clipRgn, reporter, ctxInfo) { 1497 GrContext* context = ctxInfo.grContext(); 1498 1499 const int w = 10; 1500 const int h = 10; 1501 SkImageInfo info = SkImageInfo::Make(w, h, kRGBA_8888_SkColorType, kPremul_SkAlphaType); 1502 sk_sp<SkSurface> surf = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info); 1503 SkCanvas* canvas = surf->getCanvas(); 1504 SkRegion rgn; 1505 1506 canvas->temporary_internal_getRgnClip(&rgn); 1507 REPORTER_ASSERT(reporter, rgn.isRect()); 1508 REPORTER_ASSERT(reporter, rgn.getBounds() == SkIRect::MakeWH(w, h)); 1509 1510 canvas->save(); 1511 canvas->clipRect(SkRect::MakeWH(5, 5), kDifference_SkClipOp); 1512 canvas->temporary_internal_getRgnClip(&rgn); 1513 REPORTER_ASSERT(reporter, rgn.isComplex()); 1514 REPORTER_ASSERT(reporter, rgn.getBounds() == SkIRect::MakeWH(w, h)); 1515 canvas->restore(); 1516 1517 canvas->save(); 1518 canvas->clipRRect(SkRRect::MakeOval(SkRect::MakeLTRB(3, 3, 7, 7))); 1519 canvas->temporary_internal_getRgnClip(&rgn); 1520 REPORTER_ASSERT(reporter, rgn.isComplex()); 1521 REPORTER_ASSERT(reporter, rgn.getBounds() == SkIRect::MakeLTRB(3, 3, 7, 7)); 1522 canvas->restore(); 1523 } 1524 #endif 1525