PictureTest.cpp revision 7614794c9ad14d76abed6cf00890ad1a09c2cb8b
1/*
2 * Copyright 2012 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 "SkBigPicture.h"
9#include "SkBBoxHierarchy.h"
10#include "SkBlurImageFilter.h"
11#include "SkCanvas.h"
12#include "SkColorMatrixFilter.h"
13#include "SkColorPriv.h"
14#include "SkDashPathEffect.h"
15#include "SkData.h"
16#include "SkImageGenerator.h"
17#include "SkImageEncoder.h"
18#include "SkImageGenerator.h"
19#include "SkMD5.h"
20#include "SkPaint.h"
21#include "SkPicture.h"
22#include "SkPictureAnalyzer.h"
23#include "SkPictureRecorder.h"
24#include "SkPictureUtils.h"
25#include "SkPixelRef.h"
26#include "SkPixelSerializer.h"
27#include "SkMiniRecorder.h"
28#include "SkRRect.h"
29#include "SkRandom.h"
30#include "SkRecord.h"
31#include "SkShader.h"
32#include "SkStream.h"
33#include "sk_tool_utils.h"
34
35#include "Test.h"
36
37#include "SkLumaColorFilter.h"
38#include "SkColorFilterImageFilter.h"
39
40static void make_bm(SkBitmap* bm, int w, int h, SkColor color, bool immutable) {
41    bm->allocN32Pixels(w, h);
42    bm->eraseColor(color);
43    if (immutable) {
44        bm->setImmutable();
45    }
46}
47
48// For a while willPlayBackBitmaps() ignored SkImages and just looked for SkBitmaps.
49static void test_images_are_found_by_willPlayBackBitmaps(skiatest::Reporter* reporter) {
50    // We just need _some_ SkImage
51    const SkPMColor pixel = 0;
52    const SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1);
53    sk_sp<SkImage> image(SkImage::MakeRasterCopy(SkPixmap(info, &pixel, sizeof(pixel))));
54
55    SkPictureRecorder recorder;
56    recorder.beginRecording(100,100)->drawImage(image, 0,0);
57    sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
58
59    REPORTER_ASSERT(reporter, picture->willPlayBackBitmaps());
60}
61
62/* Hit a few SkPicture::Analysis cases not handled elsewhere. */
63static void test_analysis(skiatest::Reporter* reporter) {
64    SkPictureRecorder recorder;
65
66    SkCanvas* canvas = recorder.beginRecording(100, 100);
67    {
68        canvas->drawRect(SkRect::MakeWH(10, 10), SkPaint ());
69    }
70    sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
71    REPORTER_ASSERT(reporter, !picture->willPlayBackBitmaps());
72
73    canvas = recorder.beginRecording(100, 100);
74    {
75        SkPaint paint;
76        // CreateBitmapShader is too smart for us; an empty (or 1x1) bitmap shader
77        // gets optimized into a non-bitmap form, so we create a 2x2 bitmap here.
78        SkBitmap bitmap;
79        bitmap.allocPixels(SkImageInfo::MakeN32Premul(2, 2));
80        bitmap.eraseColor(SK_ColorBLUE);
81        *(bitmap.getAddr32(0, 0)) = SK_ColorGREEN;
82        paint.setShader(SkShader::MakeBitmapShader(bitmap, SkShader::kClamp_TileMode,
83                                                   SkShader::kClamp_TileMode));
84        REPORTER_ASSERT(reporter, paint.getShader()->isAImage());
85
86        canvas->drawRect(SkRect::MakeWH(10, 10), paint);
87    }
88    REPORTER_ASSERT(reporter, recorder.finishRecordingAsPicture()->willPlayBackBitmaps());
89}
90
91
92#ifdef SK_DEBUG
93// Ensure that deleting an empty SkPicture does not assert. Asserts only fire
94// in debug mode, so only run in debug mode.
95static void test_deleting_empty_picture() {
96    SkPictureRecorder recorder;
97    // Creates an SkPictureRecord
98    recorder.beginRecording(0, 0);
99    // Turns that into an SkPicture
100    sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
101    // Ceates a new SkPictureRecord
102    recorder.beginRecording(0, 0);
103}
104
105// Ensure that serializing an empty picture does not assert. Likewise only runs in debug mode.
106static void test_serializing_empty_picture() {
107    SkPictureRecorder recorder;
108    recorder.beginRecording(0, 0);
109    sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
110    SkDynamicMemoryWStream stream;
111    picture->serialize(&stream);
112}
113#endif
114
115static void rand_op(SkCanvas* canvas, SkRandom& rand) {
116    SkPaint paint;
117    SkRect rect = SkRect::MakeWH(50, 50);
118
119    SkScalar unit = rand.nextUScalar1();
120    if (unit <= 0.3) {
121//        SkDebugf("save\n");
122        canvas->save();
123    } else if (unit <= 0.6) {
124//        SkDebugf("restore\n");
125        canvas->restore();
126    } else if (unit <= 0.9) {
127//        SkDebugf("clip\n");
128        canvas->clipRect(rect);
129    } else {
130//        SkDebugf("draw\n");
131        canvas->drawPaint(paint);
132    }
133}
134
135#if SK_SUPPORT_GPU
136
137static SkPath make_convex_path() {
138    SkPath path;
139    path.lineTo(100, 0);
140    path.lineTo(50, 100);
141    path.close();
142
143    return path;
144}
145
146static SkPath make_concave_path() {
147    SkPath path;
148    path.lineTo(50, 50);
149    path.lineTo(100, 0);
150    path.lineTo(50, 100);
151    path.close();
152
153    return path;
154}
155
156static void test_gpu_veto(skiatest::Reporter* reporter) {
157    SkPictureRecorder recorder;
158
159    SkCanvas* canvas = recorder.beginRecording(100, 100);
160    {
161        SkPath path;
162        path.moveTo(0, 0);
163        path.lineTo(50, 50);
164
165        SkScalar intervals[] = { 1.0f, 1.0f };
166        sk_sp<SkPathEffect> dash(SkDashPathEffect::Make(intervals, 2, 0));
167
168        SkPaint paint;
169        paint.setStyle(SkPaint::kStroke_Style);
170        paint.setPathEffect(dash);
171
172        for (int i = 0; i < 50; ++i) {
173            canvas->drawPath(path, paint);
174        }
175    }
176    sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
177    // path effects currently render an SkPicture undesireable for GPU rendering
178
179    const char *reason = nullptr;
180    REPORTER_ASSERT(reporter,
181        !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization(&reason));
182    REPORTER_ASSERT(reporter, reason);
183
184    canvas = recorder.beginRecording(100, 100);
185    {
186        SkPath path;
187
188        path.moveTo(0, 0);
189        path.lineTo(0, 50);
190        path.lineTo(25, 25);
191        path.lineTo(50, 50);
192        path.lineTo(50, 0);
193        path.close();
194        REPORTER_ASSERT(reporter, !path.isConvex());
195
196        SkPaint paint;
197        paint.setAntiAlias(true);
198        for (int i = 0; i < 50; ++i) {
199            canvas->drawPath(path, paint);
200        }
201    }
202    picture = recorder.finishRecordingAsPicture();
203    // A lot of small AA concave paths should be fine for GPU rendering
204    REPORTER_ASSERT(reporter, SkPictureGpuAnalyzer(picture).suitableForGpuRasterization());
205
206    canvas = recorder.beginRecording(100, 100);
207    {
208        SkPath path;
209
210        path.moveTo(0, 0);
211        path.lineTo(0, 100);
212        path.lineTo(50, 50);
213        path.lineTo(100, 100);
214        path.lineTo(100, 0);
215        path.close();
216        REPORTER_ASSERT(reporter, !path.isConvex());
217
218        SkPaint paint;
219        paint.setAntiAlias(true);
220        for (int i = 0; i < 50; ++i) {
221            canvas->drawPath(path, paint);
222        }
223    }
224    picture = recorder.finishRecordingAsPicture();
225    // A lot of large AA concave paths currently render an SkPicture undesireable for GPU rendering
226    REPORTER_ASSERT(reporter, !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization());
227
228    canvas = recorder.beginRecording(100, 100);
229    {
230        SkPath path;
231
232        path.moveTo(0, 0);
233        path.lineTo(0, 50);
234        path.lineTo(25, 25);
235        path.lineTo(50, 50);
236        path.lineTo(50, 0);
237        path.close();
238        REPORTER_ASSERT(reporter, !path.isConvex());
239
240        SkPaint paint;
241        paint.setAntiAlias(true);
242        paint.setStyle(SkPaint::kStroke_Style);
243        paint.setStrokeWidth(0);
244        for (int i = 0; i < 50; ++i) {
245            canvas->drawPath(path, paint);
246        }
247    }
248    picture = recorder.finishRecordingAsPicture();
249    // hairline stroked AA concave paths are fine for GPU rendering
250    REPORTER_ASSERT(reporter, SkPictureGpuAnalyzer(picture).suitableForGpuRasterization());
251
252    canvas = recorder.beginRecording(100, 100);
253    {
254        SkPaint paint;
255        SkScalar intervals [] = { 10, 20 };
256        paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 25));
257
258        SkPoint points [2] = { { 0, 0 }, { 100, 0 } };
259
260        for (int i = 0; i < 50; ++i) {
261            canvas->drawPoints(SkCanvas::kLines_PointMode, 2, points, paint);
262        }
263    }
264    picture = recorder.finishRecordingAsPicture();
265    // fast-path dashed effects are fine for GPU rendering ...
266    REPORTER_ASSERT(reporter, SkPictureGpuAnalyzer(picture).suitableForGpuRasterization());
267
268    canvas = recorder.beginRecording(100, 100);
269    {
270        SkPaint paint;
271        SkScalar intervals [] = { 10, 20 };
272        paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 25));
273
274        for (int i = 0; i < 50; ++i) {
275            canvas->drawRect(SkRect::MakeWH(10, 10), paint);
276        }
277    }
278    picture = recorder.finishRecordingAsPicture();
279    // ... but only when applied to drawPoint() calls
280    REPORTER_ASSERT(reporter, !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization());
281
282    canvas = recorder.beginRecording(100, 100);
283    {
284        const SkPath convexClip = make_convex_path();
285        const SkPath concaveClip = make_concave_path();
286
287        for (int i = 0; i < 50; ++i) {
288            canvas->clipPath(convexClip);
289            canvas->clipPath(concaveClip);
290            canvas->clipPath(convexClip, SkCanvas::kIntersect_Op, true);
291            canvas->drawRect(SkRect::MakeWH(100, 100), SkPaint());
292        }
293    }
294    picture = recorder.finishRecordingAsPicture();
295    // Convex clips and non-AA concave clips are fine on the GPU.
296    REPORTER_ASSERT(reporter, SkPictureGpuAnalyzer(picture).suitableForGpuRasterization());
297
298    canvas = recorder.beginRecording(100, 100);
299    {
300        const SkPath concaveClip = make_concave_path();
301        for (int i = 0; i < 50; ++i) {
302            canvas->clipPath(concaveClip, SkCanvas::kIntersect_Op, true);
303            canvas->drawRect(SkRect::MakeWH(100, 100), SkPaint());
304        }
305    }
306    picture = recorder.finishRecordingAsPicture();
307    // ... but AA concave clips are not.
308    REPORTER_ASSERT(reporter, !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization());
309
310    // Nest the previous picture inside a new one.
311    canvas = recorder.beginRecording(100, 100);
312    {
313        canvas->drawPicture(picture);
314    }
315    picture = recorder.finishRecordingAsPicture();
316    REPORTER_ASSERT(reporter, !SkPictureGpuAnalyzer(picture).suitableForGpuRasterization());
317}
318
319#endif // SK_SUPPORT_GPU
320
321static void set_canvas_to_save_count_4(SkCanvas* canvas) {
322    canvas->restoreToCount(1);
323    canvas->save();
324    canvas->save();
325    canvas->save();
326}
327
328/**
329 * A canvas that records the number of saves, saveLayers and restores.
330 */
331class SaveCountingCanvas : public SkCanvas {
332public:
333    SaveCountingCanvas(int width, int height)
334        : INHERITED(width, height)
335        , fSaveCount(0)
336        , fSaveLayerCount(0)
337        , fRestoreCount(0){
338    }
339
340    SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec& rec) override {
341        ++fSaveLayerCount;
342        return this->INHERITED::getSaveLayerStrategy(rec);
343    }
344
345    void willSave() override {
346        ++fSaveCount;
347        this->INHERITED::willSave();
348    }
349
350    void willRestore() override {
351        ++fRestoreCount;
352        this->INHERITED::willRestore();
353    }
354
355    unsigned int getSaveCount() const { return fSaveCount; }
356    unsigned int getSaveLayerCount() const { return fSaveLayerCount; }
357    unsigned int getRestoreCount() const { return fRestoreCount; }
358
359private:
360    unsigned int fSaveCount;
361    unsigned int fSaveLayerCount;
362    unsigned int fRestoreCount;
363
364    typedef SkCanvas INHERITED;
365};
366
367void check_save_state(skiatest::Reporter* reporter, SkPicture* picture,
368                      unsigned int numSaves, unsigned int numSaveLayers,
369                      unsigned int numRestores) {
370    SaveCountingCanvas canvas(SkScalarCeilToInt(picture->cullRect().width()),
371                              SkScalarCeilToInt(picture->cullRect().height()));
372
373    picture->playback(&canvas);
374
375    // Optimizations may have removed these,
376    // so expect to have seen no more than num{Saves,SaveLayers,Restores}.
377    REPORTER_ASSERT(reporter, numSaves >= canvas.getSaveCount());
378    REPORTER_ASSERT(reporter, numSaveLayers >= canvas.getSaveLayerCount());
379    REPORTER_ASSERT(reporter, numRestores >= canvas.getRestoreCount());
380}
381
382// This class exists so SkPicture can friend it and give it access to
383// the 'partialReplay' method.
384class SkPictureRecorderReplayTester {
385public:
386    static sk_sp<SkPicture> Copy(SkPictureRecorder* recorder) {
387        SkPictureRecorder recorder2;
388
389        SkCanvas* canvas = recorder2.beginRecording(10, 10);
390
391        recorder->partialReplay(canvas);
392
393        return recorder2.finishRecordingAsPicture();
394    }
395};
396
397static void create_imbalance(SkCanvas* canvas) {
398    SkRect clipRect = SkRect::MakeWH(2, 2);
399    SkRect drawRect = SkRect::MakeWH(10, 10);
400    canvas->save();
401        canvas->clipRect(clipRect, SkCanvas::kReplace_Op);
402        canvas->translate(1.0f, 1.0f);
403        SkPaint p;
404        p.setColor(SK_ColorGREEN);
405        canvas->drawRect(drawRect, p);
406    // no restore
407}
408
409// This tests that replaying a potentially unbalanced picture into a canvas
410// doesn't affect the canvas' save count or matrix/clip state.
411static void check_balance(skiatest::Reporter* reporter, SkPicture* picture) {
412    SkBitmap bm;
413    bm.allocN32Pixels(4, 3);
414    SkCanvas canvas(bm);
415
416    int beforeSaveCount = canvas.getSaveCount();
417
418    SkMatrix beforeMatrix = canvas.getTotalMatrix();
419
420    SkRect beforeClip;
421
422    canvas.getClipBounds(&beforeClip);
423
424    canvas.drawPicture(picture);
425
426    REPORTER_ASSERT(reporter, beforeSaveCount == canvas.getSaveCount());
427    REPORTER_ASSERT(reporter, beforeMatrix == canvas.getTotalMatrix());
428
429    SkRect afterClip;
430
431    canvas.getClipBounds(&afterClip);
432
433    REPORTER_ASSERT(reporter, afterClip == beforeClip);
434}
435
436// Test out SkPictureRecorder::partialReplay
437DEF_TEST(PictureRecorder_replay, reporter) {
438    // check save/saveLayer state
439    {
440        SkPictureRecorder recorder;
441
442        SkCanvas* canvas = recorder.beginRecording(10, 10);
443
444        canvas->saveLayer(nullptr, nullptr);
445
446        sk_sp<SkPicture> copy(SkPictureRecorderReplayTester::Copy(&recorder));
447
448        // The extra save and restore comes from the Copy process.
449        check_save_state(reporter, copy.get(), 2, 1, 3);
450
451        canvas->saveLayer(nullptr, nullptr);
452
453        sk_sp<SkPicture> final(recorder.finishRecordingAsPicture());
454
455        check_save_state(reporter, final.get(), 1, 2, 3);
456
457        // The copy shouldn't pick up any operations added after it was made
458        check_save_state(reporter, copy.get(), 2, 1, 3);
459    }
460
461    // (partially) check leakage of draw ops
462    {
463        SkPictureRecorder recorder;
464
465        SkCanvas* canvas = recorder.beginRecording(10, 10);
466
467        SkRect r = SkRect::MakeWH(5, 5);
468        SkPaint p;
469
470        canvas->drawRect(r, p);
471
472        sk_sp<SkPicture> copy(SkPictureRecorderReplayTester::Copy(&recorder));
473
474        REPORTER_ASSERT(reporter, !copy->willPlayBackBitmaps());
475
476        SkBitmap bm;
477        make_bm(&bm, 10, 10, SK_ColorRED, true);
478
479        r.offset(5.0f, 5.0f);
480        canvas->drawBitmapRect(bm, r, nullptr);
481
482        sk_sp<SkPicture> final(recorder.finishRecordingAsPicture());
483        REPORTER_ASSERT(reporter, final->willPlayBackBitmaps());
484
485        REPORTER_ASSERT(reporter, copy->uniqueID() != final->uniqueID());
486
487        // The snapshot shouldn't pick up any operations added after it was made
488        REPORTER_ASSERT(reporter, !copy->willPlayBackBitmaps());
489    }
490
491    // Recreate the Android partialReplay test case
492    {
493        SkPictureRecorder recorder;
494
495        SkCanvas* canvas = recorder.beginRecording(4, 3, nullptr, 0);
496        create_imbalance(canvas);
497
498        int expectedSaveCount = canvas->getSaveCount();
499
500        sk_sp<SkPicture> copy(SkPictureRecorderReplayTester::Copy(&recorder));
501        check_balance(reporter, copy.get());
502
503        REPORTER_ASSERT(reporter, expectedSaveCount = canvas->getSaveCount());
504
505        // End the recording of source to test the picture finalization
506        // process isn't complicated by the partialReplay step
507        sk_sp<SkPicture> final(recorder.finishRecordingAsPicture());
508    }
509}
510
511static void test_unbalanced_save_restores(skiatest::Reporter* reporter) {
512    SkCanvas testCanvas(100, 100);
513    set_canvas_to_save_count_4(&testCanvas);
514
515    REPORTER_ASSERT(reporter, 4 == testCanvas.getSaveCount());
516
517    SkPaint paint;
518    SkRect rect = SkRect::MakeLTRB(-10000000, -10000000, 10000000, 10000000);
519
520    SkPictureRecorder recorder;
521
522    {
523        // Create picture with 2 unbalanced saves
524        SkCanvas* canvas = recorder.beginRecording(100, 100);
525        canvas->save();
526        canvas->translate(10, 10);
527        canvas->drawRect(rect, paint);
528        canvas->save();
529        canvas->translate(10, 10);
530        canvas->drawRect(rect, paint);
531        sk_sp<SkPicture> extraSavePicture(recorder.finishRecordingAsPicture());
532
533        testCanvas.drawPicture(extraSavePicture);
534        REPORTER_ASSERT(reporter, 4 == testCanvas.getSaveCount());
535    }
536
537    set_canvas_to_save_count_4(&testCanvas);
538
539    {
540        // Create picture with 2 unbalanced restores
541        SkCanvas* canvas = recorder.beginRecording(100, 100);
542        canvas->save();
543        canvas->translate(10, 10);
544        canvas->drawRect(rect, paint);
545        canvas->save();
546        canvas->translate(10, 10);
547        canvas->drawRect(rect, paint);
548        canvas->restore();
549        canvas->restore();
550        canvas->restore();
551        canvas->restore();
552        sk_sp<SkPicture> extraRestorePicture(recorder.finishRecordingAsPicture());
553
554        testCanvas.drawPicture(extraRestorePicture);
555        REPORTER_ASSERT(reporter, 4 == testCanvas.getSaveCount());
556    }
557
558    set_canvas_to_save_count_4(&testCanvas);
559
560    {
561        SkCanvas* canvas = recorder.beginRecording(100, 100);
562        canvas->translate(10, 10);
563        canvas->drawRect(rect, paint);
564        sk_sp<SkPicture> noSavePicture(recorder.finishRecordingAsPicture());
565
566        testCanvas.drawPicture(noSavePicture);
567        REPORTER_ASSERT(reporter, 4 == testCanvas.getSaveCount());
568        REPORTER_ASSERT(reporter, testCanvas.getTotalMatrix().isIdentity());
569    }
570}
571
572static void test_peephole() {
573    SkRandom rand;
574
575    SkPictureRecorder recorder;
576
577    for (int j = 0; j < 100; j++) {
578        SkRandom rand2(rand); // remember the seed
579
580        SkCanvas* canvas = recorder.beginRecording(100, 100);
581
582        for (int i = 0; i < 1000; ++i) {
583            rand_op(canvas, rand);
584        }
585        sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
586
587        rand = rand2;
588    }
589
590    {
591        SkCanvas* canvas = recorder.beginRecording(100, 100);
592        SkRect rect = SkRect::MakeWH(50, 50);
593
594        for (int i = 0; i < 100; ++i) {
595            canvas->save();
596        }
597        while (canvas->getSaveCount() > 1) {
598            canvas->clipRect(rect);
599            canvas->restore();
600        }
601        sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
602    }
603}
604
605#ifndef SK_DEBUG
606// Only test this is in release mode. We deliberately crash in debug mode, since a valid caller
607// should never do this.
608static void test_bad_bitmap() {
609    // This bitmap has a width and height but no pixels. As a result, attempting to record it will
610    // fail.
611    SkBitmap bm;
612    bm.setInfo(SkImageInfo::MakeN32Premul(100, 100));
613    SkPictureRecorder recorder;
614    SkCanvas* recordingCanvas = recorder.beginRecording(100, 100);
615    recordingCanvas->drawBitmap(bm, 0, 0);
616    sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
617
618    SkCanvas canvas;
619    canvas.drawPicture(picture);
620}
621#endif
622
623static void test_clip_bound_opt(skiatest::Reporter* reporter) {
624    // Test for crbug.com/229011
625    SkRect rect1 = SkRect::MakeXYWH(SkIntToScalar(4), SkIntToScalar(4),
626                                    SkIntToScalar(2), SkIntToScalar(2));
627    SkRect rect2 = SkRect::MakeXYWH(SkIntToScalar(7), SkIntToScalar(7),
628                                    SkIntToScalar(1), SkIntToScalar(1));
629    SkRect rect3 = SkRect::MakeXYWH(SkIntToScalar(6), SkIntToScalar(6),
630                                    SkIntToScalar(1), SkIntToScalar(1));
631
632    SkPath invPath;
633    invPath.addOval(rect1);
634    invPath.setFillType(SkPath::kInverseEvenOdd_FillType);
635    SkPath path;
636    path.addOval(rect2);
637    SkPath path2;
638    path2.addOval(rect3);
639    SkIRect clipBounds;
640    SkPictureRecorder recorder;
641
642    // Testing conservative-raster-clip that is enabled by PictureRecord
643    {
644        SkCanvas* canvas = recorder.beginRecording(10, 10);
645        canvas->clipPath(invPath);
646        bool nonEmpty = canvas->getClipDeviceBounds(&clipBounds);
647        REPORTER_ASSERT(reporter, true == nonEmpty);
648        REPORTER_ASSERT(reporter, 0 == clipBounds.fLeft);
649        REPORTER_ASSERT(reporter, 0 == clipBounds.fTop);
650        REPORTER_ASSERT(reporter, 10 == clipBounds.fBottom);
651        REPORTER_ASSERT(reporter, 10 == clipBounds.fRight);
652    }
653    {
654        SkCanvas* canvas = recorder.beginRecording(10, 10);
655        canvas->clipPath(path);
656        canvas->clipPath(invPath);
657        bool nonEmpty = canvas->getClipDeviceBounds(&clipBounds);
658        REPORTER_ASSERT(reporter, true == nonEmpty);
659        REPORTER_ASSERT(reporter, 7 == clipBounds.fLeft);
660        REPORTER_ASSERT(reporter, 7 == clipBounds.fTop);
661        REPORTER_ASSERT(reporter, 8 == clipBounds.fBottom);
662        REPORTER_ASSERT(reporter, 8 == clipBounds.fRight);
663    }
664    {
665        SkCanvas* canvas = recorder.beginRecording(10, 10);
666        canvas->clipPath(path);
667        canvas->clipPath(invPath, SkCanvas::kUnion_Op);
668        bool nonEmpty = canvas->getClipDeviceBounds(&clipBounds);
669        REPORTER_ASSERT(reporter, true == nonEmpty);
670        REPORTER_ASSERT(reporter, 0 == clipBounds.fLeft);
671        REPORTER_ASSERT(reporter, 0 == clipBounds.fTop);
672        REPORTER_ASSERT(reporter, 10 == clipBounds.fBottom);
673        REPORTER_ASSERT(reporter, 10 == clipBounds.fRight);
674    }
675    {
676        SkCanvas* canvas = recorder.beginRecording(10, 10);
677        canvas->clipPath(path, SkCanvas::kDifference_Op);
678        bool nonEmpty = canvas->getClipDeviceBounds(&clipBounds);
679        REPORTER_ASSERT(reporter, true == nonEmpty);
680        REPORTER_ASSERT(reporter, 0 == clipBounds.fLeft);
681        REPORTER_ASSERT(reporter, 0 == clipBounds.fTop);
682        REPORTER_ASSERT(reporter, 10 == clipBounds.fBottom);
683        REPORTER_ASSERT(reporter, 10 == clipBounds.fRight);
684    }
685    {
686        SkCanvas* canvas = recorder.beginRecording(10, 10);
687        canvas->clipPath(path, SkCanvas::kReverseDifference_Op);
688        bool nonEmpty = canvas->getClipDeviceBounds(&clipBounds);
689        // True clip is actually empty in this case, but the best
690        // determination we can make using only bounds as input is that the
691        // clip is included in the bounds of 'path'.
692        REPORTER_ASSERT(reporter, true == nonEmpty);
693        REPORTER_ASSERT(reporter, 7 == clipBounds.fLeft);
694        REPORTER_ASSERT(reporter, 7 == clipBounds.fTop);
695        REPORTER_ASSERT(reporter, 8 == clipBounds.fBottom);
696        REPORTER_ASSERT(reporter, 8 == clipBounds.fRight);
697    }
698    {
699        SkCanvas* canvas = recorder.beginRecording(10, 10);
700        canvas->clipPath(path, SkCanvas::kIntersect_Op);
701        canvas->clipPath(path2, SkCanvas::kXOR_Op);
702        bool nonEmpty = canvas->getClipDeviceBounds(&clipBounds);
703        REPORTER_ASSERT(reporter, true == nonEmpty);
704        REPORTER_ASSERT(reporter, 6 == clipBounds.fLeft);
705        REPORTER_ASSERT(reporter, 6 == clipBounds.fTop);
706        REPORTER_ASSERT(reporter, 8 == clipBounds.fBottom);
707        REPORTER_ASSERT(reporter, 8 == clipBounds.fRight);
708    }
709}
710
711static void test_cull_rect_reset(skiatest::Reporter* reporter) {
712    SkPictureRecorder recorder;
713    SkRect bounds = SkRect::MakeWH(10, 10);
714    SkRTreeFactory factory;
715    SkCanvas* canvas = recorder.beginRecording(bounds, &factory);
716    bounds = SkRect::MakeWH(100, 100);
717    SkPaint paint;
718    canvas->drawRect(bounds, paint);
719    canvas->drawRect(bounds, paint);
720    sk_sp<SkPicture> p(recorder.finishRecordingAsPictureWithCull(bounds));
721    const SkBigPicture* picture = p->asSkBigPicture();
722    REPORTER_ASSERT(reporter, picture);
723
724    SkRect finalCullRect = picture->cullRect();
725    REPORTER_ASSERT(reporter, 0 == finalCullRect.fLeft);
726    REPORTER_ASSERT(reporter, 0 == finalCullRect.fTop);
727    REPORTER_ASSERT(reporter, 100 == finalCullRect.fBottom);
728    REPORTER_ASSERT(reporter, 100 == finalCullRect.fRight);
729
730    const SkBBoxHierarchy* pictureBBH = picture->bbh();
731    SkRect bbhCullRect = pictureBBH->getRootBound();
732    REPORTER_ASSERT(reporter, 0 == bbhCullRect.fLeft);
733    REPORTER_ASSERT(reporter, 0 == bbhCullRect.fTop);
734    REPORTER_ASSERT(reporter, 100 == bbhCullRect.fBottom);
735    REPORTER_ASSERT(reporter, 100 == bbhCullRect.fRight);
736}
737
738
739/**
740 * A canvas that records the number of clip commands.
741 */
742class ClipCountingCanvas : public SkCanvas {
743public:
744    ClipCountingCanvas(int width, int height)
745        : INHERITED(width, height)
746        , fClipCount(0){
747    }
748
749    void onClipRect(const SkRect& r, ClipOp op, ClipEdgeStyle edgeStyle) override {
750        fClipCount += 1;
751        this->INHERITED::onClipRect(r, op, edgeStyle);
752    }
753
754    void onClipRRect(const SkRRect& rrect, ClipOp op, ClipEdgeStyle edgeStyle)override {
755        fClipCount += 1;
756        this->INHERITED::onClipRRect(rrect, op, edgeStyle);
757    }
758
759    void onClipPath(const SkPath& path, ClipOp op, ClipEdgeStyle edgeStyle) override {
760        fClipCount += 1;
761        this->INHERITED::onClipPath(path, op, edgeStyle);
762    }
763
764    void onClipRegion(const SkRegion& deviceRgn, ClipOp op) override {
765        fClipCount += 1;
766        this->INHERITED::onClipRegion(deviceRgn, op);
767    }
768
769    unsigned getClipCount() const { return fClipCount; }
770
771private:
772    unsigned fClipCount;
773
774    typedef SkCanvas INHERITED;
775};
776
777static void test_clip_expansion(skiatest::Reporter* reporter) {
778    SkPictureRecorder recorder;
779    SkCanvas* canvas = recorder.beginRecording(10, 10);
780
781    canvas->clipRect(SkRect::MakeEmpty(), SkCanvas::kReplace_Op);
782    // The following expanding clip should not be skipped.
783    canvas->clipRect(SkRect::MakeXYWH(4, 4, 3, 3), SkCanvas::kUnion_Op);
784    // Draw something so the optimizer doesn't just fold the world.
785    SkPaint p;
786    p.setColor(SK_ColorBLUE);
787    canvas->drawPaint(p);
788    sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
789
790    ClipCountingCanvas testCanvas(10, 10);
791    picture->playback(&testCanvas);
792
793    // Both clips should be present on playback.
794    REPORTER_ASSERT(reporter, testCanvas.getClipCount() == 2);
795}
796
797static void test_hierarchical(skiatest::Reporter* reporter) {
798    SkBitmap bm;
799    make_bm(&bm, 10, 10, SK_ColorRED, true);
800
801    SkPictureRecorder recorder;
802
803    recorder.beginRecording(10, 10);
804    sk_sp<SkPicture> childPlain(recorder.finishRecordingAsPicture());
805    REPORTER_ASSERT(reporter, !childPlain->willPlayBackBitmaps()); // 0
806
807    recorder.beginRecording(10, 10)->drawBitmap(bm, 0, 0);
808    sk_sp<SkPicture> childWithBitmap(recorder.finishRecordingAsPicture());
809    REPORTER_ASSERT(reporter, childWithBitmap->willPlayBackBitmaps()); // 1
810
811    {
812        SkCanvas* canvas = recorder.beginRecording(10, 10);
813        canvas->drawPicture(childPlain);
814        sk_sp<SkPicture> parentPP(recorder.finishRecordingAsPicture());
815        REPORTER_ASSERT(reporter, !parentPP->willPlayBackBitmaps()); // 0
816    }
817    {
818        SkCanvas* canvas = recorder.beginRecording(10, 10);
819        canvas->drawPicture(childWithBitmap);
820        sk_sp<SkPicture> parentPWB(recorder.finishRecordingAsPicture());
821        REPORTER_ASSERT(reporter, parentPWB->willPlayBackBitmaps()); // 1
822    }
823    {
824        SkCanvas* canvas = recorder.beginRecording(10, 10);
825        canvas->drawBitmap(bm, 0, 0);
826        canvas->drawPicture(childPlain);
827        sk_sp<SkPicture> parentWBP(recorder.finishRecordingAsPicture());
828        REPORTER_ASSERT(reporter, parentWBP->willPlayBackBitmaps()); // 1
829    }
830    {
831        SkCanvas* canvas = recorder.beginRecording(10, 10);
832        canvas->drawBitmap(bm, 0, 0);
833        canvas->drawPicture(childWithBitmap);
834        sk_sp<SkPicture> parentWBWB(recorder.finishRecordingAsPicture());
835        REPORTER_ASSERT(reporter, parentWBWB->willPlayBackBitmaps()); // 2
836    }
837}
838
839static void test_gen_id(skiatest::Reporter* reporter) {
840
841    SkPictureRecorder recorder;
842    recorder.beginRecording(0, 0);
843    sk_sp<SkPicture> empty(recorder.finishRecordingAsPicture());
844
845    // Empty pictures should still have a valid ID
846    REPORTER_ASSERT(reporter, empty->uniqueID() != SK_InvalidGenID);
847
848    SkCanvas* canvas = recorder.beginRecording(1, 1);
849    canvas->drawARGB(255, 255, 255, 255);
850    sk_sp<SkPicture> hasData(recorder.finishRecordingAsPicture());
851    // picture should have a non-zero id after recording
852    REPORTER_ASSERT(reporter, hasData->uniqueID() != SK_InvalidGenID);
853
854    // both pictures should have different ids
855    REPORTER_ASSERT(reporter, hasData->uniqueID() != empty->uniqueID());
856}
857
858static void test_typeface(skiatest::Reporter* reporter) {
859    SkPictureRecorder recorder;
860    SkCanvas* canvas = recorder.beginRecording(10, 10);
861    SkPaint paint;
862    paint.setTypeface(SkTypeface::MakeFromName("Arial",
863                                               SkFontStyle::FromOldStyle(SkTypeface::kItalic)));
864    canvas->drawText("Q", 1, 0, 10, paint);
865    sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
866    SkDynamicMemoryWStream stream;
867    picture->serialize(&stream);
868}
869
870DEF_TEST(Picture, reporter) {
871    test_typeface(reporter);
872#ifdef SK_DEBUG
873    test_deleting_empty_picture();
874    test_serializing_empty_picture();
875#else
876    test_bad_bitmap();
877#endif
878    test_unbalanced_save_restores(reporter);
879    test_peephole();
880#if SK_SUPPORT_GPU
881    test_gpu_veto(reporter);
882#endif
883    test_images_are_found_by_willPlayBackBitmaps(reporter);
884    test_analysis(reporter);
885    test_clip_bound_opt(reporter);
886    test_clip_expansion(reporter);
887    test_hierarchical(reporter);
888    test_gen_id(reporter);
889    test_cull_rect_reset(reporter);
890}
891
892static void draw_bitmaps(const SkBitmap bitmap, SkCanvas* canvas) {
893    const SkPaint paint;
894    const SkRect rect = { 5.0f, 5.0f, 8.0f, 8.0f };
895    const SkIRect irect =  { 2, 2, 3, 3 };
896    int divs[] = { 2, 3 };
897    SkCanvas::Lattice lattice;
898    lattice.fXCount = lattice.fYCount = 2;
899    lattice.fXDivs = lattice.fYDivs = divs;
900
901    // Don't care what these record, as long as they're legal.
902    canvas->drawBitmap(bitmap, 0.0f, 0.0f, &paint);
903    canvas->drawBitmapRect(bitmap, rect, rect, &paint, SkCanvas::kStrict_SrcRectConstraint);
904    canvas->drawBitmapNine(bitmap, irect, rect, &paint);
905    canvas->drawBitmap(bitmap, 1, 1);   // drawSprite
906    canvas->drawBitmapLattice(bitmap, lattice, rect, &paint);
907}
908
909static void test_draw_bitmaps(SkCanvas* canvas) {
910    SkBitmap empty;
911    draw_bitmaps(empty, canvas);
912    empty.setInfo(SkImageInfo::MakeN32Premul(10, 10));
913    draw_bitmaps(empty, canvas);
914}
915
916DEF_TEST(Picture_EmptyBitmap, r) {
917    SkPictureRecorder recorder;
918    test_draw_bitmaps(recorder.beginRecording(10, 10));
919    sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
920}
921
922DEF_TEST(Canvas_EmptyBitmap, r) {
923    SkBitmap dst;
924    dst.allocN32Pixels(10, 10);
925    SkCanvas canvas(dst);
926
927    test_draw_bitmaps(&canvas);
928}
929
930DEF_TEST(DontOptimizeSaveLayerDrawDrawRestore, reporter) {
931    // This test is from crbug.com/344987.
932    // The commands are:
933    //   saveLayer with paint that modifies alpha
934    //     drawBitmapRect
935    //     drawBitmapRect
936    //   restore
937    // The bug was that this structure was modified so that:
938    //  - The saveLayer and restore were eliminated
939    //  - The alpha was only applied to the first drawBitmapRectToRect
940
941    // This test draws blue and red squares inside a 50% transparent
942    // layer.  Both colours should show up muted.
943    // When the bug is present, the red square (the second bitmap)
944    // shows upwith full opacity.
945
946    SkBitmap blueBM;
947    make_bm(&blueBM, 100, 100, SkColorSetARGB(255, 0, 0, 255), true);
948    SkBitmap redBM;
949    make_bm(&redBM, 100, 100, SkColorSetARGB(255, 255, 0, 0), true);
950    SkPaint semiTransparent;
951    semiTransparent.setAlpha(0x80);
952
953    SkPictureRecorder recorder;
954    SkCanvas* canvas = recorder.beginRecording(100, 100);
955    canvas->drawARGB(0, 0, 0, 0);
956
957    canvas->saveLayer(0, &semiTransparent);
958    canvas->drawBitmap(blueBM, 25, 25);
959    canvas->drawBitmap(redBM, 50, 50);
960    canvas->restore();
961
962    sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
963
964    // Now replay the picture back on another canvas
965    // and check a couple of its pixels.
966    SkBitmap replayBM;
967    make_bm(&replayBM, 100, 100, SK_ColorBLACK, false);
968    SkCanvas replayCanvas(replayBM);
969    picture->playback(&replayCanvas);
970    replayCanvas.flush();
971
972    // With the bug present, at (55, 55) we would get a fully opaque red
973    // intead of a dark red.
974    REPORTER_ASSERT(reporter, replayBM.getColor(30, 30) == 0xff000080);
975    REPORTER_ASSERT(reporter, replayBM.getColor(55, 55) == 0xff800000);
976}
977
978struct CountingBBH : public SkBBoxHierarchy {
979    mutable int searchCalls;
980    SkRect rootBound;
981
982    CountingBBH(const SkRect& bound) : searchCalls(0), rootBound(bound) {}
983
984    void search(const SkRect& query, SkTDArray<int>* results) const override {
985        this->searchCalls++;
986    }
987
988    void insert(const SkRect[], int) override {}
989    virtual size_t bytesUsed() const override { return 0; }
990    SkRect getRootBound() const override { return rootBound; }
991};
992
993class SpoonFedBBHFactory : public SkBBHFactory {
994public:
995    explicit SpoonFedBBHFactory(SkBBoxHierarchy* bbh) : fBBH(bbh) {}
996    SkBBoxHierarchy* operator()(const SkRect&) const override {
997        return SkRef(fBBH);
998    }
999private:
1000    SkBBoxHierarchy* fBBH;
1001};
1002
1003// When the canvas clip covers the full picture, we don't need to call the BBH.
1004DEF_TEST(Picture_SkipBBH, r) {
1005    SkRect bound = SkRect::MakeWH(320, 240);
1006    CountingBBH bbh(bound);
1007    SpoonFedBBHFactory factory(&bbh);
1008
1009    SkPictureRecorder recorder;
1010    SkCanvas* c = recorder.beginRecording(bound, &factory);
1011    // Record a few ops so we don't hit a small- or empty- picture optimization.
1012        c->drawRect(bound, SkPaint());
1013        c->drawRect(bound, SkPaint());
1014    sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
1015
1016    SkCanvas big(640, 480), small(300, 200);
1017
1018    picture->playback(&big);
1019    REPORTER_ASSERT(r, bbh.searchCalls == 0);
1020
1021    picture->playback(&small);
1022    REPORTER_ASSERT(r, bbh.searchCalls == 1);
1023}
1024
1025DEF_TEST(Picture_BitmapLeak, r) {
1026    SkBitmap mut, immut;
1027    mut.allocN32Pixels(300, 200);
1028    immut.allocN32Pixels(300, 200);
1029    immut.setImmutable();
1030    SkASSERT(!mut.isImmutable());
1031    SkASSERT(immut.isImmutable());
1032
1033    // No one can hold a ref on our pixels yet.
1034    REPORTER_ASSERT(r, mut.pixelRef()->unique());
1035    REPORTER_ASSERT(r, immut.pixelRef()->unique());
1036
1037    sk_sp<SkPicture> pic;
1038    {
1039        // we want the recorder to go out of scope before our subsequent checks, so we
1040        // place it inside local braces.
1041        SkPictureRecorder rec;
1042        SkCanvas* canvas = rec.beginRecording(1920, 1200);
1043            canvas->drawBitmap(mut, 0, 0);
1044            canvas->drawBitmap(immut, 800, 600);
1045        pic = rec.finishRecordingAsPicture();
1046    }
1047
1048    // The picture shares the immutable pixels but copies the mutable ones.
1049    REPORTER_ASSERT(r, mut.pixelRef()->unique());
1050    REPORTER_ASSERT(r, !immut.pixelRef()->unique());
1051
1052    // When the picture goes away, it's just our bitmaps holding the refs.
1053    pic = nullptr;
1054    REPORTER_ASSERT(r, mut.pixelRef()->unique());
1055    REPORTER_ASSERT(r, immut.pixelRef()->unique());
1056}
1057
1058// getRecordingCanvas() should return a SkCanvas when recording, null when not recording.
1059DEF_TEST(Picture_getRecordingCanvas, r) {
1060    SkPictureRecorder rec;
1061    REPORTER_ASSERT(r, !rec.getRecordingCanvas());
1062    for (int i = 0; i < 3; i++) {
1063        rec.beginRecording(100, 100);
1064        REPORTER_ASSERT(r, rec.getRecordingCanvas());
1065        rec.finishRecordingAsPicture();
1066        REPORTER_ASSERT(r, !rec.getRecordingCanvas());
1067    }
1068}
1069
1070DEF_TEST(MiniRecorderLeftHanging, r) {
1071    // Any shader or other ref-counted effect will do just fine here.
1072    SkPaint paint;
1073    paint.setShader(SkShader::MakeColorShader(SK_ColorRED));
1074
1075    SkMiniRecorder rec;
1076    REPORTER_ASSERT(r, rec.drawRect(SkRect::MakeWH(20,30), paint));
1077    // Don't call rec.detachPicture().  Test succeeds by not asserting or leaking the shader.
1078}
1079
1080DEF_TEST(Picture_preserveCullRect, r) {
1081    SkPictureRecorder recorder;
1082
1083    SkCanvas* c = recorder.beginRecording(SkRect::MakeLTRB(1, 2, 3, 4));
1084    c->clear(SK_ColorCYAN);
1085
1086    sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
1087    SkDynamicMemoryWStream wstream;
1088    picture->serialize(&wstream);
1089
1090    SkAutoTDelete<SkStream> rstream(wstream.detachAsStream());
1091    sk_sp<SkPicture> deserializedPicture(SkPicture::MakeFromStream(rstream));
1092
1093    REPORTER_ASSERT(r, deserializedPicture != nullptr);
1094    REPORTER_ASSERT(r, deserializedPicture->cullRect().left() == 1);
1095    REPORTER_ASSERT(r, deserializedPicture->cullRect().top() == 2);
1096    REPORTER_ASSERT(r, deserializedPicture->cullRect().right() == 3);
1097    REPORTER_ASSERT(r, deserializedPicture->cullRect().bottom() == 4);
1098}
1099
1100#if SK_SUPPORT_GPU
1101
1102DEF_TEST(PictureGpuAnalyzer, r) {
1103    SkPictureRecorder recorder;
1104
1105    {
1106        SkCanvas* canvas = recorder.beginRecording(10, 10);
1107        SkPaint paint;
1108        SkScalar intervals [] = { 10, 20 };
1109        paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 25));
1110
1111        for (int i = 0; i < 50; ++i) {
1112            canvas->drawRect(SkRect::MakeWH(10, 10), paint);
1113        }
1114    }
1115    sk_sp<SkPicture> vetoPicture(recorder.finishRecordingAsPicture());
1116
1117    SkPictureGpuAnalyzer analyzer;
1118    REPORTER_ASSERT(r, analyzer.suitableForGpuRasterization());
1119
1120    analyzer.analyzePicture(vetoPicture.get());
1121    REPORTER_ASSERT(r, !analyzer.suitableForGpuRasterization());
1122
1123    analyzer.reset();
1124    REPORTER_ASSERT(r, analyzer.suitableForGpuRasterization());
1125
1126    recorder.beginRecording(10, 10)->drawPicture(vetoPicture);
1127    sk_sp<SkPicture> nestedVetoPicture(recorder.finishRecordingAsPicture());
1128
1129    analyzer.analyzePicture(nestedVetoPicture.get());
1130    REPORTER_ASSERT(r, !analyzer.suitableForGpuRasterization());
1131
1132    analyzer.reset();
1133
1134    const SkPath convexClip = make_convex_path();
1135    const SkPath concaveClip = make_concave_path();
1136    for (int i = 0; i < 50; ++i) {
1137        analyzer.analyzeClipPath(convexClip, SkCanvas::kIntersect_Op, false);
1138        analyzer.analyzeClipPath(convexClip, SkCanvas::kIntersect_Op, true);
1139        analyzer.analyzeClipPath(concaveClip, SkCanvas::kIntersect_Op, false);
1140    }
1141    REPORTER_ASSERT(r, analyzer.suitableForGpuRasterization());
1142
1143    for (int i = 0; i < 50; ++i) {
1144        analyzer.analyzeClipPath(concaveClip, SkCanvas::kIntersect_Op, true);
1145    }
1146    REPORTER_ASSERT(r, !analyzer.suitableForGpuRasterization());
1147}
1148
1149#endif // SK_SUPPORT_GPU
1150
1151///////////////////////////////////////////////////////////////////////////////////////////////////
1152
1153// Disable until we properly fix https://bugs.chromium.org/p/skia/issues/detail?id=5548
1154#if 0
1155static void empty_ops(SkCanvas* canvas) {
1156}
1157static void clip_ops(SkCanvas* canvas) {
1158    canvas->save();
1159    canvas->clipRect(SkRect::MakeWH(20, 20));
1160    canvas->restore();
1161}
1162static void matrix_ops(SkCanvas* canvas) {
1163    canvas->save();
1164    canvas->scale(2, 3);
1165    canvas->restore();
1166}
1167static void matrixclip_ops(SkCanvas* canvas) {
1168    canvas->save();
1169    canvas->scale(2, 3);
1170    canvas->clipRect(SkRect::MakeWH(20, 20));
1171    canvas->restore();
1172}
1173typedef void (*CanvasProc)(SkCanvas*);
1174
1175// Test the kReturnNullForEmpty_FinishFlag option when recording
1176//
1177DEF_TEST(Picture_RecordEmpty, r) {
1178    const SkRect cull = SkRect::MakeWH(100, 100);
1179
1180    CanvasProc procs[] { empty_ops, clip_ops, matrix_ops, matrixclip_ops };
1181
1182    for (auto proc : procs) {
1183        {
1184            SkPictureRecorder rec;
1185            proc(rec.beginRecording(cull));
1186            sk_sp<SkPicture> pic = rec.finishRecordingAsPicture(0);
1187            REPORTER_ASSERT(r, pic.get());
1188            REPORTER_ASSERT(r, pic->approximateOpCount() == 0);
1189        }
1190        {
1191            SkPictureRecorder rec;
1192            proc(rec.beginRecording(cull));
1193            sk_sp<SkPicture> pic = rec.finishRecordingAsPicture(
1194                                                 SkPictureRecorder::kReturnNullForEmpty_FinishFlag);
1195            REPORTER_ASSERT(r, !pic.get());
1196        }
1197        {
1198            SkPictureRecorder rec;
1199            proc(rec.beginRecording(cull));
1200            sk_sp<SkDrawable> dr = rec.finishRecordingAsDrawable(0);
1201            REPORTER_ASSERT(r, dr.get());
1202        }
1203        {
1204            SkPictureRecorder rec;
1205            proc(rec.beginRecording(cull));
1206            sk_sp<SkDrawable> dr = rec.finishRecordingAsDrawable(
1207                                                 SkPictureRecorder::kReturnNullForEmpty_FinishFlag);
1208            REPORTER_ASSERT(r, !dr.get());
1209        }
1210    }
1211}
1212#endif
1213
1214