1/*
2 * Copyright 2014 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 "SkCanvas.h"
10#include "SkDebugCanvas.h"
11#include "SkPicture.h"
12#include "SkPictureFlat.h"
13#include "SkPictureRecord.h"
14
15// This test exercises the Matrix/Clip State collapsing system. It generates
16// example skps and the compares the actual stored operations to the expected
17// operations. The test works by emitting canvas operations at three levels:
18// overall structure, bodies that draw something and model/clip state changes.
19//
20// Structure methods only directly emit save and restores but call the
21// ModelClip and Body helper methods to fill in the structure. Since they only
22// emit saves and restores the operations emitted by the structure methods will
23// be completely removed by the matrix/clip collapse. Note: every save in
24// a structure method is followed by a call to a ModelClip helper.
25//
26// Body methods only directly emit draw ops and saveLayer/restore pairs but call
27// the ModelClip helper methods. Since the body methods emit the ops that cannot
28// be collapsed (i.e., draw ops, saveLayer/restore) they also generate the
29// expected result information. Note: every saveLayer in a body method is
30// followed by a call to a ModelClip helper.
31//
32// The ModelClip methods output matrix and clip ops in various orders and
33// combinations. They contribute to the expected result by outputting the
34// expected matrix & clip ops. Note that, currently, the entire clip stack
35// is output for each MC state so the clip operations accumulate down the
36// save/restore stack.
37
38// TODOs:
39//   check on clip offsets
40//      - not sure if this is possible. The desire is to verify that the clip
41//        operations' offsets point to the correct follow-on operations. This
42//        could be difficult since there is no good way to communicate the
43//        offset stored in the SkPicture to the debugger's clip objects
44//   add comparison of rendered before & after images?
45//      - not sure if this would be useful since it somewhat duplicates the
46//        correctness test of running render_pictures in record mode and
47//        rendering before and after images. Additionally the matrix/clip collapse
48//        is sure to cause some small differences so an automated test might
49//        yield too many false positives.
50//   run the matrix/clip collapse system on the 10K skp set
51//      - this should give us warm fuzzies that the matrix clip collapse
52//        system is ready for prime time
53//   bench the recording times with/without matrix/clip collapsing
54
55#ifdef SK_COLLAPSE_MATRIX_CLIP_STATE
56
57// Enable/disable debugging helper code
58//#define TEST_COLLAPSE_MATRIX_CLIP_STATE 1
59
60// Extract the command ops from the input SkPicture
61static void gets_ops(SkPicture& input, SkTDArray<DrawType>* ops) {
62    SkDebugCanvas debugCanvas(input.width(), input.height());
63    debugCanvas.setBounds(input.width(), input.height());
64    input.draw(&debugCanvas);
65
66    ops->setCount(debugCanvas.getSize());
67    for (int i = 0; i < debugCanvas.getSize(); ++i) {
68        (*ops)[i] = debugCanvas.getDrawCommandAt(i)->getType();
69    }
70}
71
72enum ClipType {
73    kNone_ClipType,
74    kRect_ClipType,
75    kRRect_ClipType,
76    kPath_ClipType,
77    kRegion_ClipType,
78
79    kLast_ClipType = kRRect_ClipType
80};
81
82static const int kClipTypeCount = kLast_ClipType + 1;
83
84enum MatType {
85    kNone_MatType,
86    kTranslate_MatType,
87    kScale_MatType,
88    kSkew_MatType,
89    kRotate_MatType,
90    kConcat_MatType,
91    kSetMatrix_MatType,
92
93    kLast_MatType = kScale_MatType
94};
95
96static const int kMatTypeCount = kLast_MatType + 1;
97
98// TODO: implement the rest of the draw ops
99enum DrawOpType {
100    kNone_DrawOpType,
101#if 0
102    kBitmap_DrawOpType,
103    kBitmapMatrix_DrawOpType,
104    kBitmapNone_DrawOpType,
105    kBitmapRectToRect_DrawOpType,
106#endif
107    kClear_DrawOpType,
108#if 0
109    kData_DrawOpType,
110#endif
111    kOval_DrawOpType,
112#if 0
113    kPaint_DrawOpType,
114    kPath_DrawOpType,
115    kPicture_DrawOpType,
116    kPoints_DrawOpType,
117    kPosText_DrawOpType,
118    kPosTextTopBottom_DrawOpType,
119    kPosTextH_DrawOpType,
120    kPosTextHTopBottom_DrawOpType,
121#endif
122    kRect_DrawOpType,
123    kRRect_DrawOpType,
124#if 0
125    kSprite_DrawOpType,
126    kText_DrawOpType,
127    kTextOnPath_DrawOpType,
128    kTextTopBottom_DrawOpType,
129    kDrawVertices_DrawOpType,
130#endif
131
132    kLastNonSaveLayer_DrawOpType = kRect_DrawOpType,
133
134    // saveLayer's have to handled apart from the other draw operations
135    // since they also alter the save/restore structure.
136    kSaveLayer_DrawOpType,
137};
138
139static const int kNonSaveLayerDrawOpTypeCount = kLastNonSaveLayer_DrawOpType + 1;
140
141typedef void (*PFEmitMC)(SkCanvas* canvas, MatType mat, ClipType clip,
142                         DrawOpType draw, SkTDArray<DrawType>* expected,
143                         int accumulatedClips);
144typedef void (*PFEmitBody)(SkCanvas* canvas, PFEmitMC emitMC, MatType mat,
145                           ClipType clip, DrawOpType draw,
146                           SkTDArray<DrawType>* expected, int accumulatedClips);
147typedef void (*PFEmitStruct)(SkCanvas* canvas, PFEmitMC emitMC, MatType mat,
148                             ClipType clip, PFEmitBody emitBody, DrawOpType draw,
149                             SkTDArray<DrawType>* expected);
150
151//////////////////////////////////////////////////////////////////////////////
152
153// TODO: expand the testing to include the different ops & AA types!
154static void emit_clip(SkCanvas* canvas, ClipType clip) {
155    switch (clip) {
156        case kNone_ClipType:
157            break;
158        case kRect_ClipType: {
159            SkRect r = SkRect::MakeLTRB(10, 10, 90, 90);
160            canvas->clipRect(r, SkRegion::kIntersect_Op, true);
161            break;
162        }
163        case kRRect_ClipType: {
164            SkRect r = SkRect::MakeLTRB(10, 10, 90, 90);
165            SkRRect rr;
166            rr.setRectXY(r, 10, 10);
167            canvas->clipRRect(rr, SkRegion::kIntersect_Op, true);
168            break;
169        }
170        case kPath_ClipType: {
171            SkPath p;
172            p.moveTo(5.0f, 5.0f);
173            p.lineTo(50.0f, 50.0f);
174            p.lineTo(100.0f, 5.0f);
175            p.close();
176            canvas->clipPath(p, SkRegion::kIntersect_Op, true);
177            break;
178        }
179        case kRegion_ClipType: {
180            SkIRect rects[2] = {
181                { 1, 1, 55, 55 },
182                { 45, 45, 99, 99 },
183            };
184            SkRegion r;
185            r.setRects(rects, 2);
186            canvas->clipRegion(r, SkRegion::kIntersect_Op);
187            break;
188        }
189        default:
190            SkASSERT(0);
191    }
192}
193
194static void add_clip(ClipType clip, MatType mat, SkTDArray<DrawType>* expected) {
195    if (nullptr == expected) {
196        // expected is nullptr if this clip will be fused into later clips
197        return;
198    }
199
200    switch (clip) {
201        case kNone_ClipType:
202            break;
203        case kRect_ClipType:
204            *expected->append() = CONCAT;
205            *expected->append() = CLIP_RECT;
206            break;
207        case kRRect_ClipType:
208            *expected->append() = CONCAT;
209            *expected->append() = CLIP_RRECT;
210            break;
211        case kPath_ClipType:
212            *expected->append() = CONCAT;
213            *expected->append() = CLIP_PATH;
214            break;
215        case kRegion_ClipType:
216            *expected->append() = CONCAT;
217            *expected->append() = CLIP_REGION;
218            break;
219        default:
220            SkASSERT(0);
221    }
222}
223
224static void emit_mat(SkCanvas* canvas, MatType mat) {
225    switch (mat) {
226    case kNone_MatType:
227        break;
228    case kTranslate_MatType:
229        canvas->translate(5.0f, 5.0f);
230        break;
231    case kScale_MatType:
232        canvas->scale(1.1f, 1.1f);
233        break;
234    case kSkew_MatType:
235        canvas->skew(1.1f, 1.1f);
236        break;
237    case kRotate_MatType:
238        canvas->rotate(1.0f);
239        break;
240    case kConcat_MatType: {
241        SkMatrix m;
242        m.setTranslate(1.0f, 1.0f);
243        canvas->concat(m);
244        break;
245    }
246    case kSetMatrix_MatType: {
247        SkMatrix m;
248        m.setTranslate(1.0f, 1.0f);
249        canvas->setMatrix(m);
250        break;
251    }
252    default:
253        SkASSERT(0);
254    }
255}
256
257static void add_mat(MatType mat, SkTDArray<DrawType>* expected) {
258    if (nullptr == expected) {
259        // expected is nullptr if this matrix call will be fused into later ones
260        return;
261    }
262
263    switch (mat) {
264    case kNone_MatType:
265        break;
266    case kTranslate_MatType:    // fall thru
267    case kScale_MatType:        // fall thru
268    case kSkew_MatType:         // fall thru
269    case kRotate_MatType:       // fall thru
270    case kConcat_MatType:       // fall thru
271    case kSetMatrix_MatType:
272        // TODO: this system currently converts a setMatrix to concat. If we wanted to
273        // really preserve the setMatrix semantics we should keep it a setMatrix. I'm
274        // not sure if this is a good idea though since this would keep things like pinch
275        // zoom from working.
276        *expected->append() = CONCAT;
277        break;
278    default:
279        SkASSERT(0);
280    }
281}
282
283static void emit_draw(SkCanvas* canvas, DrawOpType draw, SkTDArray<DrawType>* expected) {
284    switch (draw) {
285        case kNone_DrawOpType:
286            break;
287        case kClear_DrawOpType:
288            canvas->clear(SK_ColorRED);
289            *expected->append() = DRAW_CLEAR;
290            break;
291        case kOval_DrawOpType: {
292            SkRect r = SkRect::MakeLTRB(10, 10, 90, 90);
293            SkPaint p;
294            canvas->drawOval(r, p);
295            *expected->append() = DRAW_OVAL;
296            break;
297        }
298        case kRect_DrawOpType: {
299            SkRect r = SkRect::MakeLTRB(10, 10, 90, 90);
300            SkPaint p;
301            canvas->drawRect(r, p);
302            *expected->append() = DRAW_RECT;
303            break;
304        }
305        case kRRect_DrawOpType: {
306            SkRect r = SkRect::MakeLTRB(10.0f, 10.0f, 90.0f, 90.0f);
307            SkRRect rr;
308            rr.setRectXY(r, 5.0f, 5.0f);
309            SkPaint p;
310            canvas->drawRRect(rr, p);
311            *expected->append() = DRAW_RRECT;
312            break;
313        }
314        default:
315            SkASSERT(0);
316    }
317}
318
319//////////////////////////////////////////////////////////////////////////////
320
321// Emit:
322//  clip
323//  matrix
324// Simple case - the clip isn't effect by the matrix
325static void emit_clip_and_mat(SkCanvas* canvas, MatType mat, ClipType clip,
326                              DrawOpType draw, SkTDArray<DrawType>* expected,
327                              int accumulatedClips) {
328    emit_clip(canvas, clip);
329    emit_mat(canvas, mat);
330
331    if (kNone_DrawOpType == draw) {
332        return;
333    }
334
335    for (int i = 0; i < accumulatedClips; ++i) {
336        add_clip(clip, mat, expected);
337    }
338    add_mat(mat, expected);
339}
340
341// Emit:
342//  matrix
343//  clip
344// Emitting the matrix first is more challenging since the matrix has to be
345// pushed across (i.e., applied to) the clip.
346static void emit_mat_and_clip(SkCanvas* canvas, MatType mat, ClipType clip,
347                              DrawOpType draw, SkTDArray<DrawType>* expected,
348                              int accumulatedClips) {
349    emit_mat(canvas, mat);
350    emit_clip(canvas, clip);
351
352    if (kNone_DrawOpType == draw) {
353        return;
354    }
355
356    // the matrix & clip order will be reversed once collapsed!
357    for (int i = 0; i < accumulatedClips; ++i) {
358        add_clip(clip, mat, expected);
359    }
360    add_mat(mat, expected);
361}
362
363// Emit:
364//  matrix
365//  clip
366//  matrix
367//  clip
368// This tests that the matrices and clips coalesce when collapsed
369static void emit_double_mat_and_clip(SkCanvas* canvas, MatType mat, ClipType clip,
370                                     DrawOpType draw, SkTDArray<DrawType>* expected,
371                                     int accumulatedClips) {
372    emit_mat(canvas, mat);
373    emit_clip(canvas, clip);
374    emit_mat(canvas, mat);
375    emit_clip(canvas, clip);
376
377    if (kNone_DrawOpType == draw) {
378        return;
379    }
380
381    for (int i = 0; i < accumulatedClips; ++i) {
382        add_clip(clip, mat, expected);
383        add_clip(clip, mat, expected);
384    }
385    add_mat(mat, expected);
386}
387
388// Emit:
389//  matrix
390//  clip
391//  clip
392// This tests accumulation of clips in same transform state. It also tests pushing
393// of the matrix across both the clips.
394static void emit_mat_clip_clip(SkCanvas* canvas, MatType mat, ClipType clip,
395                               DrawOpType draw, SkTDArray<DrawType>* expected,
396                               int accumulatedClips) {
397    emit_mat(canvas, mat);
398    emit_clip(canvas, clip);
399    emit_clip(canvas, clip);
400
401    if (kNone_DrawOpType == draw) {
402        return;
403    }
404
405    for (int i = 0; i < accumulatedClips; ++i) {
406        add_clip(clip, mat, expected);
407        add_clip(clip, mat, expected);
408    }
409    add_mat(mat, expected);
410}
411
412//////////////////////////////////////////////////////////////////////////////
413
414// Emit:
415//  matrix & clip calls
416//  draw op
417static void emit_body0(SkCanvas* canvas, PFEmitMC emitMC, MatType mat,
418                       ClipType clip, DrawOpType draw,
419                       SkTDArray<DrawType>* expected, int accumulatedClips) {
420    bool needsSaveRestore = kNone_DrawOpType != draw &&
421                            (kNone_MatType != mat || kNone_ClipType != clip);
422
423    if (needsSaveRestore) {
424        *expected->append() = SAVE;
425    }
426    (*emitMC)(canvas, mat, clip, draw, expected, accumulatedClips+1);
427    emit_draw(canvas, draw, expected);
428    if (needsSaveRestore) {
429        *expected->append() = RESTORE;
430    }
431}
432
433// Emit:
434//  matrix & clip calls
435//  draw op
436//  matrix & clip calls
437//  draw op
438static void emit_body1(SkCanvas* canvas, PFEmitMC emitMC, MatType mat,
439                       ClipType clip, DrawOpType draw,
440                       SkTDArray<DrawType>* expected, int accumulatedClips) {
441    bool needsSaveRestore = kNone_DrawOpType != draw &&
442                            (kNone_MatType != mat || kNone_ClipType != clip);
443
444    if (needsSaveRestore) {
445        *expected->append() = SAVE;
446    }
447    (*emitMC)(canvas, mat, clip, draw, expected, accumulatedClips+1);
448    emit_draw(canvas, draw, expected);
449    if (needsSaveRestore) {
450        *expected->append() = RESTORE;
451        *expected->append() = SAVE;
452    }
453    (*emitMC)(canvas, mat, clip, draw, expected, accumulatedClips+2);
454    emit_draw(canvas, draw, expected);
455    if (needsSaveRestore) {
456        *expected->append() = RESTORE;
457    }
458}
459
460// Emit:
461//  matrix & clip calls
462//  SaveLayer
463//      matrix & clip calls
464//      draw op
465//  Restore
466static void emit_body2(SkCanvas* canvas, PFEmitMC emitMC, MatType mat,
467                       ClipType clip, DrawOpType draw,
468                       SkTDArray<DrawType>* expected, int accumulatedClips) {
469    bool needsSaveRestore = kNone_DrawOpType != draw &&
470                            (kNone_MatType != mat || kNone_ClipType != clip);
471
472    if (kNone_MatType != mat || kNone_ClipType != clip) {
473        *expected->append() = SAVE;
474    }
475    (*emitMC)(canvas, mat, clip, kSaveLayer_DrawOpType, expected, accumulatedClips+1);
476    *expected->append() = SAVE_LAYER;
477    // TODO: widen testing to exercise saveLayer's parameters
478    canvas->saveLayer(nullptr, nullptr);
479        if (needsSaveRestore) {
480            *expected->append() = SAVE;
481        }
482        (*emitMC)(canvas, mat, clip, draw, expected, 1);
483        emit_draw(canvas, draw, expected);
484        if (needsSaveRestore) {
485            *expected->append() = RESTORE;
486        }
487    canvas->restore();
488    *expected->append() = RESTORE;
489    if (kNone_MatType != mat || kNone_ClipType != clip) {
490        *expected->append() = RESTORE;
491    }
492}
493
494// Emit:
495//  matrix & clip calls
496//  SaveLayer
497//      matrix & clip calls
498//      SaveLayer
499//          matrix & clip calls
500//          draw op
501//      Restore
502//      matrix & clip calls (will be ignored)
503//  Restore
504static void emit_body3(SkCanvas* canvas, PFEmitMC emitMC, MatType mat,
505                       ClipType clip, DrawOpType draw,
506                       SkTDArray<DrawType>* expected, int accumulatedClips) {
507    bool needsSaveRestore = kNone_DrawOpType != draw &&
508                            (kNone_MatType != mat || kNone_ClipType != clip);
509
510    if (kNone_MatType != mat || kNone_ClipType != clip) {
511        *expected->append() = SAVE;
512    }
513    (*emitMC)(canvas, mat, clip, kSaveLayer_DrawOpType, expected, accumulatedClips+1);
514    *expected->append() = SAVE_LAYER;
515    // TODO: widen testing to exercise saveLayer's parameters
516    canvas->saveLayer(nullptr, nullptr);
517        (*emitMC)(canvas, mat, clip, kSaveLayer_DrawOpType, expected, 1);
518        if (kNone_MatType != mat || kNone_ClipType != clip) {
519            *expected->append() = SAVE;
520        }
521        *expected->append() = SAVE_LAYER;
522        // TODO: widen testing to exercise saveLayer's parameters
523        canvas->saveLayer(nullptr, nullptr);
524            if (needsSaveRestore) {
525                *expected->append() = SAVE;
526            }
527            (*emitMC)(canvas, mat, clip, draw, expected, 1);
528            emit_draw(canvas, draw, expected);
529            if (needsSaveRestore) {
530                *expected->append() = RESTORE;
531            }
532        canvas->restore();             // for saveLayer
533        *expected->append() = RESTORE; // for saveLayer
534        if (kNone_MatType != mat || kNone_ClipType != clip) {
535            *expected->append() = RESTORE;
536        }
537    canvas->restore();
538    // required to match forced SAVE_LAYER
539    *expected->append() = RESTORE;
540    if (kNone_MatType != mat || kNone_ClipType != clip) {
541        *expected->append() = RESTORE;
542    }
543}
544
545//////////////////////////////////////////////////////////////////////////////
546
547// Emit:
548//  Save
549//      some body
550//  Restore
551// Note: the outer save/restore are provided by beginRecording/endRecording
552static void emit_struct0(SkCanvas* canvas,
553                         PFEmitMC emitMC, MatType mat, ClipType clip,
554                         PFEmitBody emitBody, DrawOpType draw,
555                         SkTDArray<DrawType>* expected) {
556    (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 0);
557}
558
559// Emit:
560//  Save
561//      matrix & clip calls
562//      Save
563//          some body
564//      Restore
565//      matrix & clip calls (will be ignored)
566//  Restore
567// Note: the outer save/restore are provided by beginRecording/endRecording
568static void emit_struct1(SkCanvas* canvas,
569                         PFEmitMC emitMC, MatType mat, ClipType clip,
570                         PFEmitBody emitBody, DrawOpType draw,
571                         SkTDArray<DrawType>* expected) {
572    (*emitMC)(canvas, mat, clip, draw, nullptr, 0); // these get fused into later ops
573    canvas->save();
574        (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 1);
575    canvas->restore();
576    (*emitMC)(canvas, mat, clip, draw, nullptr, 0); // these will get removed
577}
578
579// Emit:
580//  Save
581//      matrix & clip calls
582//      Save
583//          some body
584//      Restore
585//      Save
586//          some body
587//      Restore
588//      matrix & clip calls (will be ignored)
589//  Restore
590// Note: the outer save/restore are provided by beginRecording/endRecording
591static void emit_struct2(SkCanvas* canvas,
592                         PFEmitMC emitMC, MatType mat, ClipType clip,
593                         PFEmitBody emitBody, DrawOpType draw,
594                         SkTDArray<DrawType>* expected) {
595    (*emitMC)(canvas, mat, clip, draw, nullptr, 1); // these will get fused into later ops
596    canvas->save();
597        (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 1);
598    canvas->restore();
599    canvas->save();
600        (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 1);
601    canvas->restore();
602    (*emitMC)(canvas, mat, clip, draw, nullptr, 1); // these will get removed
603}
604
605// Emit:
606//  Save
607//      matrix & clip calls
608//      Save
609//          some body
610//      Restore
611//      Save
612//          matrix & clip calls
613//          Save
614//              some body
615//          Restore
616//      Restore
617//      matrix & clip calls (will be ignored)
618//  Restore
619// Note: the outer save/restore are provided by beginRecording/endRecording
620static void emit_struct3(SkCanvas* canvas,
621                         PFEmitMC emitMC, MatType mat, ClipType clip,
622                         PFEmitBody emitBody, DrawOpType draw,
623                         SkTDArray<DrawType>* expected) {
624    (*emitMC)(canvas, mat, clip, draw, nullptr, 0); // these will get fused into later ops
625    canvas->save();
626        (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 1);
627    canvas->restore();
628    canvas->save();
629        (*emitMC)(canvas, mat, clip, draw, nullptr, 1); // these will get fused into later ops
630        canvas->save();
631            (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 2);
632        canvas->restore();
633    canvas->restore();
634    (*emitMC)(canvas, mat, clip, draw, nullptr, 0); // these will get removed
635}
636
637//////////////////////////////////////////////////////////////////////////////
638
639#ifdef SK_COLLAPSE_MATRIX_CLIP_STATE
640static void print(const SkTDArray<DrawType>& expected, const SkTDArray<DrawType>& actual) {
641    SkDebugf("\n\nexpected %d --- actual %d\n", expected.count(), actual.count());
642    int max = SkMax32(expected.count(), actual.count());
643
644    for (int i = 0; i < max; ++i) {
645        if (i < expected.count()) {
646            SkDebugf("%16s,    ", SkDrawCommand::GetCommandString(expected[i]));
647        } else {
648            SkDebugf("%16s,    ", " ");
649        }
650
651        if (i < actual.count()) {
652            SkDebugf("%s\n", SkDrawCommand::GetCommandString(actual[i]));
653        } else {
654            SkDebugf("\n");
655        }
656    }
657    SkDebugf("\n\n");
658    SkASSERT(0);
659}
660#endif
661
662static void test_collapse(skiatest::Reporter* reporter) {
663    PFEmitStruct gStructure[] = { emit_struct0, emit_struct1, emit_struct2, emit_struct3 };
664    PFEmitBody gBody[] = { emit_body0, emit_body1, emit_body2, emit_body3 };
665    PFEmitMC gMCs[] = { emit_clip_and_mat, emit_mat_and_clip,
666                        emit_double_mat_and_clip, emit_mat_clip_clip };
667
668    for (size_t i = 0; i < SK_ARRAY_COUNT(gStructure); ++i) {
669        for (size_t j = 0; j < SK_ARRAY_COUNT(gBody); ++j) {
670            for (size_t k = 0; k < SK_ARRAY_COUNT(gMCs); ++k) {
671                for (int l = 0; l < kMatTypeCount; ++l) {
672                    for (int m = 0; m < kClipTypeCount; ++m) {
673                        for (int n = 0; n < kNonSaveLayerDrawOpTypeCount; ++n) {
674#ifdef TEST_COLLAPSE_MATRIX_CLIP_STATE
675                            static int testID = -1;
676                            ++testID;
677                            if (testID < -1) {
678                                continue;
679                            }
680                            SkDebugf("test: %d\n", testID);
681#endif
682
683                            SkTDArray<DrawType> expected, actual;
684
685                            SkPicture picture;
686
687                            // Note: beginRecording/endRecording add a save/restore pair
688                            SkCanvas* canvas = picture.beginRecording(100, 100);
689                            (*gStructure[i])(canvas,
690                                             gMCs[k],
691                                             (MatType) l,
692                                             (ClipType) m,
693                                             gBody[j],
694                                             (DrawOpType) n,
695                                             &expected);
696                            picture.endRecording();
697
698                            gets_ops(picture, &actual);
699
700                            REPORTER_ASSERT(reporter, expected.count() == actual.count());
701
702                            if (expected.count() != actual.count()) {
703#ifdef TEST_COLLAPSE_MATRIX_CLIP_STATE
704                                print(expected, actual);
705#endif
706                                continue;
707                            }
708
709                            for (int i = 0; i < expected.count(); ++i) {
710                                REPORTER_ASSERT(reporter, expected[i] == actual[i]);
711#ifdef TEST_COLLAPSE_MATRIX_CLIP_STATE
712                                if (expected[i] != actual[i]) {
713                                    print(expected, actual);
714                                }
715#endif
716                                break;
717                            }
718                        }
719                    }
720                }
721            }
722        }
723    }
724}
725
726DEF_TEST(MatrixClipCollapse, reporter) {
727    test_collapse(reporter);
728}
729
730#endif
731