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