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