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