ClipStackTest.cpp revision 170bd792e17469769d145b7dc15dea6cd01b7966
1
2/*
3 * Copyright 2011 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
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
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
275            stack.getConservativeBounds(0, 0, 100, 100, &devClipBound,
276                                        &isIntersectionOfRects);
277
278            if (useRects) {
279                REPORTER_ASSERT(reporter, isIntersectionOfRects ==
280                        (gOps[op] == SkRegion::kIntersect_Op));
281            } else {
282                REPORTER_ASSERT(reporter, !isIntersectionOfRects);
283            }
284
285            SkASSERT(testCase < gNumCases);
286            REPORTER_ASSERT(reporter, devClipBound == gAnswerRectsBW[testCase]);
287            ++testCase;
288
289            stack.restore();
290        }
291    }
292}
293
294// Test out 'isWideOpen' entry point
295static void test_isWideOpen(skiatest::Reporter* reporter) {
296
297    SkRect rectA, rectB;
298
299    rectA.iset(10, 10, 40, 40);
300    rectB.iset(50, 50, 80, 80);
301
302    // Stack should initially be wide open
303    {
304        SkClipStack stack;
305
306        REPORTER_ASSERT(reporter, stack.isWideOpen());
307    }
308
309    // Test out case where the user specifies a union that includes everything
310    {
311        SkClipStack stack;
312
313        SkPath clipA, clipB;
314
315        clipA.addRoundRect(rectA, SkIntToScalar(5), SkIntToScalar(5));
316        clipA.setFillType(SkPath::kInverseEvenOdd_FillType);
317
318        clipB.addRoundRect(rectB, SkIntToScalar(5), SkIntToScalar(5));
319        clipB.setFillType(SkPath::kInverseEvenOdd_FillType);
320
321        stack.clipDevPath(clipA, SkRegion::kReplace_Op, false);
322        stack.clipDevPath(clipB, SkRegion::kUnion_Op, false);
323
324        REPORTER_ASSERT(reporter, stack.isWideOpen());
325    }
326
327    // Test out union w/ a wide open clip
328    {
329        SkClipStack stack;
330
331        stack.clipDevRect(rectA, SkRegion::kUnion_Op, false);
332
333        REPORTER_ASSERT(reporter, stack.isWideOpen());
334    }
335
336    // Test out empty difference from a wide open clip
337    {
338        SkClipStack stack;
339
340        SkRect emptyRect;
341        emptyRect.setEmpty();
342
343        stack.clipDevRect(emptyRect, SkRegion::kDifference_Op, false);
344
345        REPORTER_ASSERT(reporter, stack.isWideOpen());
346    }
347
348    // Test out return to wide open
349    {
350        SkClipStack stack;
351
352        stack.save();
353
354        stack.clipDevRect(rectA, SkRegion::kReplace_Op, false);
355
356        REPORTER_ASSERT(reporter, !stack.isWideOpen());
357
358        stack.restore();
359
360        REPORTER_ASSERT(reporter, stack.isWideOpen());
361    }
362}
363
364static int count(const SkClipStack& stack) {
365
366    SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
367
368    const SkClipStack::Element* element = NULL;
369    int count = 0;
370
371    for (element = iter.prev(); element; element = iter.prev(), ++count) {
372        ;
373    }
374
375    return count;
376}
377
378// Test out SkClipStack's merging of rect clips. In particular exercise
379// merging of aa vs. bw rects.
380static void test_rect_merging(skiatest::Reporter* reporter) {
381
382    SkRect overlapLeft  = SkRect::MakeLTRB(10, 10, 50, 50);
383    SkRect overlapRight = SkRect::MakeLTRB(40, 40, 80, 80);
384
385    SkRect nestedParent = SkRect::MakeLTRB(10, 10, 90, 90);
386    SkRect nestedChild  = SkRect::MakeLTRB(40, 40, 60, 60);
387
388    SkRect bound;
389    SkClipStack::BoundsType type;
390    bool isIntersectionOfRects;
391
392    // all bw overlapping - should merge
393    {
394        SkClipStack stack;
395
396        stack.clipDevRect(overlapLeft, SkRegion::kReplace_Op, false);
397
398        stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, false);
399
400        REPORTER_ASSERT(reporter, 1 == count(stack));
401
402        stack.getBounds(&bound, &type, &isIntersectionOfRects);
403
404        REPORTER_ASSERT(reporter, isIntersectionOfRects);
405    }
406
407    // all aa overlapping - should merge
408    {
409        SkClipStack stack;
410
411        stack.clipDevRect(overlapLeft, SkRegion::kReplace_Op, true);
412
413        stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, true);
414
415        REPORTER_ASSERT(reporter, 1 == count(stack));
416
417        stack.getBounds(&bound, &type, &isIntersectionOfRects);
418
419        REPORTER_ASSERT(reporter, isIntersectionOfRects);
420    }
421
422    // mixed overlapping - should _not_ merge
423    {
424        SkClipStack stack;
425
426        stack.clipDevRect(overlapLeft, SkRegion::kReplace_Op, true);
427
428        stack.clipDevRect(overlapRight, SkRegion::kIntersect_Op, false);
429
430        REPORTER_ASSERT(reporter, 2 == count(stack));
431
432        stack.getBounds(&bound, &type, &isIntersectionOfRects);
433
434        REPORTER_ASSERT(reporter, !isIntersectionOfRects);
435    }
436
437    // mixed nested (bw inside aa) - should merge
438    {
439        SkClipStack stack;
440
441        stack.clipDevRect(nestedParent, SkRegion::kReplace_Op, true);
442
443        stack.clipDevRect(nestedChild, SkRegion::kIntersect_Op, false);
444
445        REPORTER_ASSERT(reporter, 1 == count(stack));
446
447        stack.getBounds(&bound, &type, &isIntersectionOfRects);
448
449        REPORTER_ASSERT(reporter, isIntersectionOfRects);
450    }
451
452    // mixed nested (aa inside bw) - should merge
453    {
454        SkClipStack stack;
455
456        stack.clipDevRect(nestedParent, SkRegion::kReplace_Op, false);
457
458        stack.clipDevRect(nestedChild, SkRegion::kIntersect_Op, true);
459
460        REPORTER_ASSERT(reporter, 1 == count(stack));
461
462        stack.getBounds(&bound, &type, &isIntersectionOfRects);
463
464        REPORTER_ASSERT(reporter, isIntersectionOfRects);
465    }
466
467    // reverse nested (aa inside bw) - should _not_ merge
468    {
469        SkClipStack stack;
470
471        stack.clipDevRect(nestedChild, SkRegion::kReplace_Op, false);
472
473        stack.clipDevRect(nestedParent, SkRegion::kIntersect_Op, true);
474
475        REPORTER_ASSERT(reporter, 2 == count(stack));
476
477        stack.getBounds(&bound, &type, &isIntersectionOfRects);
478
479        REPORTER_ASSERT(reporter, !isIntersectionOfRects);
480    }
481}
482
483///////////////////////////////////////////////////////////////////////////////////////////////////
484
485#if SK_SUPPORT_GPU
486// Functions that add a shape to the clip stack. The shape is computed from a rectangle.
487// AA is always disabled since the clip stack reducer can cause changes in aa rasterization of the
488// stack. A fractional edge repeated in different elements may be rasterized fewer times using the
489// reduced stack.
490typedef void (*AddElementFunc) (const SkRect& rect,
491                                bool invert,
492                                SkRegion::Op op,
493                                SkClipStack* stack);
494
495static void add_round_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) {
496    SkPath path;
497    SkScalar rx = rect.width() / 10;
498    SkScalar ry = rect.height() / 20;
499    path.addRoundRect(rect, rx, ry);
500    if (invert) {
501        path.setFillType(SkPath::kInverseWinding_FillType);
502    }
503    stack->clipDevPath(path, op, false);
504};
505
506static void add_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) {
507    if (invert) {
508        SkPath path;
509        path.addRect(rect);
510        path.setFillType(SkPath::kInverseWinding_FillType);
511        stack->clipDevPath(path, op, false);
512    } else {
513        stack->clipDevRect(rect, op, false);
514    }
515};
516
517static void add_oval(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) {
518    SkPath path;
519    path.addOval(rect);
520    if (invert) {
521        path.setFillType(SkPath::kInverseWinding_FillType);
522    }
523    stack->clipDevPath(path, op, false);
524};
525
526static void add_elem_to_stack(const SkClipStack::Element& element, SkClipStack* stack) {
527    switch (element.getType()) {
528        case SkClipStack::Element::kRect_Type:
529            stack->clipDevRect(element.getRect(), element.getOp(), element.isAA());
530            break;
531        case SkClipStack::Element::kPath_Type:
532            stack->clipDevPath(element.getPath(), element.getOp(), element.isAA());
533            break;
534        case SkClipStack::Element::kEmpty_Type:
535            SkDEBUGFAIL("Why did the reducer produce an explicit empty.");
536            stack->clipEmpty();
537            break;
538    }
539}
540
541static void add_elem_to_region(const SkClipStack::Element& element,
542                               const SkIRect& bounds,
543                               SkRegion* region) {
544    SkRegion elemRegion;
545    SkRegion boundsRgn(bounds);
546
547    switch (element.getType()) {
548        case SkClipStack::Element::kRect_Type: {
549            SkPath path;
550            path.addRect(element.getRect());
551            elemRegion.setPath(path, boundsRgn);
552            break;
553        }
554        case SkClipStack::Element::kPath_Type:
555            elemRegion.setPath(element.getPath(), boundsRgn);
556            break;
557        case SkClipStack::Element::kEmpty_Type:
558            //
559            region->setEmpty();
560            return;
561    }
562    region->op(elemRegion, element.getOp());
563}
564
565// This can assist with debugging the clip stack reduction code when the test below fails.
566static void print_clip(const SkClipStack::Element& element) {
567    static const char* kOpStrs[] = {
568        "DF",
569        "IS",
570        "UN",
571        "XR",
572        "RD",
573        "RP",
574    };
575    if (SkClipStack::Element::kEmpty_Type != element.getType()) {
576        const SkRect& bounds = element.getBounds();
577        bool isRect = SkClipStack::Element::kRect_Type == element.getType();
578        SkDebugf("%s %s %s [%f %f] x [%f %f]\n",
579                 kOpStrs[element.getOp()],
580                 (isRect ? "R" : "P"),
581                 (element.isInverseFilled() ? "I" : " "),
582                 bounds.fLeft, bounds.fRight, bounds.fTop, bounds.fBottom);
583    } else {
584        SkDebugf("EM\n");
585    }
586}
587
588static void test_reduced_clip_stack(skiatest::Reporter* reporter) {
589    // We construct random clip stacks, reduce them, and then rasterize both versions to verify that
590    // they are equal.
591
592    // All the clip elements will be contained within these bounds.
593    static const SkRect kBounds = SkRect::MakeWH(100, 100);
594
595    enum {
596        kNumTests = 200,
597        kMinElemsPerTest = 1,
598        kMaxElemsPerTest = 50,
599    };
600
601    // min/max size of a clip element as a fraction of kBounds.
602    static const SkScalar kMinElemSizeFrac = SK_Scalar1 / 5;
603    static const SkScalar kMaxElemSizeFrac = SK_Scalar1;
604
605    static const SkRegion::Op kOps[] = {
606        SkRegion::kDifference_Op,
607        SkRegion::kIntersect_Op,
608        SkRegion::kUnion_Op,
609        SkRegion::kXOR_Op,
610        SkRegion::kReverseDifference_Op,
611        SkRegion::kReplace_Op,
612    };
613
614    // Replace operations short-circuit the optimizer. We want to make sure that we test this code
615    // path a little bit but we don't want it to prevent us from testing many longer traversals in
616    // the optimizer.
617    static const int kReplaceDiv = 4 * kMaxElemsPerTest;
618
619    // We want to test inverse fills. However, they are quite rare in practice so don't over do it.
620    static const SkScalar kFractionInverted = SK_Scalar1 / kMaxElemsPerTest;
621
622    static const AddElementFunc kElementFuncs[] = {
623        add_rect,
624        add_round_rect,
625        add_oval,
626    };
627
628    SkRandom r;
629
630    for (int i = 0; i < kNumTests; ++i) {
631        // Randomly generate a clip stack.
632        SkClipStack stack;
633        int numElems = r.nextRangeU(kMinElemsPerTest, kMaxElemsPerTest);
634        for (int e = 0; e < numElems; ++e) {
635            SkRegion::Op op = kOps[r.nextULessThan(SK_ARRAY_COUNT(kOps))];
636            if (op == SkRegion::kReplace_Op) {
637                if (r.nextU() % kReplaceDiv) {
638                    --e;
639                    continue;
640                }
641            }
642
643            // saves can change the clip stack behavior when an element is added.
644            bool doSave = r.nextBool();
645
646            SkSize size = SkSize::Make(
647                SkScalarFloorToScalar(SkScalarMul(kBounds.width(), r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac))),
648                SkScalarFloorToScalar(SkScalarMul(kBounds.height(), r.nextRangeScalar(kMinElemSizeFrac, kMaxElemSizeFrac))));
649
650            SkPoint xy = {SkScalarFloorToScalar(r.nextRangeScalar(kBounds.fLeft, kBounds.fRight - size.fWidth)),
651                          SkScalarFloorToScalar(r.nextRangeScalar(kBounds.fTop, kBounds.fBottom - size.fHeight))};
652
653            SkRect rect = SkRect::MakeXYWH(xy.fX, xy.fY, size.fWidth, size.fHeight);
654
655            bool invert = r.nextBiasedBool(kFractionInverted);
656            kElementFuncs[r.nextULessThan(SK_ARRAY_COUNT(kElementFuncs))](rect, invert, op, &stack);
657            if (doSave) {
658                stack.save();
659            }
660        }
661
662        SkRect inflatedBounds = kBounds;
663        inflatedBounds.outset(kBounds.width() / 2, kBounds.height() / 2);
664        SkIRect inflatedIBounds;
665        inflatedBounds.roundOut(&inflatedIBounds);
666
667        typedef GrReducedClip::ElementList ElementList;
668        // Get the reduced version of the stack.
669        ElementList reducedClips;
670
671        GrReducedClip::InitialState initial;
672        GrReducedClip::GrReduceClipStack(stack, inflatedBounds, &reducedClips, &initial);
673
674        // Build a new clip stack based on the reduced clip elements
675        SkClipStack reducedStack;
676        if (GrReducedClip::kAllOut_InitialState == initial) {
677            // whether the result is bounded or not, the whole plane should start outside the clip.
678            reducedStack.clipEmpty();
679        }
680        for (ElementList::Iter iter = reducedClips.headIter(); NULL != iter.get(); iter.next()) {
681            add_elem_to_stack(*iter.get(), &reducedStack);
682        }
683
684        // convert both the original stack and reduced stack to SkRegions and see if they're equal
685        SkRegion region;
686        SkRegion reducedRegion;
687
688        region.setRect(inflatedIBounds);
689        const SkClipStack::Element* element;
690        SkClipStack::Iter iter(stack, SkClipStack::Iter::kBottom_IterStart);
691        while ((element = iter.next())) {
692            add_elem_to_region(*element, inflatedIBounds, &region);
693        }
694
695        reducedRegion.setRect(inflatedIBounds);
696        iter.reset(reducedStack, SkClipStack::Iter::kBottom_IterStart);
697        while ((element = iter.next())) {
698            add_elem_to_region(*element, inflatedIBounds, &reducedRegion);
699        }
700
701        REPORTER_ASSERT(reporter, region == reducedRegion);
702    }
703}
704
705#endif
706///////////////////////////////////////////////////////////////////////////////////////////////////
707
708static void TestClipStack(skiatest::Reporter* reporter) {
709    SkClipStack stack;
710
711    REPORTER_ASSERT(reporter, 0 == stack.getSaveCount());
712    assert_count(reporter, stack, 0);
713
714    static const SkIRect gRects[] = {
715        { 0, 0, 100, 100 },
716        { 25, 25, 125, 125 },
717        { 0, 0, 1000, 1000 },
718        { 0, 0, 75, 75 }
719    };
720    for (size_t i = 0; i < SK_ARRAY_COUNT(gRects); i++) {
721        stack.clipDevRect(gRects[i], SkRegion::kIntersect_Op);
722    }
723
724    // all of the above rects should have been intersected, leaving only 1 rect
725    SkClipStack::B2TIter iter(stack);
726    const SkClipStack::Element* element = iter.next();
727    SkRect answer;
728    answer.iset(25, 25, 75, 75);
729
730    REPORTER_ASSERT(reporter, NULL != element);
731    REPORTER_ASSERT(reporter, SkClipStack::Element::kRect_Type == element->getType());
732    REPORTER_ASSERT(reporter, SkRegion::kIntersect_Op == element->getOp());
733    REPORTER_ASSERT(reporter, element->getRect() == answer);
734    // now check that we only had one in our iterator
735    REPORTER_ASSERT(reporter, !iter.next());
736
737    stack.reset();
738    REPORTER_ASSERT(reporter, 0 == stack.getSaveCount());
739    assert_count(reporter, stack, 0);
740
741    test_assign_and_comparison(reporter);
742    test_iterators(reporter);
743    test_bounds(reporter, true);        // once with rects
744    test_bounds(reporter, false);       // once with paths
745    test_isWideOpen(reporter);
746    test_rect_merging(reporter);
747#if SK_SUPPORT_GPU
748    test_reduced_clip_stack(reporter);
749#endif
750}
751
752#include "TestClassDef.h"
753DEFINE_TESTCLASS("ClipStack", TestClipStackClass, TestClipStack)
754