Home | History | Annotate | Download | only in tests
      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
     61 static 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 
     72 enum ClipType {
     73     kNone_ClipType,
     74     kRect_ClipType,
     75     kRRect_ClipType,
     76     kPath_ClipType,
     77     kRegion_ClipType,
     78 
     79     kLast_ClipType = kRRect_ClipType
     80 };
     81 
     82 static const int kClipTypeCount = kLast_ClipType + 1;
     83 
     84 enum 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 
     96 static const int kMatTypeCount = kLast_MatType + 1;
     97 
     98 // TODO: implement the rest of the draw ops
     99 enum 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 
    139 static const int kNonSaveLayerDrawOpTypeCount = kLastNonSaveLayer_DrawOpType + 1;
    140 
    141 typedef void (*PFEmitMC)(SkCanvas* canvas, MatType mat, ClipType clip,
    142                          DrawOpType draw, SkTDArray<DrawType>* expected,
    143                          int accumulatedClips);
    144 typedef void (*PFEmitBody)(SkCanvas* canvas, PFEmitMC emitMC, MatType mat,
    145                            ClipType clip, DrawOpType draw,
    146                            SkTDArray<DrawType>* expected, int accumulatedClips);
    147 typedef 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!
    154 static 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 
    194 static 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 
    224 static 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 
    257 static 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 
    283 static 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
    325 static 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.
    346 static 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
    369 static 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.
    394 static 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
    417 static 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
    438 static 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
    466 static 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
    504 static 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
    552 static 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
    568 static 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
    591 static 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
    620 static 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
    640 static 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 
    662 static 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 
    726 DEF_TEST(MatrixClipCollapse, reporter) {
    727     test_collapse(reporter);
    728 }
    729 
    730 #endif
    731