text: /*
   * Copyright 2014 Google Inc.
   *
   * Use of this source code is governed by a BSD-style license that can be
   * found in the LICENSE file.
   */
  
  #include "include/core/SkCanvas.h"
  #include "include/core/SkPicture.h"
  #include "src/core/SkPictureFlat.h"
  #include "src/core/SkPictureRecord.h"
  #include "tests/Test.h"
  #include "tools/debugger/DebugCanvas.h"
  
  // This test exercises the Matrix/Clip State collapsing system. It generates
  // example skps and the compares the actual stored operations to the expected
  // operations. The test works by emitting canvas operations at three levels:
  // overall structure, bodies that draw something and model/clip state changes.
  //
  // Structure methods only directly emit save and restores but call the
  // ModelClip and Body helper methods to fill in the structure. Since they only
  // emit saves and restores the operations emitted by the structure methods will
  // be completely removed by the matrix/clip collapse. Note: every save in
  // a structure method is followed by a call to a ModelClip helper.
  //
  // Body methods only directly emit draw ops and saveLayer/restore pairs but call
  // the ModelClip helper methods. Since the body methods emit the ops that cannot
  // be collapsed (i.e., draw ops, saveLayer/restore) they also generate the
  // expected result information. Note: every saveLayer in a body method is
  // followed by a call to a ModelClip helper.
  //
  // The ModelClip methods output matrix and clip ops in various orders and
  // combinations. They contribute to the expected result by outputting the
  // expected matrix & clip ops. Note that, currently, the entire clip stack
  // is output for each MC state so the clip operations accumulate down the
  // save/restore stack.
  
  // TODOs:
  //   check on clip offsets
  //      - not sure if this is possible. The desire is to verify that the clip
  //        operations' offsets point to the correct follow-on operations. This
  //        could be difficult since there is no good way to communicate the
  //        offset stored in the SkPicture to the debugger's clip objects
  //   add comparison of rendered before & after images?
  //      - not sure if this would be useful since it somewhat duplicates the
  //        correctness test of running render_pictures in record mode and
  //        rendering before and after images. Additionally the matrix/clip collapse
  //        is sure to cause some small differences so an automated test might
  //        yield too many false positives.
  //   run the matrix/clip collapse system on the 10K skp set
  //      - this should give us warm fuzzies that the matrix clip collapse
  //        system is ready for prime time
  //   bench the recording times with/without matrix/clip collapsing
  
  #ifdef SK_COLLAPSE_MATRIX_CLIP_STATE
  
  // Enable/disable debugging helper code
  //#define TEST_COLLAPSE_MATRIX_CLIP_STATE 1
  
  // Extract the command ops from the input SkPicture
  static void gets_ops(SkPicture& input, SkTDArray<DrawType>* ops) {
    DebugCanvas debugCanvas(input.width(), input.height());
    debugCanvas.setBounds(input.width(), input.height());
    input.draw(&debugCanvas);
  
    ops->setCount(debugCanvas.getSize());
    for (int i = 0; i < debugCanvas.getSize(); ++i) {
      (*ops)[i] = debugCanvas.getDrawCommandAt(i)->getType();
    }
  }
  
  enum ClipType {
    kNone_ClipType,
    kRect_ClipType,
    kRRect_ClipType,
    kPath_ClipType,
    kRegion_ClipType,
  
    kLast_ClipType = kRRect_ClipType
  };
  
  static const int kClipTypeCount = kLast_ClipType + 1;
  
  enum MatType {
    kNone_MatType,
    kTranslate_MatType,
    kScale_MatType,
    kSkew_MatType,
    kRotate_MatType,
    kConcat_MatType,
    kSetMatrix_MatType,
  
    kLast_MatType = kScale_MatType
  };
  
  static const int kMatTypeCount = kLast_MatType + 1;
  
  // TODO: implement the rest of the draw ops
  enum DrawOpType {
    kNone_DrawOpType,
  #  if 0
      kBitmap_DrawOpType,
      kBitmapMatrix_DrawOpType,
      kBitmapNone_DrawOpType,
      kBitmapRectToRect_DrawOpType,
  #  endif
    kClear_DrawOpType,
  #  if 0
      kData_DrawOpType,
  #  endif
    kOval_DrawOpType,
  #  if 0
      kPaint_DrawOpType,
      kPath_DrawOpType,
      kPicture_DrawOpType,
      kPoints_DrawOpType,
      kPosText_DrawOpType,
      kPosTextTopBottom_DrawOpType,
      kPosTextH_DrawOpType,
      kPosTextHTopBottom_DrawOpType,
  #  endif
    kRect_DrawOpType,
    kRRect_DrawOpType,
  #  if 0
      kSprite_DrawOpType,
      kText_DrawOpType,
      kTextOnPath_DrawOpType,
      kTextTopBottom_DrawOpType,
      kDrawVertices_DrawOpType,
  #  endif
  
    kLastNonSaveLayer_DrawOpType = kRect_DrawOpType,
  
    // saveLayer's have to handled apart from the other draw operations
    // since they also alter the save/restore structure.
    kSaveLayer_DrawOpType,
  };
  
  static const int kNonSaveLayerDrawOpTypeCount = kLastNonSaveLayer_DrawOpType + 1;
  
  typedef void (*PFEmitMC)(
      SkCanvas* canvas, MatType mat, ClipType clip, DrawOpType draw, SkTDArray<DrawType>* expected,
      int accumulatedClips);
  typedef void (*PFEmitBody)(
      SkCanvas* canvas, PFEmitMC emitMC, MatType mat, ClipType clip, DrawOpType draw,
      SkTDArray<DrawType>* expected, int accumulatedClips);
  typedef void (*PFEmitStruct)(
      SkCanvas* canvas, PFEmitMC emitMC, MatType mat, ClipType clip, PFEmitBody emitBody,
      DrawOpType draw, SkTDArray<DrawType>* expected);
  
  //////////////////////////////////////////////////////////////////////////////
  
  // TODO: expand the testing to include the different ops & AA types!
  static void emit_clip(SkCanvas* canvas, ClipType clip) {
    switch (clip) {
      case kNone_ClipType: break;
      case kRect_ClipType: {
        SkRect r = SkRect::MakeLTRB(10, 10, 90, 90);
        canvas->clipRect(r, SkRegion::kIntersect_Op, true);
        break;
      }
      case kRRect_ClipType: {
        SkRect r = SkRect::MakeLTRB(10, 10, 90, 90);
        SkRRect rr;
        rr.setRectXY(r, 10, 10);
        canvas->clipRRect(rr, SkRegion::kIntersect_Op, true);
        break;
      }
      case kPath_ClipType: {
        SkPath p;
        p.moveTo(5.0f, 5.0f);
        p.lineTo(50.0f, 50.0f);
        p.lineTo(100.0f, 5.0f);
        p.close();
        canvas->clipPath(p, SkRegion::kIntersect_Op, true);
        break;
      }
      case kRegion_ClipType: {
        SkIRect rects[2] = {
            {1, 1, 55, 55},
            {45, 45, 99, 99},
        };
        SkRegion r;
        r.setRects(rects, 2);
        canvas->clipRegion(r, SkRegion::kIntersect_Op);
        break;
      }
      default: SkASSERT(0);
    }
  }
  
  static void add_clip(ClipType clip, MatType mat, SkTDArray<DrawType>* expected) {
    if (nullptr == expected) {
      // expected is nullptr if this clip will be fused into later clips
      return;
    }
  
    switch (clip) {
      case kNone_ClipType: break;
      case kRect_ClipType:
        *expected->append() = CONCAT;
        *expected->append() = CLIP_RECT;
        break;
      case kRRect_ClipType:
        *expected->append() = CONCAT;
        *expected->append() = CLIP_RRECT;
        break;
      case kPath_ClipType:
        *expected->append() = CONCAT;
        *expected->append() = CLIP_PATH;
        break;
      case kRegion_ClipType:
        *expected->append() = CONCAT;
        *expected->append() = CLIP_REGION;
        break;
      default: SkASSERT(0);
    }
  }
  
  static void emit_mat(SkCanvas* canvas, MatType mat) {
    switch (mat) {
      case kNone_MatType: break;
      case kTranslate_MatType: canvas->translate(5.0f, 5.0f); break;
      case kScale_MatType: canvas->scale(1.1f, 1.1f); break;
      case kSkew_MatType: canvas->skew(1.1f, 1.1f); break;
      case kRotate_MatType: canvas->rotate(1.0f); break;
      case kConcat_MatType: {
        SkMatrix m;
        m.setTranslate(1.0f, 1.0f);
        canvas->concat(m);
        break;
      }
      case kSetMatrix_MatType: {
        SkMatrix m;
        m.setTranslate(1.0f, 1.0f);
        canvas->setMatrix(m);
        break;
      }
      default: SkASSERT(0);
    }
  }
  
  static void add_mat(MatType mat, SkTDArray<DrawType>* expected) {
    if (nullptr == expected) {
      // expected is nullptr if this matrix call will be fused into later ones
      return;
    }
  
    switch (mat) {
      case kNone_MatType: break;
      case kTranslate_MatType:  // fall thru
      case kScale_MatType:      // fall thru
      case kSkew_MatType:       // fall thru
      case kRotate_MatType:     // fall thru
      case kConcat_MatType:     // fall thru
      case kSetMatrix_MatType:
        // TODO: this system currently converts a setMatrix to concat. If we wanted to
        // really preserve the setMatrix semantics we should keep it a setMatrix. I'm
        // not sure if this is a good idea though since this would keep things like pinch
        // zoom from working.
        *expected->append() = CONCAT;
        break;
      default: SkASSERT(0);
    }
  }
  
  static void emit_draw(SkCanvas* canvas, DrawOpType draw, SkTDArray<DrawType>* expected) {
    switch (draw) {
      case kNone_DrawOpType: break;
      case kClear_DrawOpType:
        canvas->clear(SK_ColorRED);
        *expected->append() = DRAW_CLEAR;
        break;
      case kOval_DrawOpType: {
        SkRect r = SkRect::MakeLTRB(10, 10, 90, 90);
        SkPaint p;
        canvas->drawOval(r, p);
        *expected->append() = DRAW_OVAL;
        break;
      }
      case kRect_DrawOpType: {
        SkRect r = SkRect::MakeLTRB(10, 10, 90, 90);
        SkPaint p;
        canvas->drawRect(r, p);
        *expected->append() = DRAW_RECT;
        break;
      }
      case kRRect_DrawOpType: {
        SkRect r = SkRect::MakeLTRB(10.0f, 10.0f, 90.0f, 90.0f);
        SkRRect rr;
        rr.setRectXY(r, 5.0f, 5.0f);
        SkPaint p;
        canvas->drawRRect(rr, p);
        *expected->append() = DRAW_RRECT;
        break;
      }
      default: SkASSERT(0);
    }
  }
  
  //////////////////////////////////////////////////////////////////////////////
  
  // Emit:
  //  clip
  //  matrix
  // Simple case - the clip isn't effect by the matrix
  static void emit_clip_and_mat(
      SkCanvas* canvas, MatType mat, ClipType clip, DrawOpType draw, SkTDArray<DrawType>* expected,
      int accumulatedClips) {
    emit_clip(canvas, clip);
    emit_mat(canvas, mat);
  
    if (kNone_DrawOpType == draw) {
      return;
    }
  
    for (int i = 0; i < accumulatedClips; ++i) {
      add_clip(clip, mat, expected);
    }
    add_mat(mat, expected);
  }
  
  // Emit:
  //  matrix
  //  clip
  // Emitting the matrix first is more challenging since the matrix has to be
  // pushed across (i.e., applied to) the clip.
  static void emit_mat_and_clip(
      SkCanvas* canvas, MatType mat, ClipType clip, DrawOpType draw, SkTDArray<DrawType>* expected,
      int accumulatedClips) {
    emit_mat(canvas, mat);
    emit_clip(canvas, clip);
  
    if (kNone_DrawOpType == draw) {
      return;
    }
  
    // the matrix & clip order will be reversed once collapsed!
    for (int i = 0; i < accumulatedClips; ++i) {
      add_clip(clip, mat, expected);
    }
    add_mat(mat, expected);
  }
  
  // Emit:
  //  matrix
  //  clip
  //  matrix
  //  clip
  // This tests that the matrices and clips coalesce when collapsed
  static void emit_double_mat_and_clip(
      SkCanvas* canvas, MatType mat, ClipType clip, DrawOpType draw, SkTDArray<DrawType>* expected,
      int accumulatedClips) {
    emit_mat(canvas, mat);
    emit_clip(canvas, clip);
    emit_mat(canvas, mat);
    emit_clip(canvas, clip);
  
    if (kNone_DrawOpType == draw) {
      return;
    }
  
    for (int i = 0; i < accumulatedClips; ++i) {
      add_clip(clip, mat, expected);
      add_clip(clip, mat, expected);
    }
    add_mat(mat, expected);
  }
  
  // Emit:
  //  matrix
  //  clip
  //  clip
  // This tests accumulation of clips in same transform state. It also tests pushing
  // of the matrix across both the clips.
  static void emit_mat_clip_clip(
      SkCanvas* canvas, MatType mat, ClipType clip, DrawOpType draw, SkTDArray<DrawType>* expected,
      int accumulatedClips) {
    emit_mat(canvas, mat);
    emit_clip(canvas, clip);
    emit_clip(canvas, clip);
  
    if (kNone_DrawOpType == draw) {
      return;
    }
  
    for (int i = 0; i < accumulatedClips; ++i) {
      add_clip(clip, mat, expected);
      add_clip(clip, mat, expected);
    }
    add_mat(mat, expected);
  }
  
  //////////////////////////////////////////////////////////////////////////////
  
  // Emit:
  //  matrix & clip calls
  //  draw op
  static void emit_body0(
      SkCanvas* canvas, PFEmitMC emitMC, MatType mat, ClipType clip, DrawOpType draw,
      SkTDArray<DrawType>* expected, int accumulatedClips) {
    bool needsSaveRestore =
        kNone_DrawOpType != draw && (kNone_MatType != mat || kNone_ClipType != clip);
  
    if (needsSaveRestore) {
      *expected->append() = SAVE;
    }
    (*emitMC)(canvas, mat, clip, draw, expected, accumulatedClips + 1);
    emit_draw(canvas, draw, expected);
    if (needsSaveRestore) {
      *expected->append() = RESTORE;
    }
  }
  
  // Emit:
  //  matrix & clip calls
  //  draw op
  //  matrix & clip calls
  //  draw op
  static void emit_body1(
      SkCanvas* canvas, PFEmitMC emitMC, MatType mat, ClipType clip, DrawOpType draw,
      SkTDArray<DrawType>* expected, int accumulatedClips) {
    bool needsSaveRestore =
        kNone_DrawOpType != draw && (kNone_MatType != mat || kNone_ClipType != clip);
  
    if (needsSaveRestore) {
      *expected->append() = SAVE;
    }
    (*emitMC)(canvas, mat, clip, draw, expected, accumulatedClips + 1);
    emit_draw(canvas, draw, expected);
    if (needsSaveRestore) {
      *expected->append() = RESTORE;
      *expected->append() = SAVE;
    }
    (*emitMC)(canvas, mat, clip, draw, expected, accumulatedClips + 2);
    emit_draw(canvas, draw, expected);
    if (needsSaveRestore) {
      *expected->append() = RESTORE;
    }
  }
  
  // Emit:
  //  matrix & clip calls
  //  SaveLayer
  //      matrix & clip calls
  //      draw op
  //  Restore
  static void emit_body2(
      SkCanvas* canvas, PFEmitMC emitMC, MatType mat, ClipType clip, DrawOpType draw,
      SkTDArray<DrawType>* expected, int accumulatedClips) {
    bool needsSaveRestore =
        kNone_DrawOpType != draw && (kNone_MatType != mat || kNone_ClipType != clip);
  
    if (kNone_MatType != mat || kNone_ClipType != clip) {
      *expected->append() = SAVE;
    }
    (*emitMC)(canvas, mat, clip, kSaveLayer_DrawOpType, expected, accumulatedClips + 1);
    *expected->append() = SAVE_LAYER;
    // TODO: widen testing to exercise saveLayer's parameters
    canvas->saveLayer(nullptr, nullptr);
    if (needsSaveRestore) {
      *expected->append() = SAVE;
    }
    (*emitMC)(canvas, mat, clip, draw, expected, 1);
    emit_draw(canvas, draw, expected);
    if (needsSaveRestore) {
      *expected->append() = RESTORE;
    }
    canvas->restore();
    *expected->append() = RESTORE;
    if (kNone_MatType != mat || kNone_ClipType != clip) {
      *expected->append() = RESTORE;
    }
  }
  
  // Emit:
  //  matrix & clip calls
  //  SaveLayer
  //      matrix & clip calls
  //      SaveLayer
  //          matrix & clip calls
  //          draw op
  //      Restore
  //      matrix & clip calls (will be ignored)
  //  Restore
  static void emit_body3(
      SkCanvas* canvas, PFEmitMC emitMC, MatType mat, ClipType clip, DrawOpType draw,
      SkTDArray<DrawType>* expected, int accumulatedClips) {
    bool needsSaveRestore =
        kNone_DrawOpType != draw && (kNone_MatType != mat || kNone_ClipType != clip);
  
    if (kNone_MatType != mat || kNone_ClipType != clip) {
      *expected->append() = SAVE;
    }
    (*emitMC)(canvas, mat, clip, kSaveLayer_DrawOpType, expected, accumulatedClips + 1);
    *expected->append() = SAVE_LAYER;
    // TODO: widen testing to exercise saveLayer's parameters
    canvas->saveLayer(nullptr, nullptr);
    (*emitMC)(canvas, mat, clip, kSaveLayer_DrawOpType, expected, 1);
    if (kNone_MatType != mat || kNone_ClipType != clip) {
      *expected->append() = SAVE;
    }
    *expected->append() = SAVE_LAYER;
    // TODO: widen testing to exercise saveLayer's parameters
    canvas->saveLayer(nullptr, nullptr);
    if (needsSaveRestore) {
      *expected->append() = SAVE;
    }
    (*emitMC)(canvas, mat, clip, draw, expected, 1);
    emit_draw(canvas, draw, expected);
    if (needsSaveRestore) {
      *expected->append() = RESTORE;
    }
    canvas->restore();              // for saveLayer
    *expected->append() = RESTORE;  // for saveLayer
    if (kNone_MatType != mat || kNone_ClipType != clip) {
      *expected->append() = RESTORE;
    }
    canvas->restore();
    // required to match forced SAVE_LAYER
    *expected->append() = RESTORE;
    if (kNone_MatType != mat || kNone_ClipType != clip) {
      *expected->append() = RESTORE;
    }
  }
  
  //////////////////////////////////////////////////////////////////////////////
  
  // Emit:
  //  Save
  //      some body
  //  Restore
  // Note: the outer save/restore are provided by beginRecording/endRecording
  static void emit_struct0(
      SkCanvas* canvas, PFEmitMC emitMC, MatType mat, ClipType clip, PFEmitBody emitBody,
      DrawOpType draw, SkTDArray<DrawType>* expected) {
    (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 0);
  }
  
  // Emit:
  //  Save
  //      matrix & clip calls
  //      Save
  //          some body
  //      Restore
  //      matrix & clip calls (will be ignored)
  //  Restore
  // Note: the outer save/restore are provided by beginRecording/endRecording
  static void emit_struct1(
      SkCanvas* canvas, PFEmitMC emitMC, MatType mat, ClipType clip, PFEmitBody emitBody,
      DrawOpType draw, SkTDArray<DrawType>* expected) {
    (*emitMC)(canvas, mat, clip, draw, nullptr, 0);  // these get fused into later ops
    canvas->save();
    (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 1);
    canvas->restore();
    (*emitMC)(canvas, mat, clip, draw, nullptr, 0);  // these will get removed
  }
  
  // Emit:
  //  Save
  //      matrix & clip calls
  //      Save
  //          some body
  //      Restore
  //      Save
  //          some body
  //      Restore
  //      matrix & clip calls (will be ignored)
  //  Restore
  // Note: the outer save/restore are provided by beginRecording/endRecording
  static void emit_struct2(
      SkCanvas* canvas, PFEmitMC emitMC, MatType mat, ClipType clip, PFEmitBody emitBody,
      DrawOpType draw, SkTDArray<DrawType>* expected) {
    (*emitMC)(canvas, mat, clip, draw, nullptr, 1);  // these will get fused into later ops
    canvas->save();
    (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 1);
    canvas->restore();
    canvas->save();
    (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 1);
    canvas->restore();
    (*emitMC)(canvas, mat, clip, draw, nullptr, 1);  // these will get removed
  }
  
  // Emit:
  //  Save
  //      matrix & clip calls
  //      Save
  //          some body
  //      Restore
  //      Save
  //          matrix & clip calls
  //          Save
  //              some body
  //          Restore
  //      Restore
  //      matrix & clip calls (will be ignored)
  //  Restore
  // Note: the outer save/restore are provided by beginRecording/endRecording
  static void emit_struct3(
      SkCanvas* canvas, PFEmitMC emitMC, MatType mat, ClipType clip, PFEmitBody emitBody,
      DrawOpType draw, SkTDArray<DrawType>* expected) {
    (*emitMC)(canvas, mat, clip, draw, nullptr, 0);  // these will get fused into later ops
    canvas->save();
    (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 1);
    canvas->restore();
    canvas->save();
    (*emitMC)(canvas, mat, clip, draw, nullptr, 1);  // these will get fused into later ops
    canvas->save();
    (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 2);
    canvas->restore();
    canvas->restore();
    (*emitMC)(canvas, mat, clip, draw, nullptr, 0);  // these will get removed
  }
  
  //////////////////////////////////////////////////////////////////////////////
  
  #  ifdef SK_COLLAPSE_MATRIX_CLIP_STATE
  static void print(const SkTDArray<DrawType>& expected, const SkTDArray<DrawType>& actual) {
    SkDebugf("\n\nexpected %d --- actual %d\n", expected.count(), actual.count());
    int max = std::max(expected.count(), actual.count());
  
    for (int i = 0; i < max; ++i) {
      if (i < expected.count()) {
        SkDebugf("%16s,    ", DrawCommand::GetCommandString(expected[i]));
      } else {
        SkDebugf("%16s,    ", " ");
      }
  
      if (i < actual.count()) {
        SkDebugf("%s\n", DrawCommand::GetCommandString(actual[i]));
      } else {
        SkDebugf("\n");
      }
    }
    SkDebugf("\n\n");
    SkASSERT(0);
  }
  #  endif
  
  static void test_collapse(skiatest::Reporter* reporter) {
    PFEmitStruct gStructure[] = {emit_struct0, emit_struct1, emit_struct2, emit_struct3};
    PFEmitBody gBody[] = {emit_body0, emit_body1, emit_body2, emit_body3};
    PFEmitMC gMCs[] = {
        emit_clip_and_mat, emit_mat_and_clip, emit_double_mat_and_clip, emit_mat_clip_clip};
  
    for (size_t i = 0; i < SK_ARRAY_COUNT(gStructure); ++i) {
      for (size_t j = 0; j < SK_ARRAY_COUNT(gBody); ++j) {
        for (size_t k = 0; k < SK_ARRAY_COUNT(gMCs); ++k) {
          for (int l = 0; l < kMatTypeCount; ++l) {
            for (int m = 0; m < kClipTypeCount; ++m) {
              for (int n = 0; n < kNonSaveLayerDrawOpTypeCount; ++n) {
  #  ifdef TEST_COLLAPSE_MATRIX_CLIP_STATE
                static int testID = -1;
                ++testID;
                if (testID < -1) {
                  continue;
                }
                SkDebugf("test: %d\n", testID);
  #  endif
  
                SkTDArray<DrawType> expected, actual;
  
                SkPicture picture;
  
                // Note: beginRecording/endRecording add a save/restore pair
                SkCanvas* canvas = picture.beginRecording(100, 100);
                (*gStructure[i])(
                    canvas, gMCs[k], (MatType)l, (ClipType)m, gBody[j], (DrawOpType)n, &expected);
                picture.endRecording();
  
                gets_ops(picture, &actual);
  
                REPORTER_ASSERT(reporter, expected.count() == actual.count());
  
                if (expected.count() != actual.count()) {
  #  ifdef TEST_COLLAPSE_MATRIX_CLIP_STATE
                  print(expected, actual);
  #  endif
                  continue;
                }
  
                for (int i = 0; i < expected.count(); ++i) {
                  REPORTER_ASSERT(reporter, expected[i] == actual[i]);
  #  ifdef TEST_COLLAPSE_MATRIX_CLIP_STATE
                  if (expected[i] != actual[i]) {
                    print(expected, actual);
                  }
  #  endif
                  break;
                }
              }
            }
          }
        }
      }
    }
  }
  
  DEF_TEST(MatrixClipCollapse, reporter) { test_collapse(reporter); }
  
  #endif
  