MatrixClipCollapseTest.cpp revision 105a4a584c4c2c84c24e102112326b15683673f5
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    kLast_DrawOpType = kRect_DrawOpType
133};
134
135static const int kDrawOpTypeCount = kLast_DrawOpType + 1;
136
137typedef void (*PFEmitMC)(SkCanvas* canvas, MatType mat, ClipType clip,
138                         DrawOpType draw, SkTDArray<DrawType>* expected,
139                         int accumulatedClips);
140typedef void (*PFEmitBody)(SkCanvas* canvas, PFEmitMC emitMC, MatType mat,
141                           ClipType clip, DrawOpType draw,
142                           SkTDArray<DrawType>* expected, int accumulatedClips);
143typedef void (*PFEmitStruct)(SkCanvas* canvas, PFEmitMC emitMC, MatType mat,
144                             ClipType clip, PFEmitBody emitBody, DrawOpType draw,
145                             SkTDArray<DrawType>* expected);
146
147//////////////////////////////////////////////////////////////////////////////
148
149// TODO: expand the testing to include the different ops & AA types!
150static void emit_clip(SkCanvas* canvas, ClipType clip) {
151    switch (clip) {
152        case kNone_ClipType:
153            break;
154        case kRect_ClipType: {
155            SkRect r = SkRect::MakeLTRB(10, 10, 90, 90);
156            canvas->clipRect(r, SkRegion::kIntersect_Op, true);
157            break;
158        }
159        case kRRect_ClipType: {
160            SkRect r = SkRect::MakeLTRB(10, 10, 90, 90);
161            SkRRect rr;
162            rr.setRectXY(r, 10, 10);
163            canvas->clipRRect(rr, SkRegion::kIntersect_Op, true);
164            break;
165        }
166        case kPath_ClipType: {
167            SkPath p;
168            p.moveTo(5.0f, 5.0f);
169            p.lineTo(50.0f, 50.0f);
170            p.lineTo(100.0f, 5.0f);
171            p.close();
172            canvas->clipPath(p, SkRegion::kIntersect_Op, true);
173            break;
174        }
175        case kRegion_ClipType: {
176            SkIRect rects[2] = {
177                { 1, 1, 55, 55 },
178                { 45, 45, 99, 99 },
179            };
180            SkRegion r;
181            r.setRects(rects, 2);
182            canvas->clipRegion(r, SkRegion::kIntersect_Op);
183            break;
184        }
185        default:
186            SkASSERT(0);
187    }
188}
189
190static void add_clip(ClipType clip, MatType mat, SkTDArray<DrawType>* expected) {
191    if (NULL == expected) {
192        // expected is NULL if this clip will be fused into later clips
193        return;
194    }
195
196    switch (clip) {
197        case kNone_ClipType:
198            break;
199        case kRect_ClipType:
200            *expected->append() = CONCAT;
201            *expected->append() = CLIP_RECT;
202            break;
203        case kRRect_ClipType:
204            *expected->append() = CONCAT;
205            *expected->append() = CLIP_RRECT;
206            break;
207        case kPath_ClipType:
208            *expected->append() = CONCAT;
209            *expected->append() = CLIP_PATH;
210            break;
211        case kRegion_ClipType:
212            *expected->append() = CONCAT;
213            *expected->append() = CLIP_REGION;
214            break;
215        default:
216            SkASSERT(0);
217    }
218}
219
220static void emit_mat(SkCanvas* canvas, MatType mat) {
221    switch (mat) {
222    case kNone_MatType:
223        break;
224    case kTranslate_MatType:
225        canvas->translate(5.0f, 5.0f);
226        break;
227    case kScale_MatType:
228        canvas->scale(1.1f, 1.1f);
229        break;
230    case kSkew_MatType:
231        canvas->skew(1.1f, 1.1f);
232        break;
233    case kRotate_MatType:
234        canvas->rotate(1.0f);
235        break;
236    case kConcat_MatType: {
237        SkMatrix m;
238        m.setTranslate(1.0f, 1.0f);
239        canvas->concat(m);
240        break;
241    }
242    case kSetMatrix_MatType: {
243        SkMatrix m;
244        m.setTranslate(1.0f, 1.0f);
245        canvas->setMatrix(m);
246        break;
247    }
248    default:
249        SkASSERT(0);
250    }
251}
252
253static void add_mat(MatType mat, SkTDArray<DrawType>* expected) {
254    if (NULL == expected) {
255        // expected is NULL if this matrix call will be fused into later ones
256        return;
257    }
258
259    switch (mat) {
260    case kNone_MatType:
261        break;
262    case kTranslate_MatType:    // fall thru
263    case kScale_MatType:        // fall thru
264    case kSkew_MatType:         // fall thru
265    case kRotate_MatType:       // fall thru
266    case kConcat_MatType:       // fall thru
267    case kSetMatrix_MatType:
268        // TODO: this system currently converts a setMatrix to concat. If we wanted to
269        // really preserve the setMatrix semantics we should keep it a setMatrix. I'm
270        // not sure if this is a good idea though since this would keep things like pinch
271        // zoom from working.
272        *expected->append() = CONCAT;
273        break;
274    default:
275        SkASSERT(0);
276    }
277}
278
279static void emit_draw(SkCanvas* canvas, DrawOpType draw, SkTDArray<DrawType>* expected) {
280    switch (draw) {
281        case kNone_DrawOpType:
282            break;
283        case kClear_DrawOpType:
284            canvas->clear(SK_ColorRED);
285            *expected->append() = DRAW_CLEAR;
286            break;
287        case kOval_DrawOpType: {
288            SkRect r = SkRect::MakeLTRB(10, 10, 90, 90);
289            SkPaint p;
290            canvas->drawOval(r, p);
291            *expected->append() = DRAW_OVAL;
292            break;
293        }
294        case kRect_DrawOpType: {
295            SkRect r = SkRect::MakeLTRB(10, 10, 90, 90);
296            SkPaint p;
297            canvas->drawRect(r, p);
298            *expected->append() = DRAW_RECT;
299            break;
300        }
301        case kRRect_DrawOpType: {
302            SkRect r = SkRect::MakeLTRB(10.0f, 10.0f, 90.0f, 90.0f);
303            SkRRect rr;
304            rr.setRectXY(r, 5.0f, 5.0f);
305            SkPaint p;
306            canvas->drawRRect(rr, p);
307            *expected->append() = DRAW_RRECT;
308            break;
309        }
310        default:
311            SkASSERT(0);
312    }
313}
314
315//////////////////////////////////////////////////////////////////////////////
316
317// Emit:
318//  clip
319//  matrix
320// Simple case - the clip isn't effect by the matrix
321static void emit_clip_and_mat(SkCanvas* canvas, MatType mat, ClipType clip,
322                              DrawOpType draw, SkTDArray<DrawType>* expected,
323                              int accumulatedClips) {
324    if (kNone_DrawOpType == draw) {
325        return;
326    }
327
328    emit_clip(canvas, clip);
329    emit_mat(canvas, mat);
330
331    for (int i = 0; i < accumulatedClips; ++i) {
332        add_clip(clip, mat, expected);
333    }
334    add_mat(mat, expected);
335}
336
337// Emit:
338//  matrix
339//  clip
340// Emitting the matrix first is more challenging since the matrix has to be
341// pushed across (i.e., applied to) the clip.
342static void emit_mat_and_clip(SkCanvas* canvas, MatType mat, ClipType clip,
343                              DrawOpType draw, SkTDArray<DrawType>* expected,
344                              int accumulatedClips) {
345    if (kNone_DrawOpType == draw) {
346        return;
347    }
348
349    emit_mat(canvas, mat);
350    emit_clip(canvas, clip);
351
352    // the matrix & clip order will be reversed once collapsed!
353    for (int i = 0; i < accumulatedClips; ++i) {
354        add_clip(clip, mat, expected);
355    }
356    add_mat(mat, expected);
357}
358
359// Emit:
360//  matrix
361//  clip
362//  matrix
363//  clip
364// This tests that the matrices and clips coalesce when collapsed
365static void emit_double_mat_and_clip(SkCanvas* canvas, MatType mat, ClipType clip,
366                                     DrawOpType draw, SkTDArray<DrawType>* expected,
367                                     int accumulatedClips) {
368    if (kNone_DrawOpType == draw) {
369        return;
370    }
371
372    emit_mat(canvas, mat);
373    emit_clip(canvas, clip);
374    emit_mat(canvas, mat);
375    emit_clip(canvas, clip);
376
377    for (int i = 0; i < accumulatedClips; ++i) {
378        add_clip(clip, mat, expected);
379        add_clip(clip, mat, expected);
380    }
381    add_mat(mat, expected);
382}
383
384// Emit:
385//  matrix
386//  clip
387//  clip
388// This tests accumulation of clips in same transform state. It also tests pushing
389// of the matrix across both the clips.
390static void emit_mat_clip_clip(SkCanvas* canvas, MatType mat, ClipType clip,
391                               DrawOpType draw, SkTDArray<DrawType>* expected,
392                               int accumulatedClips) {
393    if (kNone_DrawOpType == draw) {
394        return;
395    }
396
397    emit_mat(canvas, mat);
398    emit_clip(canvas, clip);
399    emit_clip(canvas, clip);
400
401    for (int i = 0; i < accumulatedClips; ++i) {
402        add_clip(clip, mat, expected);
403        add_clip(clip, mat, expected);
404    }
405    add_mat(mat, expected);
406}
407
408//////////////////////////////////////////////////////////////////////////////
409
410// Emit:
411//  matrix & clip calls
412//  draw op
413static void emit_body0(SkCanvas* canvas, PFEmitMC emitMC, MatType mat,
414                       ClipType clip, DrawOpType draw,
415                       SkTDArray<DrawType>* expected, int accumulatedClips) {
416    bool needsSaveRestore = kNone_DrawOpType != draw &&
417                            (kNone_MatType != mat || kNone_ClipType != clip);
418
419    if (needsSaveRestore) {
420        *expected->append() = SAVE;
421    }
422    (*emitMC)(canvas, mat, clip, draw, expected, accumulatedClips+1);
423    emit_draw(canvas, draw, expected);
424    if (needsSaveRestore) {
425        *expected->append() = RESTORE;
426    }
427}
428
429// Emit:
430//  matrix & clip calls
431//  draw op
432//  matrix & clip calls
433//  draw op
434static void emit_body1(SkCanvas* canvas, PFEmitMC emitMC, MatType mat,
435                       ClipType clip, DrawOpType draw,
436                       SkTDArray<DrawType>* expected, int accumulatedClips) {
437    bool needsSaveRestore = kNone_DrawOpType != draw &&
438                            (kNone_MatType != mat || kNone_ClipType != clip);
439
440    if (needsSaveRestore) {
441        *expected->append() = SAVE;
442    }
443    (*emitMC)(canvas, mat, clip, draw, expected, accumulatedClips+1);
444    emit_draw(canvas, draw, expected);
445    if (needsSaveRestore) {
446        *expected->append() = RESTORE;
447        *expected->append() = SAVE;
448    }
449    (*emitMC)(canvas, mat, clip, draw, expected, accumulatedClips+2);
450    emit_draw(canvas, draw, expected);
451    if (needsSaveRestore) {
452        *expected->append() = RESTORE;
453    }
454}
455
456// Emit:
457//  matrix & clip calls
458//  SaveLayer
459//      matrix & clip calls
460//      draw op
461//  Restore
462static void emit_body2(SkCanvas* canvas, PFEmitMC emitMC, MatType mat,
463                       ClipType clip, DrawOpType draw,
464                       SkTDArray<DrawType>* expected, int accumulatedClips) {
465    bool needsSaveRestore = kNone_DrawOpType != draw &&
466                            (kNone_MatType != mat || kNone_ClipType != clip);
467
468    if (needsSaveRestore) {
469        *expected->append() = SAVE_LAYER;
470    }
471    (*emitMC)(canvas, mat, clip, draw, NULL, 0); // these get fused into later ops
472    // TODO: widen testing to exercise saveLayer's parameters
473    canvas->saveLayer(NULL, NULL);
474        if (needsSaveRestore) {
475            *expected->append() = SAVE;
476        }
477        (*emitMC)(canvas, mat, clip, draw, expected, accumulatedClips+2);
478        emit_draw(canvas, draw, expected);
479        if (needsSaveRestore) {
480            *expected->append() = RESTORE;
481        }
482    canvas->restore();
483    if (needsSaveRestore) {
484        *expected->append() = RESTORE;
485    }
486}
487
488// Emit:
489//  matrix & clip calls
490//  SaveLayer
491//      matrix & clip calls
492//      SaveLayer
493//          matrix & clip calls
494//          draw op
495//      Restore
496//      matrix & clip calls (will be ignored)
497//  Restore
498static void emit_body3(SkCanvas* canvas, PFEmitMC emitMC, MatType mat,
499                       ClipType clip, DrawOpType draw,
500                       SkTDArray<DrawType>* expected, int accumulatedClips) {
501    bool needsSaveRestore = kNone_DrawOpType != draw &&
502                            (kNone_MatType != mat || kNone_ClipType != clip);
503
504    // This saveLayer will always be forced b.c. we currently can't tell
505    // ahead of time if it will be empty (see comment in SkMatrixClipStateMgr::save)
506    *expected->append() = SAVE_LAYER;
507
508    (*emitMC)(canvas, mat, clip, draw, NULL, 0); // these get fused into later ops
509    // TODO: widen testing to exercise saveLayer's parameters
510    canvas->saveLayer(NULL, NULL);
511        (*emitMC)(canvas, mat, clip, draw, NULL, 0); // these get fused into later ops
512        if (needsSaveRestore) {
513            *expected->append() = SAVE_LAYER;
514        }
515        // TODO: widen testing to exercise saveLayer's parameters
516        canvas->saveLayer(NULL, NULL);
517            if (needsSaveRestore) {
518                *expected->append() = SAVE;
519            }
520            (*emitMC)(canvas, mat, clip, draw, expected, accumulatedClips+3);
521            emit_draw(canvas, draw, expected);
522            if (needsSaveRestore) {
523                *expected->append() = RESTORE;
524            }
525        canvas->restore();
526        if (needsSaveRestore) {
527            *expected->append() = RESTORE;
528        }
529    canvas->restore();
530
531    // required to match forced SAVE_LAYER
532    *expected->append() = RESTORE;
533}
534
535//////////////////////////////////////////////////////////////////////////////
536
537// Emit:
538//  Save
539//      some body
540//  Restore
541// Note: the outer save/restore are provided by beginRecording/endRecording
542static void emit_struct0(SkCanvas* canvas,
543                         PFEmitMC emitMC, MatType mat, ClipType clip,
544                         PFEmitBody emitBody, DrawOpType draw,
545                         SkTDArray<DrawType>* expected) {
546    (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 0);
547}
548
549// Emit:
550//  Save
551//      matrix & clip calls
552//      Save
553//          some body
554//      Restore
555//      matrix & clip calls (will be ignored)
556//  Restore
557// Note: the outer save/restore are provided by beginRecording/endRecording
558static void emit_struct1(SkCanvas* canvas,
559                         PFEmitMC emitMC, MatType mat, ClipType clip,
560                         PFEmitBody emitBody, DrawOpType draw,
561                         SkTDArray<DrawType>* expected) {
562    (*emitMC)(canvas, mat, clip, draw, NULL, 0); // these get fused into later ops
563    canvas->save();
564        (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 1);
565    canvas->restore();
566    (*emitMC)(canvas, mat, clip, draw, NULL, 0); // these will get removed
567}
568
569// Emit:
570//  Save
571//      matrix & clip calls
572//      Save
573//          some body
574//      Restore
575//      Save
576//          some body
577//      Restore
578//      matrix & clip calls (will be ignored)
579//  Restore
580// Note: the outer save/restore are provided by beginRecording/endRecording
581static void emit_struct2(SkCanvas* canvas,
582                         PFEmitMC emitMC, MatType mat, ClipType clip,
583                         PFEmitBody emitBody, DrawOpType draw,
584                         SkTDArray<DrawType>* expected) {
585    (*emitMC)(canvas, mat, clip, draw, NULL, 1); // these will get fused into later ops
586    canvas->save();
587        (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 1);
588    canvas->restore();
589    canvas->save();
590        (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 1);
591    canvas->restore();
592    (*emitMC)(canvas, mat, clip, draw, NULL, 1); // these will get removed
593}
594
595// Emit:
596//  Save
597//      matrix & clip calls
598//      Save
599//          some body
600//      Restore
601//      Save
602//          matrix & clip calls
603//          Save
604//              some body
605//          Restore
606//      Restore
607//      matrix & clip calls (will be ignored)
608//  Restore
609// Note: the outer save/restore are provided by beginRecording/endRecording
610static void emit_struct3(SkCanvas* canvas,
611                         PFEmitMC emitMC, MatType mat, ClipType clip,
612                         PFEmitBody emitBody, DrawOpType draw,
613                         SkTDArray<DrawType>* expected) {
614    (*emitMC)(canvas, mat, clip, draw, NULL, 0); // these will get fused into later ops
615    canvas->save();
616        (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 1);
617    canvas->restore();
618    canvas->save();
619        (*emitMC)(canvas, mat, clip, draw, NULL, 1); // these will get fused into later ops
620        canvas->save();
621            (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 2);
622        canvas->restore();
623    canvas->restore();
624    (*emitMC)(canvas, mat, clip, draw, NULL, 0); // these will get removed
625}
626
627//////////////////////////////////////////////////////////////////////////////
628
629#ifdef SK_COLLAPSE_MATRIX_CLIP_STATE
630static void print(const SkTDArray<DrawType>& expected, const SkTDArray<DrawType>& actual) {
631    SkDebugf("\n\nexpected %d --- actual %d\n", expected.count(), actual.count());
632    int max = SkMax32(expected.count(), actual.count());
633
634    for (int i = 0; i < max; ++i) {
635        if (i < expected.count()) {
636            SkDebugf("%16s,    ", SkDrawCommand::GetCommandString(expected[i]));
637        } else {
638            SkDebugf("%16s,    ", " ");
639        }
640
641        if (i < actual.count()) {
642            SkDebugf("%s\n", SkDrawCommand::GetCommandString(actual[i]));
643        } else {
644            SkDebugf("\n");
645        }
646    }
647    SkDebugf("\n\n");
648    SkASSERT(0);
649}
650#endif
651
652static void test_collapse(skiatest::Reporter* reporter) {
653    PFEmitStruct gStructure[] = { emit_struct0, emit_struct1, emit_struct2, emit_struct3 };
654    PFEmitBody gBody[] = { emit_body0, emit_body1, emit_body2, emit_body3 };
655    PFEmitMC gMCs[] = { emit_clip_and_mat, emit_mat_and_clip,
656                        emit_double_mat_and_clip, emit_mat_clip_clip };
657
658    for (size_t i = 0; i < SK_ARRAY_COUNT(gStructure); ++i) {
659        for (size_t j = 0; j < SK_ARRAY_COUNT(gBody); ++j) {
660            for (size_t k = 0; k < SK_ARRAY_COUNT(gMCs); ++k) {
661                for (int l = 0; l < kMatTypeCount; ++l) {
662                    for (int m = 0; m < kClipTypeCount; ++m) {
663                        for (int n = 0; n < kDrawOpTypeCount; ++n) {
664#ifdef TEST_COLLAPSE_MATRIX_CLIP_STATE
665                            static int testID = -1;
666                            ++testID;
667                            if (testID < -1) {
668                                continue;
669                            }
670#endif
671
672                            SkTDArray<DrawType> expected, actual;
673
674                            SkPicture picture;
675
676                            // Note: beginRecording/endRecording add a save/restore pair
677                            SkCanvas* canvas = picture.beginRecording(100, 100);
678                            (*gStructure[i])(canvas,
679                                             gMCs[k],
680                                             (MatType) l,
681                                             (ClipType) m,
682                                             gBody[j],
683                                             (DrawOpType) n,
684                                             &expected);
685                            picture.endRecording();
686
687                            gets_ops(picture, &actual);
688
689                            REPORTER_ASSERT(reporter, expected.count() == actual.count());
690
691                            if (expected.count() != actual.count()) {
692#ifdef TEST_COLLAPSE_MATRIX_CLIP_STATE
693                                print(expected, actual);
694#endif
695                                continue;
696                            }
697
698                            for (int i = 0; i < expected.count(); ++i) {
699                                REPORTER_ASSERT(reporter, expected[i] == actual[i]);
700#ifdef TEST_COLLAPSE_MATRIX_CLIP_STATE
701                                if (expected[i] != actual[i]) {
702                                    print(expected, actual);
703                                }
704#endif
705                                break;
706                            }
707                        }
708                    }
709                }
710            }
711        }
712    }
713}
714
715DEF_TEST(MatrixClipCollapse, reporter) {
716    test_collapse(reporter);
717}
718
719#endif
720