ClipStackTest.cpp revision 910f694aefb0b671dd8522a9afe9b6be645701c1
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 "TestClassDef.h"
10#if SK_SUPPORT_GPU
11    #include "GrReducedClip.h"
12#endif
13#include "SkClipStack.h"
14#include "SkPath.h"
15#include "SkRandom.h"
16#include "SkRect.h"
17#include "SkRegion.h"
18
19static void test_assign_and_comparison(skiatest::Reporter* reporter) {
20    SkClipStack s;
21    bool doAA = false;
22
23    REPORTER_ASSERT(reporter, 0 == s.getSaveCount());
24
25    // Build up a clip stack with a path, an empty clip, and a rect.
26    s.save();
27    REPORTER_ASSERT(reporter, 1 == s.getSaveCount());
28
29    SkPath p;
30    p.moveTo(5, 6);
31    p.lineTo(7, 8);
32    p.lineTo(5, 9);
33    p.close();
34    s.clipDevPath(p, SkRegion::kIntersect_Op, doAA);
35
36    s.save();
37    REPORTER_ASSERT(reporter, 2 == s.getSaveCount());
38
39    SkRect r = SkRect::MakeLTRB(1, 2, 3, 4);
40    s.clipDevRect(r, SkRegion::kIntersect_Op, doAA);
41    r = SkRect::MakeLTRB(10, 11, 12, 13);
42    s.clipDevRect(r, SkRegion::kIntersect_Op, doAA);
43
44    s.save();
45    REPORTER_ASSERT(reporter, 3 == s.getSaveCount());
46
47    r = SkRect::MakeLTRB(14, 15, 16, 17);
48    s.clipDevRect(r, SkRegion::kUnion_Op, doAA);
49
50    // Test that assignment works.
51    SkClipStack copy = s;
52    REPORTER_ASSERT(reporter, s == copy);
53
54    // Test that different save levels triggers not equal.
55    s.restore();
56    REPORTER_ASSERT(reporter, 2 == s.getSaveCount());
57    REPORTER_ASSERT(reporter, s != copy);
58
59    // Test that an equal, but not copied version is equal.
60    s.save();
61    REPORTER_ASSERT(reporter, 3 == s.getSaveCount());
62
63    r = SkRect::MakeLTRB(14, 15, 16, 17);
64    s.clipDevRect(r, SkRegion::kUnion_Op, doAA);
65    REPORTER_ASSERT(reporter, s == copy);
66
67    // Test that a different op on one level triggers not equal.
68    s.restore();
69    REPORTER_ASSERT(reporter, 2 == s.getSaveCount());
70    s.save();
71    REPORTER_ASSERT(reporter, 3 == s.getSaveCount());
72
73    r = SkRect::MakeLTRB(14, 15, 16, 17);
74    s.clipDevRect(r, SkRegion::kIntersect_Op, doAA);
75    REPORTER_ASSERT(reporter, s != copy);
76
77    // Test that different state (clip type) triggers not equal.
78    // NO LONGER VALID: if a path contains only a rect, we turn
79    // it into a bare rect for performance reasons (working
80    // around Chromium/JavaScript bad pattern).
81/*
82    s.restore();
83    s.save();
84    SkPath rp;
85    rp.addRect(r);
86    s.clipDevPath(rp, SkRegion::kUnion_Op, doAA);
87    REPORTER_ASSERT(reporter, s != copy);
88*/
89
90    // Test that different rects triggers not equal.
91    s.restore();
92    REPORTER_ASSERT(reporter, 2 == s.getSaveCount());
93    s.save();
94    REPORTER_ASSERT(reporter, 3 == s.getSaveCount());
95
96    r = SkRect::MakeLTRB(24, 25, 26, 27);
97    s.clipDevRect(r, SkRegion::kUnion_Op, doAA);
98    REPORTER_ASSERT(reporter, s != copy);
99
100    // Sanity check
101    s.restore();
102    REPORTER_ASSERT(reporter, 2 == s.getSaveCount());
103
104    copy.restore();
105    REPORTER_ASSERT(reporter, 2 == copy.getSaveCount());
106    REPORTER_ASSERT(reporter, s == copy);
107    s.restore();
108    REPORTER_ASSERT(reporter, 1 == s.getSaveCount());
109    copy.restore();
110    REPORTER_ASSERT(reporter, 1 == copy.getSaveCount());
111    REPORTER_ASSERT(reporter, s == copy);
112
113    // Test that different paths triggers not equal.
114    s.restore();
115    REPORTER_ASSERT(reporter, 0 == s.getSaveCount());
116    s.save();
117    REPORTER_ASSERT(reporter, 1 == s.getSaveCount());
118
119    p.addRect(r);
120    s.clipDevPath(p, SkRegion::kIntersect_Op, doAA);
121    REPORTER_ASSERT(reporter, s != copy);
122}
123
124static void assert_count(skiatest::Reporter* reporter, const SkClipStack& stack,
125                         int count) {
126    SkClipStack::B2TIter iter(stack);
127    int counter = 0;
128    while (iter.next()) {
129        counter += 1;
130    }
131    REPORTER_ASSERT(reporter, count == counter);
132}
133
134// Exercise the SkClipStack's bottom to top and bidirectional iterators
135// (including the skipToTopmost functionality)
136static void test_iterators(skiatest::Reporter* reporter) {
137    SkClipStack stack;
138
139    static const SkRect gRects[] = {
140        { 0,   0,  40,  40 },
141        { 60,  0, 100,  40 },
142        { 0,  60,  40, 100 },
143        { 60, 60, 100, 100 }
144    };
145
146    for (size_t i = 0; i < SK_ARRAY_COUNT(gRects); i++) {
147        // the union op will prevent these from being fused together
148        stack.clipDevRect(gRects[i], SkRegion::kUnion_Op, false);
149    }
150
151    assert_count(reporter, stack, 4);
152
153    // bottom to top iteration
154    {
155        const SkClipStack::Element* element = NULL;
156
157        SkClipStack::B2TIter iter(stack);
158        int i;
159
160        for (i = 0, element = iter.next(); element; ++i, element = iter.next()) {
161            REPORTER_ASSERT(reporter, SkClipStack::Element::kRect_Type == element->getType());
162            REPORTER_ASSERT(reporter, element->getRect() == gRects[i]);
163        }
164
165        SkASSERT(i == 4);
166    }
167
168    // top to bottom iteration
169    {
170        const SkClipStack::Element* element = NULL;
171
172        SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
173        int i;
174
175        for (i = 3, element = iter.prev(); element; --i, element = iter.prev()) {
176            REPORTER_ASSERT(reporter, SkClipStack::Element::kRect_Type == element->getType());
177            REPORTER_ASSERT(reporter, element->getRect() == gRects[i]);
178        }
179
180        SkASSERT(i == -1);
181    }
182
183    // skipToTopmost
184    {
185        const SkClipStack::Element* element = NULL;
186
187        SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
188
189        element = iter.skipToTopmost(SkRegion::kUnion_Op);
190        REPORTER_ASSERT(reporter, SkClipStack::Element::kRect_Type == element->getType());
191        REPORTER_ASSERT(reporter, element->getRect() == gRects[3]);
192    }
193}
194
195// Exercise the SkClipStack's getConservativeBounds computation
196static void test_bounds(skiatest::Reporter* reporter, bool useRects) {
197
198    static const int gNumCases = 20;
199    static const SkRect gAnswerRectsBW[gNumCases] = {
200        // A op B
201        { 40, 40, 50, 50 },
202        { 10, 10, 50, 50 },
203        { 10, 10, 80, 80 },
204        { 10, 10, 80, 80 },
205        { 40, 40, 80, 80 },
206
207        // invA op B
208        { 40, 40, 80, 80 },
209        { 0, 0, 100, 100 },
210        { 0, 0, 100, 100 },
211        { 0, 0, 100, 100 },
212        { 40, 40, 50, 50 },
213
214        // A op invB
215        { 10, 10, 50, 50 },
216        { 40, 40, 50, 50 },
217        { 0, 0, 100, 100 },
218        { 0, 0, 100, 100 },
219        { 0, 0, 100, 100 },
220
221        // invA op invB
222        { 0, 0, 100, 100 },
223        { 40, 40, 80, 80 },
224        { 0, 0, 100, 100 },
225        { 10, 10, 80, 80 },
226        { 10, 10, 50, 50 },
227    };
228
229    static const SkRegion::Op gOps[] = {
230        SkRegion::kIntersect_Op,
231        SkRegion::kDifference_Op,
232        SkRegion::kUnion_Op,
233        SkRegion::kXOR_Op,
234        SkRegion::kReverseDifference_Op
235    };
236
237    SkRect rectA, rectB;
238
239    rectA.iset(10, 10, 50, 50);
240    rectB.iset(40, 40, 80, 80);
241
242    SkPath clipA, clipB;
243
244    clipA.addRoundRect(rectA, SkIntToScalar(5), SkIntToScalar(5));
245    clipB.addRoundRect(rectB, SkIntToScalar(5), SkIntToScalar(5));
246
247    SkClipStack stack;
248    SkRect devClipBound;
249    bool isIntersectionOfRects = false;
250
251    int testCase = 0;
252    int numBitTests = useRects ? 1 : 4;
253    for (int invBits = 0; invBits < numBitTests; ++invBits) {
254        for (size_t op = 0; op < SK_ARRAY_COUNT(gOps); ++op) {
255
256            stack.save();
257            bool doInvA = SkToBool(invBits & 1);
258            bool doInvB = SkToBool(invBits & 2);
259
260            clipA.setFillType(doInvA ? SkPath::kInverseEvenOdd_FillType :
261                                       SkPath::kEvenOdd_FillType);
262            clipB.setFillType(doInvB ? SkPath::kInverseEvenOdd_FillType :
263                                       SkPath::kEvenOdd_FillType);
264
265            if (useRects) {
266                stack.clipDevRect(rectA, SkRegion::kIntersect_Op, false);
267                stack.clipDevRect(rectB, gOps[op], false);
268            } else {
269                stack.clipDevPath(clipA, SkRegion::kIntersect_Op, false);
270                stack.clipDevPath(clipB, gOps[op], false);
271            }
272
273            REPORTER_ASSERT(reporter, !stack.isWideOpen());
274            REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID != stack.getTopmostGenID());
275
276            stack.getConservativeBounds(0, 0, 100, 100, &devClipBound,
277                                        &isIntersectionOfRects);
278
279            if (useRects) {
280                REPORTER_ASSERT(reporter, isIntersectionOfRects ==
281                        (gOps[op] == SkRegion::kIntersect_Op));
282            } else {
283                REPORTER_ASSERT(reporter, !isIntersectionOfRects);
284            }
285
286            SkASSERT(testCase < gNumCases);
287            REPORTER_ASSERT(reporter, devClipBound == gAnswerRectsBW[testCase]);
288            ++testCase;
289
290            stack.restore();
291        }
292    }
293}
294
295// Test out 'isWideOpen' entry point
296static void test_isWideOpen(skiatest::Reporter* reporter) {
297    {
298        // Empty stack is wide open. Wide open stack means that gen id is wide open.
299        SkClipStack stack;
300        REPORTER_ASSERT(reporter, stack.isWideOpen());
301        REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID());
302    }
303
304    SkRect rectA, rectB;
305
306    rectA.iset(10, 10, 40, 40);
307    rectB.iset(50, 50, 80, 80);
308
309    // Stack should initially be wide open
310    {
311        SkClipStack stack;
312
313        REPORTER_ASSERT(reporter, stack.isWideOpen());
314        REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID());
315    }
316
317    // Test out case where the user specifies a union that includes everything
318    {
319        SkClipStack stack;
320
321        SkPath clipA, clipB;
322
323        clipA.addRoundRect(rectA, SkIntToScalar(5), SkIntToScalar(5));
324        clipA.setFillType(SkPath::kInverseEvenOdd_FillType);
325
326        clipB.addRoundRect(rectB, SkIntToScalar(5), SkIntToScalar(5));
327        clipB.setFillType(SkPath::kInverseEvenOdd_FillType);
328
329        stack.clipDevPath(clipA, SkRegion::kReplace_Op, false);
330        stack.clipDevPath(clipB, SkRegion::kUnion_Op, false);
331
332        REPORTER_ASSERT(reporter, stack.isWideOpen());
333        REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID());
334    }
335
336    // Test out union w/ a wide open clip
337    {
338        SkClipStack stack;
339
340        stack.clipDevRect(rectA, SkRegion::kUnion_Op, false);
341
342        REPORTER_ASSERT(reporter, stack.isWideOpen());
343        REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID());
344    }
345
346    // Test out empty difference from a wide open clip
347    {
348        SkClipStack stack;
349
350        SkRect emptyRect;
351        emptyRect.setEmpty();
352
353        stack.clipDevRect(emptyRect, SkRegion::kDifference_Op, false);
354
355        REPORTER_ASSERT(reporter, stack.isWideOpen());
356        REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID());
357    }
358
359    // Test out return to wide open
360    {
361        SkClipStack stack;
362
363        stack.save();
364
365        stack.clipDevRect(rectA, SkRegion::kReplace_Op, false);
366
367        REPORTER_ASSERT(reporter, !stack.isWideOpen());
368        REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID != stack.getTopmostGenID());
369
370        stack.restore();
371
372        REPORTER_ASSERT(reporter, stack.isWideOpen());
373        REPORTER_ASSERT(reporter, SkClipStack::kWideOpenGenID == stack.getTopmostGenID());
374    }
375}
376
377static int count(const SkClipStack& stack) {
378
379    SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
380
381    const SkClipStack::Element* element = NULL;
382    int count = 0;
383
384    for (element = iter.prev(); element; element = iter.prev(), ++count) {
385        ;
386    }
387
388    return count;
389}
390
391static void test_rect_inverse_fill(skiatest::Reporter* reporter) {
392    // non-intersecting rectangles
393    SkRect rect  = SkRect::MakeLTRB(0, 0, 10, 10);
394
395    SkPath path;
396    path.addRect(rect);
397    path.toggleInverseFillType();
398    SkClipStack stack;
399    stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
400
401    SkRect bounds;
402    SkClipStack::BoundsType boundsType;
403    stack.getBounds(&bounds, &boundsType);
404    REPORTER_ASSERT(reporter, SkClipStack::kInsideOut_BoundsType == boundsType);
405    REPORTER_ASSERT(reporter, bounds == rect);
406}
407
408static void test_rect_replace(skiatest::Reporter* reporter) {
409    SkRect rect = SkRect::MakeWH(100, 100);
410    SkRect rect2 = SkRect::MakeXYWH(50, 50, 100, 100);
411
412    SkRect bound;
413    SkClipStack::BoundsType type;
414    bool isIntersectionOfRects;
415
416    // Adding a new rect with the replace operator should not increase
417    // the stack depth. BW replacing BW.
418    {
419        SkClipStack stack;
420        REPORTER_ASSERT(reporter, 0 == count(stack));
421        stack.clipDevRect(rect, SkRegion::kReplace_Op, false);
422        REPORTER_ASSERT(reporter, 1 == count(stack));
423        stack.clipDevRect(rect, SkRegion::kReplace_Op, false);
424        REPORTER_ASSERT(reporter, 1 == count(stack));
425    }
426
427    // Adding a new rect with the replace operator should not increase
428    // the stack depth. AA replacing AA.
429    {
430        SkClipStack stack;
431        REPORTER_ASSERT(reporter, 0 == count(stack));
432        stack.clipDevRect(rect, SkRegion::kReplace_Op, true);
433        REPORTER_ASSERT(reporter, 1 == count(stack));
434        stack.clipDevRect(rect, SkRegion::kReplace_Op, true);
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. BW replacing AA replacing BW.
440    {
441        SkClipStack stack;
442        REPORTER_ASSERT(reporter, 0 == count(stack));
443        stack.clipDevRect(rect, SkRegion::kReplace_Op, false);
444        REPORTER_ASSERT(reporter, 1 == count(stack));
445        stack.clipDevRect(rect, SkRegion::kReplace_Op, true);
446        REPORTER_ASSERT(reporter, 1 == count(stack));
447        stack.clipDevRect(rect, SkRegion::kReplace_Op, false);
448        REPORTER_ASSERT(reporter, 1 == count(stack));
449    }
450
451    // Make sure replace clip rects don't collapse too much.
452    {
453        SkClipStack stack;
454        stack.clipDevRect(rect, SkRegion::kReplace_Op, false);
455        stack.clipDevRect(rect2, SkRegion::kIntersect_Op, false);
456        REPORTER_ASSERT(reporter, 1 == count(stack));
457
458        stack.save();
459        stack.clipDevRect(rect, SkRegion::kReplace_Op, false);
460        REPORTER_ASSERT(reporter, 2 == count(stack));
461        stack.getBounds(&bound, &type, &isIntersectionOfRects);
462        REPORTER_ASSERT(reporter, bound == rect);
463        stack.restore();
464        REPORTER_ASSERT(reporter, 1 == count(stack));
465
466        stack.save();
467        stack.clipDevRect(rect, SkRegion::kReplace_Op, false);
468        stack.clipDevRect(rect, SkRegion::kReplace_Op, false);
469        REPORTER_ASSERT(reporter, 2 == count(stack));
470        stack.restore();
471        REPORTER_ASSERT(reporter, 1 == count(stack));
472
473        stack.save();
474        stack.clipDevRect(rect, SkRegion::kReplace_Op, false);
475        stack.clipDevRect(rect2, SkRegion::kIntersect_Op, false);
476        stack.clipDevRect(rect, SkRegion::kReplace_Op, false);
477        REPORTER_ASSERT(reporter, 2 == count(stack));
478        stack.restore();
479        REPORTER_ASSERT(reporter, 1 == count(stack));
480    }
481}
482
483// Simplified path-based version of test_rect_replace.
484static void test_path_replace(skiatest::Reporter* reporter) {
485    SkRect rect = SkRect::MakeWH(100, 100);
486    SkPath path;
487    path.addCircle(50, 50, 50);
488
489    // Replace operation doesn't grow the stack.
490    {
491        SkClipStack stack;
492        REPORTER_ASSERT(reporter, 0 == count(stack));
493        stack.clipDevPath(path, SkRegion::kReplace_Op, false);
494        REPORTER_ASSERT(reporter, 1 == count(stack));
495        stack.clipDevPath(path, SkRegion::kReplace_Op, false);
496        REPORTER_ASSERT(reporter, 1 == count(stack));
497    }
498
499    // Replacing rect with path.
500    {
501        SkClipStack stack;
502        stack.clipDevRect(rect, SkRegion::kReplace_Op, true);
503        REPORTER_ASSERT(reporter, 1 == count(stack));
504        stack.clipDevPath(path, SkRegion::kReplace_Op, true);
505        REPORTER_ASSERT(reporter, 1 == count(stack));
506    }
507}
508
509// Test out SkClipStack's merging of rect clips. In particular exercise
510// merging of aa vs. bw rects.
511static void test_rect_merging(skiatest::Reporter* reporter) {
512
513    SkRect overlapLeft  = SkRect::MakeLTRB(10, 10, 50, 50);
514    SkRect overlapRight = SkRect::MakeLTRB(40, 40, 80, 80);
515
516    SkRect nestedParent = SkRect::MakeLTRB(10, 10, 90, 90);
517    SkRect nestedChild  = SkRect::MakeLTRB(40, 40, 60, 60);
518
519    SkRect bound;
520    SkClipStack::BoundsType type;
521    bool isIntersectionOfRects;
522
523    // all bw overlapping - should merge
524    {
525        SkClipStack stack;
526
527        stack.clipDevRect(overlapLeft, SkRegion::kReplace_Op, false);
528
529        stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, false);
530
531        REPORTER_ASSERT(reporter, 1 == count(stack));
532
533        stack.getBounds(&bound, &type, &isIntersectionOfRects);
534
535        REPORTER_ASSERT(reporter, isIntersectionOfRects);
536    }
537
538    // all aa overlapping - should merge
539    {
540        SkClipStack stack;
541
542        stack.clipDevRect(overlapLeft, SkRegion::kReplace_Op, true);
543
544        stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, true);
545
546        REPORTER_ASSERT(reporter, 1 == count(stack));
547
548        stack.getBounds(&bound, &type, &isIntersectionOfRects);
549
550        REPORTER_ASSERT(reporter, isIntersectionOfRects);
551    }
552
553    // mixed overlapping - should _not_ merge
554    {
555        SkClipStack stack;
556
557        stack.clipDevRect(overlapLeft, SkRegion::kReplace_Op, true);
558
559        stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, false);
560
561        REPORTER_ASSERT(reporter, 2 == count(stack));
562
563        stack.getBounds(&bound, &type, &isIntersectionOfRects);
564
565        REPORTER_ASSERT(reporter, !isIntersectionOfRects);
566    }
567
568    // mixed nested (bw inside aa) - should merge
569    {
570        SkClipStack stack;
571
572        stack.clipDevRect(nestedParent, SkRegion::kReplace_Op, true);
573
574        stack.clipDevRect(nestedChild, SkRegion::kIntersect_Op, false);
575
576        REPORTER_ASSERT(reporter, 1 == count(stack));
577
578        stack.getBounds(&bound, &type, &isIntersectionOfRects);
579
580        REPORTER_ASSERT(reporter, isIntersectionOfRects);
581    }
582
583    // mixed nested (aa inside bw) - should merge
584    {
585        SkClipStack stack;
586
587        stack.clipDevRect(nestedParent, SkRegion::kReplace_Op, false);
588
589        stack.clipDevRect(nestedChild, SkRegion::kIntersect_Op, true);
590
591        REPORTER_ASSERT(reporter, 1 == count(stack));
592
593        stack.getBounds(&bound, &type, &isIntersectionOfRects);
594
595        REPORTER_ASSERT(reporter, isIntersectionOfRects);
596    }
597
598    // reverse nested (aa inside bw) - should _not_ merge
599    {
600        SkClipStack stack;
601
602        stack.clipDevRect(nestedChild, SkRegion::kReplace_Op, false);
603
604        stack.clipDevRect(nestedParent, SkRegion::kIntersect_Op, true);
605
606        REPORTER_ASSERT(reporter, 2 == count(stack));
607
608        stack.getBounds(&bound, &type, &isIntersectionOfRects);
609
610        REPORTER_ASSERT(reporter, !isIntersectionOfRects);
611    }
612}
613
614static void test_quickContains(skiatest::Reporter* reporter) {
615    SkRect testRect = SkRect::MakeLTRB(10, 10, 40, 40);
616    SkRect insideRect = SkRect::MakeLTRB(20, 20, 30, 30);
617    SkRect intersectingRect = SkRect::MakeLTRB(25, 25, 50, 50);
618    SkRect outsideRect = SkRect::MakeLTRB(0, 0, 50, 50);
619    SkRect nonIntersectingRect = SkRect::MakeLTRB(100, 100, 110, 110);
620
621    SkPath insideCircle;
622    insideCircle.addCircle(25, 25, 5);
623    SkPath intersectingCircle;
624    intersectingCircle.addCircle(25, 40, 10);
625    SkPath outsideCircle;
626    outsideCircle.addCircle(25, 25, 50);
627    SkPath nonIntersectingCircle;
628    nonIntersectingCircle.addCircle(100, 100, 5);
629
630    {
631        SkClipStack stack;
632        stack.clipDevRect(outsideRect, SkRegion::kDifference_Op, false);
633        // return false because quickContains currently does not care for kDifference_Op
634        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
635    }
636
637    // Replace Op tests
638    {
639        SkClipStack stack;
640        stack.clipDevRect(outsideRect, SkRegion::kReplace_Op, false);
641        REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
642    }
643
644    {
645        SkClipStack stack;
646        stack.clipDevRect(insideRect, SkRegion::kIntersect_Op, false);
647        stack.save(); // To prevent in-place substitution by replace OP
648        stack.clipDevRect(outsideRect, SkRegion::kReplace_Op, false);
649        REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
650        stack.restore();
651    }
652
653    {
654        SkClipStack stack;
655        stack.clipDevRect(outsideRect, SkRegion::kIntersect_Op, false);
656        stack.save(); // To prevent in-place substitution by replace OP
657        stack.clipDevRect(insideRect, SkRegion::kReplace_Op, false);
658        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
659        stack.restore();
660    }
661
662    // Verify proper traversal of multi-element clip
663    {
664        SkClipStack stack;
665        stack.clipDevRect(insideRect, SkRegion::kIntersect_Op, false);
666        // Use a path for second clip to prevent in-place intersection
667        stack.clipDevPath(outsideCircle, SkRegion::kIntersect_Op, false);
668        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
669    }
670
671    // Intersect Op tests with rectangles
672    {
673        SkClipStack stack;
674        stack.clipDevRect(outsideRect, SkRegion::kIntersect_Op, false);
675        REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
676    }
677
678    {
679        SkClipStack stack;
680        stack.clipDevRect(insideRect, SkRegion::kIntersect_Op, false);
681        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
682    }
683
684    {
685        SkClipStack stack;
686        stack.clipDevRect(intersectingRect, SkRegion::kIntersect_Op, false);
687        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
688    }
689
690    {
691        SkClipStack stack;
692        stack.clipDevRect(nonIntersectingRect, SkRegion::kIntersect_Op, false);
693        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
694    }
695
696    // Intersect Op tests with circle paths
697    {
698        SkClipStack stack;
699        stack.clipDevPath(outsideCircle, SkRegion::kIntersect_Op, false);
700        REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
701    }
702
703    {
704        SkClipStack stack;
705        stack.clipDevPath(insideCircle, SkRegion::kIntersect_Op, false);
706        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
707    }
708
709    {
710        SkClipStack stack;
711        stack.clipDevPath(intersectingCircle, SkRegion::kIntersect_Op, false);
712        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
713    }
714
715    {
716        SkClipStack stack;
717        stack.clipDevPath(nonIntersectingCircle, SkRegion::kIntersect_Op, false);
718        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
719    }
720
721    // Intersect Op tests with inverse filled rectangles
722    {
723        SkClipStack stack;
724        SkPath path;
725        path.addRect(outsideRect);
726        path.toggleInverseFillType();
727        stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
728        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
729    }
730
731    {
732        SkClipStack stack;
733        SkPath path;
734        path.addRect(insideRect);
735        path.toggleInverseFillType();
736        stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
737        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
738    }
739
740    {
741        SkClipStack stack;
742        SkPath path;
743        path.addRect(intersectingRect);
744        path.toggleInverseFillType();
745        stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
746        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
747    }
748
749    {
750        SkClipStack stack;
751        SkPath path;
752        path.addRect(nonIntersectingRect);
753        path.toggleInverseFillType();
754        stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
755        REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
756    }
757
758    // Intersect Op tests with inverse filled circles
759    {
760        SkClipStack stack;
761        SkPath path = outsideCircle;
762        path.toggleInverseFillType();
763        stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
764        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
765    }
766
767    {
768        SkClipStack stack;
769        SkPath path = insideCircle;
770        path.toggleInverseFillType();
771        stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
772        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
773    }
774
775    {
776        SkClipStack stack;
777        SkPath path = intersectingCircle;
778        path.toggleInverseFillType();
779        stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
780        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
781    }
782
783    {
784        SkClipStack stack;
785        SkPath path = nonIntersectingCircle;
786        path.toggleInverseFillType();
787        stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
788        REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
789    }
790}
791
792///////////////////////////////////////////////////////////////////////////////////////////////////
793
794#if SK_SUPPORT_GPU
795// Functions that add a shape to the clip stack. The shape is computed from a rectangle.
796// AA is always disabled since the clip stack reducer can cause changes in aa rasterization of the
797// stack. A fractional edge repeated in different elements may be rasterized fewer times using the
798// reduced stack.
799typedef void (*AddElementFunc) (const SkRect& rect,
800                                bool invert,
801                                SkRegion::Op op,
802                                SkClipStack* stack);
803
804static void add_round_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) {
805    SkPath path;
806    SkScalar rx = rect.width() / 10;
807    SkScalar ry = rect.height() / 20;
808    path.addRoundRect(rect, rx, ry);
809    if (invert) {
810        path.setFillType(SkPath::kInverseWinding_FillType);
811    }
812    stack->clipDevPath(path, op, false);
813};
814
815static void add_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) {
816    if (invert) {
817        SkPath path;
818        path.addRect(rect);
819        path.setFillType(SkPath::kInverseWinding_FillType);
820        stack->clipDevPath(path, op, false);
821    } else {
822        stack->clipDevRect(rect, op, false);
823    }
824};
825
826static void add_oval(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) {
827    SkPath path;
828    path.addOval(rect);
829    if (invert) {
830        path.setFillType(SkPath::kInverseWinding_FillType);
831    }
832    stack->clipDevPath(path, op, false);
833};
834
835static void add_elem_to_stack(const SkClipStack::Element& element, SkClipStack* stack) {
836    switch (element.getType()) {
837        case SkClipStack::Element::kRect_Type:
838            stack->clipDevRect(element.getRect(), element.getOp(), element.isAA());
839            break;
840        case SkClipStack::Element::kPath_Type:
841            stack->clipDevPath(element.getPath(), element.getOp(), element.isAA());
842            break;
843        case SkClipStack::Element::kEmpty_Type:
844            SkDEBUGFAIL("Why did the reducer produce an explicit empty.");
845            stack->clipEmpty();
846            break;
847    }
848}
849
850static void add_elem_to_region(const SkClipStack::Element& element,
851                               const SkIRect& bounds,
852                               SkRegion* region) {
853    SkRegion elemRegion;
854    SkRegion boundsRgn(bounds);
855
856    switch (element.getType()) {
857        case SkClipStack::Element::kRect_Type: {
858            SkPath path;
859            path.addRect(element.getRect());
860            elemRegion.setPath(path, boundsRgn);
861            break;
862        }
863        case SkClipStack::Element::kPath_Type:
864            elemRegion.setPath(element.getPath(), boundsRgn);
865            break;
866        case SkClipStack::Element::kEmpty_Type:
867            //
868            region->setEmpty();
869            return;
870    }
871    region->op(elemRegion, element.getOp());
872}
873
874static void test_reduced_clip_stack(skiatest::Reporter* reporter) {
875    // We construct random clip stacks, reduce them, and then rasterize both versions to verify that
876    // they are equal.
877
878    // All the clip elements will be contained within these bounds.
879    static const SkRect kBounds = SkRect::MakeWH(100, 100);
880
881    enum {
882        kNumTests = 200,
883        kMinElemsPerTest = 1,
884        kMaxElemsPerTest = 50,
885    };
886
887    // min/max size of a clip element as a fraction of kBounds.
888    static const SkScalar kMinElemSizeFrac = SK_Scalar1 / 5;
889    static const SkScalar kMaxElemSizeFrac = SK_Scalar1;
890
891    static const SkRegion::Op kOps[] = {
892        SkRegion::kDifference_Op,
893        SkRegion::kIntersect_Op,
894        SkRegion::kUnion_Op,
895        SkRegion::kXOR_Op,
896        SkRegion::kReverseDifference_Op,
897        SkRegion::kReplace_Op,
898    };
899
900    // Replace operations short-circuit the optimizer. We want to make sure that we test this code
901    // path a little bit but we don't want it to prevent us from testing many longer traversals in
902    // the optimizer.
903    static const int kReplaceDiv = 4 * kMaxElemsPerTest;
904
905    // We want to test inverse fills. However, they are quite rare in practice so don't over do it.
906    static const SkScalar kFractionInverted = SK_Scalar1 / kMaxElemsPerTest;
907
908    static const AddElementFunc kElementFuncs[] = {
909        add_rect,
910        add_round_rect,
911        add_oval,
912    };
913
914    SkRandom r;
915
916    for (int i = 0; i < kNumTests; ++i) {
917        // Randomly generate a clip stack.
918        SkClipStack stack;
919        int numElems = r.nextRangeU(kMinElemsPerTest, kMaxElemsPerTest);
920        for (int e = 0; e < numElems; ++e) {
921            SkRegion::Op op = kOps[r.nextULessThan(SK_ARRAY_COUNT(kOps))];
922            if (op == SkRegion::kReplace_Op) {
923                if (r.nextU() % kReplaceDiv) {
924                    --e;
925                    continue;
926                }
927            }
928
929            // saves can change the clip stack behavior when an element is added.
930            bool doSave = r.nextBool();
931
932            SkSize size = SkSize::Make(
933                SkScalarFloorToScalar(SkScalarMul(kBounds.width(), r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac))),
934                SkScalarFloorToScalar(SkScalarMul(kBounds.height(), r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac))));
935
936            SkPoint xy = {SkScalarFloorToScalar(r.nextRangeScalar(kBounds.fLeft, kBounds.fRight - size.fWidth)),
937                          SkScalarFloorToScalar(r.nextRangeScalar(kBounds.fTop, kBounds.fBottom - size.fHeight))};
938
939            SkRect rect = SkRect::MakeXYWH(xy.fX, xy.fY, size.fWidth, size.fHeight);
940
941            bool invert = r.nextBiasedBool(kFractionInverted);
942            kElementFuncs[r.nextULessThan(SK_ARRAY_COUNT(kElementFuncs))](rect, invert, op, &stack);
943            if (doSave) {
944                stack.save();
945            }
946        }
947
948        SkRect inflatedBounds = kBounds;
949        inflatedBounds.outset(kBounds.width() / 2, kBounds.height() / 2);
950        SkIRect inflatedIBounds;
951        inflatedBounds.roundOut(&inflatedIBounds);
952
953        typedef GrReducedClip::ElementList ElementList;
954        // Get the reduced version of the stack.
955        ElementList reducedClips;
956        int32_t reducedGenID;
957        GrReducedClip::InitialState initial;
958        SkIRect tBounds(inflatedIBounds);
959        SkIRect* tightBounds = r.nextBool() ? &tBounds : NULL;
960        GrReducedClip::ReduceClipStack(stack,
961                                       inflatedIBounds,
962                                       &reducedClips,
963                                       &reducedGenID,
964                                       &initial,
965                                       tightBounds);
966
967        REPORTER_ASSERT(reporter, SkClipStack::kInvalidGenID != reducedGenID);
968
969        // Build a new clip stack based on the reduced clip elements
970        SkClipStack reducedStack;
971        if (GrReducedClip::kAllOut_InitialState == initial) {
972            // whether the result is bounded or not, the whole plane should start outside the clip.
973            reducedStack.clipEmpty();
974        }
975        for (ElementList::Iter iter = reducedClips.headIter(); NULL != iter.get(); iter.next()) {
976            add_elem_to_stack(*iter.get(), &reducedStack);
977        }
978
979        // GrReducedClipStack assumes that the final result is clipped to the returned bounds
980        if (NULL != tightBounds) {
981            reducedStack.clipDevRect(*tightBounds, SkRegion::kIntersect_Op);
982        }
983
984        // convert both the original stack and reduced stack to SkRegions and see if they're equal
985        SkRegion region;
986        SkRegion reducedRegion;
987
988        region.setRect(inflatedIBounds);
989        const SkClipStack::Element* element;
990        SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
991        while ((element = iter.next())) {
992            add_elem_to_region(*element, inflatedIBounds, &region);
993        }
994
995        reducedRegion.setRect(inflatedIBounds);
996        iter.reset(reducedStack, SkClipStack::Iter::kBottom_IterStart);
997        while ((element = iter.next())) {
998            add_elem_to_region(*element, inflatedIBounds, &reducedRegion);
999        }
1000
1001        REPORTER_ASSERT(reporter, region == reducedRegion);
1002    }
1003}
1004
1005#if defined(WIN32)
1006    #define SUPPRESS_VISIBILITY_WARNING
1007#else
1008    #define SUPPRESS_VISIBILITY_WARNING __attribute__((visibility("hidden")))
1009#endif
1010
1011static void test_reduced_clip_stack_genid(skiatest::Reporter* reporter) {
1012    {
1013        SkClipStack stack;
1014        stack.clipDevRect(SkRect::MakeXYWH(0, 0, 100, 100), SkRegion::kReplace_Op, true);
1015        stack.clipDevRect(SkRect::MakeXYWH(0, 0, SkScalar(50.3), SkScalar(50.3)), SkRegion::kReplace_Op, true);
1016        SkIRect inflatedIBounds = SkIRect::MakeXYWH(0, 0, 100, 100);
1017
1018        GrReducedClip::ElementList reducedClips;
1019        int32_t reducedGenID;
1020        GrReducedClip::InitialState initial;
1021        SkIRect tightBounds;
1022
1023        GrReducedClip::ReduceClipStack(stack,
1024                                       inflatedIBounds,
1025                                       &reducedClips,
1026                                       &reducedGenID,
1027                                       &initial,
1028                                       &tightBounds);
1029
1030        REPORTER_ASSERT(reporter, reducedClips.count() == 1);
1031        // Clips will be cached based on the generation id. Make sure the gen id is valid.
1032        REPORTER_ASSERT(reporter, SkClipStack::kInvalidGenID != reducedGenID);
1033    }
1034    {
1035        SkClipStack stack;
1036
1037        // Create a clip with following 25.3, 25.3 boxes which are 25 apart:
1038        //  A  B
1039        //  C  D
1040
1041        stack.clipDevRect(SkRect::MakeXYWH(0, 0, SkScalar(25.3), SkScalar(25.3)), SkRegion::kReplace_Op, true);
1042        int32_t genIDA = stack.getTopmostGenID();
1043        stack.clipDevRect(SkRect::MakeXYWH(50, 0, SkScalar(25.3), SkScalar(25.3)), SkRegion::kUnion_Op, true);
1044        int32_t genIDB = stack.getTopmostGenID();
1045        stack.clipDevRect(SkRect::MakeXYWH(0, 50, SkScalar(25.3), SkScalar(25.3)), SkRegion::kUnion_Op, true);
1046        int32_t genIDC = stack.getTopmostGenID();
1047        stack.clipDevRect(SkRect::MakeXYWH(50, 50, SkScalar(25.3), SkScalar(25.3)), SkRegion::kUnion_Op, true);
1048        int32_t genIDD = stack.getTopmostGenID();
1049
1050
1051#define XYWH SkIRect::MakeXYWH
1052
1053        SkIRect unused;
1054        unused.setEmpty();
1055        SkIRect stackBounds = XYWH(0, 0, 76, 76);
1056
1057        // The base test is to test each rect in two ways:
1058        // 1) The box dimensions. (Should reduce to "all in", no elements).
1059        // 2) A bit over the box dimensions.
1060        // In the case 2, test that the generation id is what is expected.
1061        // The rects are of fractional size so that case 2 never gets optimized to an empty element
1062        // list.
1063
1064        // Not passing in tighter bounds is tested for consistency.
1065        static const struct SUPPRESS_VISIBILITY_WARNING {
1066            SkIRect testBounds;
1067            int reducedClipCount;
1068            int32_t reducedGenID;
1069            GrReducedClip::InitialState initialState;
1070            SkIRect tighterBounds; // If this is empty, the query will not pass tighter bounds
1071            // parameter.
1072        } testCases[] = {
1073            // Rect A.
1074            { XYWH(0, 0, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, XYWH(0, 0, 25, 25) },
1075            { XYWH(0, 0, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, unused },
1076            { XYWH(0, 0, 27, 27), 1, genIDA, GrReducedClip::kAllOut_InitialState, XYWH(0, 0, 27, 27)},
1077            { XYWH(0, 0, 27, 27), 1, genIDA, GrReducedClip::kAllOut_InitialState, unused },
1078
1079            // Rect B.
1080            { XYWH(50, 0, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, XYWH(50, 0, 25, 25) },
1081            { XYWH(50, 0, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, unused },
1082            { XYWH(50, 0, 27, 27), 1, genIDB, GrReducedClip::kAllOut_InitialState, XYWH(50, 0, 26, 27) },
1083            { XYWH(50, 0, 27, 27), 1, genIDB, GrReducedClip::kAllOut_InitialState, unused },
1084
1085            // Rect C.
1086            { XYWH(0, 50, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, XYWH(0, 50, 25, 25) },
1087            { XYWH(0, 50, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, unused },
1088            { XYWH(0, 50, 27, 27), 1, genIDC, GrReducedClip::kAllOut_InitialState, XYWH(0, 50, 27, 26) },
1089            { XYWH(0, 50, 27, 27), 1, genIDC, GrReducedClip::kAllOut_InitialState, unused },
1090
1091            // Rect D.
1092            { XYWH(50, 50, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, unused },
1093            { XYWH(50, 50, 25, 25), 0, SkClipStack::kWideOpenGenID, GrReducedClip::kAllIn_InitialState, XYWH(50, 50, 25, 25)},
1094            { XYWH(50, 50, 27, 27), 1, genIDD, GrReducedClip::kAllOut_InitialState, unused },
1095            { XYWH(50, 50, 27, 27), 1, genIDD, GrReducedClip::kAllOut_InitialState,  XYWH(50, 50, 26, 26)},
1096
1097            // Other tests:
1098            { XYWH(0, 0, 100, 100), 4, genIDD, GrReducedClip::kAllOut_InitialState, unused },
1099            { XYWH(0, 0, 100, 100), 4, genIDD, GrReducedClip::kAllOut_InitialState, stackBounds },
1100
1101            // Rect in the middle, touches none.
1102            { XYWH(26, 26, 24, 24), 0, SkClipStack::kEmptyGenID, GrReducedClip::kAllOut_InitialState, unused },
1103            { XYWH(26, 26, 24, 24), 0, SkClipStack::kEmptyGenID, GrReducedClip::kAllOut_InitialState, XYWH(26, 26, 24, 24) },
1104
1105            // Rect in the middle, touches all the rects. GenID is the last rect.
1106            { XYWH(24, 24, 27, 27), 4, genIDD, GrReducedClip::kAllOut_InitialState, unused },
1107            { XYWH(24, 24, 27, 27), 4, genIDD, GrReducedClip::kAllOut_InitialState, XYWH(24, 24, 27, 27) },
1108        };
1109
1110#undef XYWH
1111
1112        for (size_t i = 0; i < SK_ARRAY_COUNT(testCases); ++i) {
1113            GrReducedClip::ElementList reducedClips;
1114            int32_t reducedGenID;
1115            GrReducedClip::InitialState initial;
1116            SkIRect tightBounds;
1117
1118            GrReducedClip::ReduceClipStack(stack,
1119                                           testCases[i].testBounds,
1120                                           &reducedClips,
1121                                           &reducedGenID,
1122                                           &initial,
1123                                           testCases[i].tighterBounds.isEmpty() ? NULL : &tightBounds);
1124
1125            REPORTER_ASSERT(reporter, reducedClips.count() == testCases[i].reducedClipCount);
1126            SkASSERT(reducedClips.count() == testCases[i].reducedClipCount);
1127            REPORTER_ASSERT(reporter, reducedGenID == testCases[i].reducedGenID);
1128            SkASSERT(reducedGenID == testCases[i].reducedGenID);
1129            REPORTER_ASSERT(reporter, initial == testCases[i].initialState);
1130            SkASSERT(initial == testCases[i].initialState);
1131            if (!testCases[i].tighterBounds.isEmpty()) {
1132                REPORTER_ASSERT(reporter, tightBounds == testCases[i].tighterBounds);
1133                SkASSERT(tightBounds == testCases[i].tighterBounds);
1134            }
1135        }
1136    }
1137}
1138
1139static void test_reduced_clip_stack_no_aa_crash(skiatest::Reporter* reporter) {
1140    SkClipStack stack;
1141    stack.clipDevRect(SkIRect::MakeXYWH(0, 0, 100, 100), SkRegion::kReplace_Op);
1142    stack.clipDevRect(SkIRect::MakeXYWH(0, 0, 50, 50), SkRegion::kReplace_Op);
1143    SkIRect inflatedIBounds = SkIRect::MakeXYWH(0, 0, 100, 100);
1144
1145    GrReducedClip::ElementList reducedClips;
1146    int32_t reducedGenID;
1147    GrReducedClip::InitialState initial;
1148    SkIRect tightBounds;
1149
1150    // At the time, this would crash.
1151    GrReducedClip::ReduceClipStack(stack,
1152                                   inflatedIBounds,
1153                                   &reducedClips,
1154                                   &reducedGenID,
1155                                   &initial,
1156                                   &tightBounds);
1157
1158    REPORTER_ASSERT(reporter, 0 == reducedClips.count());
1159}
1160
1161#endif
1162
1163DEF_TEST(ClipStack, reporter) {
1164    SkClipStack stack;
1165
1166    REPORTER_ASSERT(reporter, 0 == stack.getSaveCount());
1167    assert_count(reporter, stack, 0);
1168
1169    static const SkIRect gRects[] = {
1170        { 0, 0, 100, 100 },
1171        { 25, 25, 125, 125 },
1172        { 0, 0, 1000, 1000 },
1173        { 0, 0, 75, 75 }
1174    };
1175    for (size_t i = 0; i < SK_ARRAY_COUNT(gRects); i++) {
1176        stack.clipDevRect(gRects[i], SkRegion::kIntersect_Op);
1177    }
1178
1179    // all of the above rects should have been intersected, leaving only 1 rect
1180    SkClipStack::B2TIter iter(stack);
1181    const SkClipStack::Element* element = iter.next();
1182    SkRect answer;
1183    answer.iset(25, 25, 75, 75);
1184
1185    REPORTER_ASSERT(reporter, NULL != element);
1186    REPORTER_ASSERT(reporter, SkClipStack::Element::kRect_Type == element->getType());
1187    REPORTER_ASSERT(reporter, SkRegion::kIntersect_Op == element->getOp());
1188    REPORTER_ASSERT(reporter, element->getRect() == answer);
1189    // now check that we only had one in our iterator
1190    REPORTER_ASSERT(reporter, !iter.next());
1191
1192    stack.reset();
1193    REPORTER_ASSERT(reporter, 0 == stack.getSaveCount());
1194    assert_count(reporter, stack, 0);
1195
1196    test_assign_and_comparison(reporter);
1197    test_iterators(reporter);
1198    test_bounds(reporter, true);        // once with rects
1199    test_bounds(reporter, false);       // once with paths
1200    test_isWideOpen(reporter);
1201    test_rect_merging(reporter);
1202    test_rect_replace(reporter);
1203    test_rect_inverse_fill(reporter);
1204    test_path_replace(reporter);
1205    test_quickContains(reporter);
1206#if SK_SUPPORT_GPU
1207    test_reduced_clip_stack(reporter);
1208    test_reduced_clip_stack_genid(reporter);
1209    test_reduced_clip_stack_no_aa_crash(reporter);
1210#endif
1211}
1212