From 1f71846e5c0ee85bb4e4c2b42044eefdebbcf2df Mon Sep 17 00:00:00 2001 From: "robertphillips@google.com" Date: Thu, 6 Feb 2014 14:22:47 +0000 Subject: Add test harness for matrix/clip state collapsing https://codereview.chromium.org/133003006/ git-svn-id: http://skia.googlecode.com/svn/trunk@13340 2bbb7eff-a529-9590-31e7-b0007b416f81 --- tests/MatrixClipCollapseTest.cpp | 716 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 716 insertions(+) create mode 100644 tests/MatrixClipCollapseTest.cpp (limited to 'tests/MatrixClipCollapseTest.cpp') diff --git a/tests/MatrixClipCollapseTest.cpp b/tests/MatrixClipCollapseTest.cpp new file mode 100644 index 0000000000..5e462729fd --- /dev/null +++ b/tests/MatrixClipCollapseTest.cpp @@ -0,0 +1,716 @@ +/* + * 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 "Test.h" +#include "SkCanvas.h" +#include "SkDebugCanvas.h" +#include "SkPicture.h" +#include "SkPictureFlat.h" +#include "SkPictureRecord.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 COLLAPSE_MATRIX_CLIP_STATE + +// Extract the command ops from the input SkPicture +static void gets_ops(SkPicture& input, SkTDArray* ops) { + SkDebugCanvas 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 + + kLast_DrawOpType = kRect_DrawOpType +}; + +static const int kDrawOpTypeCount = kLast_DrawOpType + 1; + +typedef void (*PFEmitMC)(SkCanvas* canvas, MatType mat, ClipType clip, + DrawOpType draw, SkTDArray* expected, + int accumulatedClips); +typedef void (*PFEmitBody)(SkCanvas* canvas, PFEmitMC emitMC, MatType mat, + ClipType clip, DrawOpType draw, + SkTDArray* expected, int accumulatedClips); +typedef void (*PFEmitStruct)(SkCanvas* canvas, PFEmitMC emitMC, MatType mat, + ClipType clip, PFEmitBody emitBody, DrawOpType draw, + SkTDArray* 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* expected) { + if (NULL == expected) { + // expected is NULL 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* expected) { + if (NULL == expected) { + // expected is NULL 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* 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* expected, + int accumulatedClips) { + if (kNone_DrawOpType == draw) { + return; + } + + emit_clip(canvas, clip); + emit_mat(canvas, mat); + + 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* expected, + int accumulatedClips) { + if (kNone_DrawOpType == draw) { + return; + } + + emit_mat(canvas, mat); + emit_clip(canvas, clip); + + // 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* expected, + int accumulatedClips) { + if (kNone_DrawOpType == draw) { + return; + } + + emit_mat(canvas, mat); + emit_clip(canvas, clip); + emit_mat(canvas, mat); + emit_clip(canvas, clip); + + 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* expected, + int accumulatedClips) { + if (kNone_DrawOpType == draw) { + return; + } + + emit_mat(canvas, mat); + emit_clip(canvas, clip); + emit_clip(canvas, clip); + + 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* 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* 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* expected, int accumulatedClips) { + bool needsSaveRestore = kNone_DrawOpType != draw && + (kNone_MatType != mat || kNone_ClipType != clip); + + if (needsSaveRestore) { + *expected->append() = SAVE_LAYER; + } + (*emitMC)(canvas, mat, clip, draw, NULL, 0); // these get fused into later ops + // TODO: widen testing to exercise saveLayer's parameters + canvas->saveLayer(NULL, NULL); + if (needsSaveRestore) { + *expected->append() = SAVE; + } + (*emitMC)(canvas, mat, clip, draw, expected, accumulatedClips+2); + emit_draw(canvas, draw, expected); + if (needsSaveRestore) { + *expected->append() = RESTORE; + } + canvas->restore(); + if (needsSaveRestore) { + *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* expected, int accumulatedClips) { + bool needsSaveRestore = kNone_DrawOpType != draw && + (kNone_MatType != mat || kNone_ClipType != clip); + + // This saveLayer will always be forced b.c. we currently can't tell + // ahead of time if it will be empty (see comment in SkMatrixClipStateMgr::save) + *expected->append() = SAVE_LAYER; + + (*emitMC)(canvas, mat, clip, draw, NULL, 0); // these get fused into later ops + // TODO: widen testing to exercise saveLayer's parameters + canvas->saveLayer(NULL, NULL); + (*emitMC)(canvas, mat, clip, draw, NULL, 0); // these get fused into later ops + if (needsSaveRestore) { + *expected->append() = SAVE_LAYER; + } + // TODO: widen testing to exercise saveLayer's parameters + canvas->saveLayer(NULL, NULL); + if (needsSaveRestore) { + *expected->append() = SAVE; + } + (*emitMC)(canvas, mat, clip, draw, expected, accumulatedClips+3); + emit_draw(canvas, draw, expected); + if (needsSaveRestore) { + *expected->append() = RESTORE; + } + canvas->restore(); + if (needsSaveRestore) { + *expected->append() = RESTORE; + } + canvas->restore(); + + // required to match forced SAVE_LAYER + *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* 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* expected) { + (*emitMC)(canvas, mat, clip, draw, NULL, 0); // these get fused into later ops + canvas->save(); + (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 1); + canvas->restore(); + (*emitMC)(canvas, mat, clip, draw, NULL, 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* expected) { + (*emitMC)(canvas, mat, clip, draw, NULL, 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, NULL, 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* expected) { + (*emitMC)(canvas, mat, clip, draw, NULL, 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, NULL, 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, NULL, 0); // these will get removed +} + +////////////////////////////////////////////////////////////////////////////// + +#ifdef COLLAPSE_MATRIX_CLIP_STATE +static void print(const SkTDArray& expected, const SkTDArray& actual) { + SkDebugf("\n\nexpected %d --- actual %d\n", expected.count(), actual.count()); + int max = SkMax32(expected.count(), actual.count()); + + for (int i = 0; i < max; ++i) { + if (i < expected.count()) { + SkDebugf("%16s, ", SkDrawCommand::GetCommandString(expected[i])); + } else { + SkDebugf("%16s, ", " "); + } + + if (i < actual.count()) { + SkDebugf("%s\n", SkDrawCommand::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 < kDrawOpTypeCount; ++n) { +#ifdef COLLAPSE_MATRIX_CLIP_STATE + static int testID = -1; + ++testID; + if (testID < -1) { + continue; + } +#endif + + SkTDArray 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 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 COLLAPSE_MATRIX_CLIP_STATE + if (expected[i] != actual[i]) { + print(expected, actual); + } +#endif + break; + } + } + } + } + } + } + } +} + +DEF_TEST(MatrixClipCollapse, reporter) { + test_collapse(reporter); +} + +#endif \ No newline at end of file -- cgit v1.2.3