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