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 
test_assign_and_comparison(skiatest::Reporter * reporter)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 
assert_count(skiatest::Reporter * reporter,const SkClipStack & stack,int count)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)
test_iterators(skiatest::Reporter * reporter)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
test_bounds(skiatest::Reporter * reporter,SkClipStack::Element::DeviceSpaceType primType)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
test_isWideOpen(skiatest::Reporter * reporter)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 
count(const SkClipStack & stack)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 
test_rect_inverse_fill(skiatest::Reporter * reporter)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 
test_rect_replace(skiatest::Reporter * reporter)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.
test_path_replace(skiatest::Reporter * reporter)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.
test_rect_merging(skiatest::Reporter * reporter)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 
test_quickContains(skiatest::Reporter * reporter)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 
set_region_to_stack(const SkClipStack & stack,const SkIRect & bounds,SkRegion * region)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 
test_invfill_diff_bug(skiatest::Reporter * reporter)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}, &region);
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 
add_round_rect(const SkRect & rect,bool invert,SkClipOp op,SkClipStack * stack,bool doAA)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 
add_rect(const SkRect & rect,bool invert,SkClipOp op,SkClipStack * stack,bool doAA)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 
add_oval(const SkRect & rect,bool invert,SkClipOp op,SkClipStack * stack,bool doAA)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 
add_elem_to_stack(const SkClipStack::Element & element,SkClipStack * stack)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 
test_reduced_clip_stack(skiatest::Reporter * reporter)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, &region);
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 
test_reduced_clip_stack_genid(skiatest::Reporter * reporter)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 
test_reduced_clip_stack_no_aa_crash(skiatest::Reporter * reporter)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 
test_aa_query(skiatest::Reporter * reporter,const SkString & testName,const SkClipStack & stack,const SkMatrix & queryXform,const SkRect & preXformQuery,ClipMethod expectedMethod,int numExpectedElems=0)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 
test_reduced_clip_stack_aa(skiatest::Reporter * reporter)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 
test_tiny_query_bounds_assertion_bug(skiatest::Reporter * reporter)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 
DEF_TEST(ClipStack,reporter)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
testingOnly_createClipMask(GrContext * context) const1468 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.
DEF_GPUTEST_FOR_ALL_CONTEXTS(ClipMaskCache,reporter,ctxInfo)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"
DEF_GPUTEST_FOR_ALL_CONTEXTS(canvas_private_clipRgn,reporter,ctxInfo)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