2 * Copyright 2014 Google Inc.
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
10 #include "SkDebugCanvas.h"
11 #include "SkPicture.h"
12 #include "SkPictureFlat.h"
13 #include "SkPictureRecord.h"
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.
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.
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.
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.
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
55 #ifdef SK_COLLAPSE_MATRIX_CLIP_STATE
57 // Enable/disable debugging helper code
58 //#define TEST_COLLAPSE_MATRIX_CLIP_STATE 1
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);
66 ops->setCount(debugCanvas.getSize());
67 for (int i = 0; i < debugCanvas.getSize(); ++i) {
68 (*ops)[i] = debugCanvas.getDrawCommandAt(i)->getType();
79 kLast_ClipType = kRRect_ClipType
82 static const int kClipTypeCount = kLast_ClipType + 1;
93 kLast_MatType = kScale_MatType
96 static const int kMatTypeCount = kLast_MatType + 1;
98 // TODO: implement the rest of the draw ops
103 kBitmapMatrix_DrawOpType,
104 kBitmapNone_DrawOpType,
105 kBitmapRectToRect_DrawOpType,
118 kPosTextTopBottom_DrawOpType,
119 kPosTextH_DrawOpType,
120 kPosTextHTopBottom_DrawOpType,
127 kTextOnPath_DrawOpType,
128 kTextTopBottom_DrawOpType,
129 kDrawVertices_DrawOpType,
132 kLastNonSaveLayer_DrawOpType = kRect_DrawOpType,
134 // saveLayer's have to handled apart from the other draw operations
135 // since they also alter the save/restore structure.
136 kSaveLayer_DrawOpType,
139 static const int kNonSaveLayerDrawOpTypeCount = kLastNonSaveLayer_DrawOpType + 1;
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);
151 //////////////////////////////////////////////////////////////////////////////
153 // TODO: expand the testing to include the different ops & AA types!
154 static void emit_clip(SkCanvas* canvas, ClipType clip) {
158 case kRect_ClipType: {
159 SkRect r = SkRect::MakeLTRB(10, 10, 90, 90);
160 canvas->clipRect(r, SkRegion::kIntersect_Op, true);
163 case kRRect_ClipType: {
164 SkRect r = SkRect::MakeLTRB(10, 10, 90, 90);
166 rr.setRectXY(r, 10, 10);
167 canvas->clipRRect(rr, SkRegion::kIntersect_Op, true);
170 case kPath_ClipType: {
172 p.moveTo(5.0f, 5.0f);
173 p.lineTo(50.0f, 50.0f);
174 p.lineTo(100.0f, 5.0f);
176 canvas->clipPath(p, SkRegion::kIntersect_Op, true);
179 case kRegion_ClipType: {
185 r.setRects(rects, 2);
186 canvas->clipRegion(r, SkRegion::kIntersect_Op);
194 static void add_clip(ClipType clip, MatType mat, SkTDArray<DrawType>* expected) {
195 if (NULL == expected) {
196 // expected is NULL if this clip will be fused into later clips
204 *expected->append() = CONCAT;
205 *expected->append() = CLIP_RECT;
207 case kRRect_ClipType:
208 *expected->append() = CONCAT;
209 *expected->append() = CLIP_RRECT;
212 *expected->append() = CONCAT;
213 *expected->append() = CLIP_PATH;
215 case kRegion_ClipType:
216 *expected->append() = CONCAT;
217 *expected->append() = CLIP_REGION;
224 static void emit_mat(SkCanvas* canvas, MatType mat) {
228 case kTranslate_MatType:
229 canvas->translate(5.0f, 5.0f);
232 canvas->scale(1.1f, 1.1f);
235 canvas->skew(1.1f, 1.1f);
237 case kRotate_MatType:
238 canvas->rotate(1.0f);
240 case kConcat_MatType: {
242 m.setTranslate(1.0f, 1.0f);
246 case kSetMatrix_MatType: {
248 m.setTranslate(1.0f, 1.0f);
249 canvas->setMatrix(m);
257 static void add_mat(MatType mat, SkTDArray<DrawType>* expected) {
258 if (NULL == expected) {
259 // expected is NULL if this matrix call will be fused into later ones
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;
283 static void emit_draw(SkCanvas* canvas, DrawOpType draw, SkTDArray<DrawType>* expected) {
285 case kNone_DrawOpType:
287 case kClear_DrawOpType:
288 canvas->clear(SK_ColorRED);
289 *expected->append() = DRAW_CLEAR;
291 case kOval_DrawOpType: {
292 SkRect r = SkRect::MakeLTRB(10, 10, 90, 90);
294 canvas->drawOval(r, p);
295 *expected->append() = DRAW_OVAL;
298 case kRect_DrawOpType: {
299 SkRect r = SkRect::MakeLTRB(10, 10, 90, 90);
301 canvas->drawRect(r, p);
302 *expected->append() = DRAW_RECT;
305 case kRRect_DrawOpType: {
306 SkRect r = SkRect::MakeLTRB(10.0f, 10.0f, 90.0f, 90.0f);
308 rr.setRectXY(r, 5.0f, 5.0f);
310 canvas->drawRRect(rr, p);
311 *expected->append() = DRAW_RRECT;
319 //////////////////////////////////////////////////////////////////////////////
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);
331 if (kNone_DrawOpType == draw) {
335 for (int i = 0; i < accumulatedClips; ++i) {
336 add_clip(clip, mat, expected);
338 add_mat(mat, expected);
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);
352 if (kNone_DrawOpType == draw) {
356 // the matrix & clip order will be reversed once collapsed!
357 for (int i = 0; i < accumulatedClips; ++i) {
358 add_clip(clip, mat, expected);
360 add_mat(mat, expected);
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);
377 if (kNone_DrawOpType == draw) {
381 for (int i = 0; i < accumulatedClips; ++i) {
382 add_clip(clip, mat, expected);
383 add_clip(clip, mat, expected);
385 add_mat(mat, expected);
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);
401 if (kNone_DrawOpType == draw) {
405 for (int i = 0; i < accumulatedClips; ++i) {
406 add_clip(clip, mat, expected);
407 add_clip(clip, mat, expected);
409 add_mat(mat, expected);
412 //////////////////////////////////////////////////////////////////////////////
415 // matrix & clip calls
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);
423 if (needsSaveRestore) {
424 *expected->append() = SAVE;
426 (*emitMC)(canvas, mat, clip, draw, expected, accumulatedClips+1);
427 emit_draw(canvas, draw, expected);
428 if (needsSaveRestore) {
429 *expected->append() = RESTORE;
434 // matrix & clip calls
436 // matrix & clip calls
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);
444 if (needsSaveRestore) {
445 *expected->append() = SAVE;
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;
453 (*emitMC)(canvas, mat, clip, draw, expected, accumulatedClips+2);
454 emit_draw(canvas, draw, expected);
455 if (needsSaveRestore) {
456 *expected->append() = RESTORE;
461 // matrix & clip calls
463 // matrix & clip calls
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);
472 if (kNone_MatType != mat || kNone_ClipType != clip) {
473 *expected->append() = SAVE;
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(NULL, NULL);
479 if (needsSaveRestore) {
480 *expected->append() = SAVE;
482 (*emitMC)(canvas, mat, clip, draw, expected, 1);
483 emit_draw(canvas, draw, expected);
484 if (needsSaveRestore) {
485 *expected->append() = RESTORE;
488 *expected->append() = RESTORE;
489 if (kNone_MatType != mat || kNone_ClipType != clip) {
490 *expected->append() = RESTORE;
495 // matrix & clip calls
497 // matrix & clip calls
499 // matrix & clip calls
502 // matrix & clip calls (will be ignored)
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);
510 if (kNone_MatType != mat || kNone_ClipType != clip) {
511 *expected->append() = SAVE;
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(NULL, NULL);
517 (*emitMC)(canvas, mat, clip, kSaveLayer_DrawOpType, expected, 1);
518 if (kNone_MatType != mat || kNone_ClipType != clip) {
519 *expected->append() = SAVE;
521 *expected->append() = SAVE_LAYER;
522 // TODO: widen testing to exercise saveLayer's parameters
523 canvas->saveLayer(NULL, NULL);
524 if (needsSaveRestore) {
525 *expected->append() = SAVE;
527 (*emitMC)(canvas, mat, clip, draw, expected, 1);
528 emit_draw(canvas, draw, expected);
529 if (needsSaveRestore) {
530 *expected->append() = RESTORE;
532 canvas->restore(); // for saveLayer
533 *expected->append() = RESTORE; // for saveLayer
534 if (kNone_MatType != mat || kNone_ClipType != clip) {
535 *expected->append() = RESTORE;
538 // required to match forced SAVE_LAYER
539 *expected->append() = RESTORE;
540 if (kNone_MatType != mat || kNone_ClipType != clip) {
541 *expected->append() = RESTORE;
545 //////////////////////////////////////////////////////////////////////////////
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);
561 // matrix & clip calls
565 // matrix & clip calls (will be ignored)
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, NULL, 0); // these get fused into later ops
574 (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 1);
576 (*emitMC)(canvas, mat, clip, draw, NULL, 0); // these will get removed
581 // matrix & clip calls
588 // matrix & clip calls (will be ignored)
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, NULL, 1); // these will get fused into later ops
597 (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 1);
600 (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 1);
602 (*emitMC)(canvas, mat, clip, draw, NULL, 1); // these will get removed
607 // matrix & clip calls
612 // matrix & clip calls
617 // matrix & clip calls (will be ignored)
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, NULL, 0); // these will get fused into later ops
626 (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 1);
629 (*emitMC)(canvas, mat, clip, draw, NULL, 1); // these will get fused into later ops
631 (*emitBody)(canvas, emitMC, mat, clip, draw, expected, 2);
634 (*emitMC)(canvas, mat, clip, draw, NULL, 0); // these will get removed
637 //////////////////////////////////////////////////////////////////////////////
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());
644 for (int i = 0; i < max; ++i) {
645 if (i < expected.count()) {
646 SkDebugf("%16s, ", SkDrawCommand::GetCommandString(expected[i]));
648 SkDebugf("%16s, ", " ");
651 if (i < actual.count()) {
652 SkDebugf("%s\n", SkDrawCommand::GetCommandString(actual[i]));
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 };
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;
680 SkDebugf("test: %d\n", testID);
683 SkTDArray<DrawType> expected, actual;
687 // Note: beginRecording/endRecording add a save/restore pair
688 SkCanvas* canvas = picture.beginRecording(100, 100);
689 (*gStructure[i])(canvas,
696 picture.endRecording();
698 gets_ops(picture, &actual);
700 REPORTER_ASSERT(reporter, expected.count() == actual.count());
702 if (expected.count() != actual.count()) {
703 #ifdef TEST_COLLAPSE_MATRIX_CLIP_STATE
704 print(expected, actual);
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);
726 DEF_TEST(MatrixClipCollapse, reporter) {
727 test_collapse(reporter);