multipicturedraw.cpp revision b1fc64b8faa17a1d7fc7140dc18906b639fbd27b
1/*
2 * Copyright 2014 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "gm.h"
9
10#include "SkColorFilter.h"
11#include "SkMultiPictureDraw.h"
12#include "SkPictureRecorder.h"
13#include "SkSurface.h"
14
15static const SkScalar kRoot3Over2 = 0.86602545f;  // sin(60)
16static const SkScalar kRoot3      = 1.73205081f;
17
18static const int kHexSide = 30;
19static const int kNumHexX = 6;
20static const int kNumHexY = 6;
21static const int kPicWidth = kNumHexX * kHexSide;
22static const int kPicHeight = SkScalarCeilToInt((kNumHexY - 0.5f) * 2 * kHexSide * kRoot3Over2);
23static const SkScalar kInset = 20.0f;
24static const int kNumPictures = 4;
25
26static const int kTriSide = 40;
27
28// Create a hexagon centered at (originX, originY)
29static SkPath make_hex_path(SkScalar originX, SkScalar originY) {
30    SkPath hex;
31    hex.moveTo(originX-kHexSide, originY);
32    hex.rLineTo(SkScalarHalf(kHexSide), kRoot3Over2 * kHexSide);
33    hex.rLineTo(SkIntToScalar(kHexSide), 0);
34    hex.rLineTo(SkScalarHalf(kHexSide), -kHexSide * kRoot3Over2);
35    hex.rLineTo(-SkScalarHalf(kHexSide), -kHexSide * kRoot3Over2);
36    hex.rLineTo(-SkIntToScalar(kHexSide), 0);
37    hex.close();
38    return hex;
39}
40
41// Make a picture that is a tiling of the plane with stroked hexagons where
42// each hexagon is in its own layer. The layers are to exercise Ganesh's
43// layer hoisting.
44static const SkPicture* make_hex_plane_picture(SkColor fillColor) {
45
46    // Create a hexagon with its center at the origin
47    SkPath hex = make_hex_path(0, 0);
48
49    SkPaint fill;
50    fill.setStyle(SkPaint::kFill_Style);
51    fill.setColor(fillColor);
52
53    SkPaint stroke;
54    stroke.setStyle(SkPaint::kStroke_Style);
55    stroke.setStrokeWidth(3);
56
57    SkPictureRecorder recorder;
58
59    SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth),
60                                               SkIntToScalar(kPicHeight));
61
62    SkScalar xPos, yPos = 0;
63
64    for (int y = 0; y < kNumHexY; ++y) {
65        xPos = 0;
66
67        for (int x = 0; x < kNumHexX; ++x) {
68            canvas->saveLayer(NULL, NULL);
69            canvas->translate(xPos, yPos + ((x % 2) ? kRoot3Over2 * kHexSide : 0));
70            canvas->drawPath(hex, fill);
71            canvas->drawPath(hex, stroke);
72            canvas->restore();
73
74            xPos += 1.5f * kHexSide;
75        }
76
77        yPos += 2 * kHexSide * kRoot3Over2;
78    }
79
80    return recorder.endRecording();
81}
82
83// Create a picture that consists of a single large layer that is tiled
84// with hexagons.
85// This is intended to exercise the layer hoisting code's clip handling (in
86// tile mode).
87static const SkPicture* make_single_layer_hex_plane_picture() {
88
89    // Create a hexagon with its center at the origin
90    SkPath hex = make_hex_path(0, 0);
91
92    SkPaint whiteFill;
93    whiteFill.setStyle(SkPaint::kFill_Style);
94    whiteFill.setColor(SK_ColorWHITE);
95
96    SkPaint greyFill;
97    greyFill.setStyle(SkPaint::kFill_Style);
98    greyFill.setColor(SK_ColorLTGRAY);
99
100    SkPaint stroke;
101    stroke.setStyle(SkPaint::kStroke_Style);
102    stroke.setStrokeWidth(3);
103
104    SkPictureRecorder recorder;
105
106    static const SkScalar kBig = 10000.0f;
107    SkCanvas* canvas = recorder.beginRecording(kBig, kBig);
108
109    SkScalar xPos = 0.0f, yPos = 0.0f;
110
111    for (int y = 0; yPos < kBig; ++y) {
112        xPos = 0;
113
114        for (int x = 0; xPos < kBig; ++x) {
115            canvas->save();
116            canvas->translate(xPos, yPos + ((x % 2) ? kRoot3Over2 * kHexSide : 0));
117            // The color of the filled hex is swapped to yield a different
118            // pattern in each tile. This allows an error in layer hoisting (e.g.,
119            // the clip isn't blocking cache reuse) to cause a visual discrepancy.
120            canvas->drawPath(hex, ((x+y) % 3) ? whiteFill : greyFill);
121            canvas->drawPath(hex, stroke);
122            canvas->restore();
123
124            xPos += 1.5f * kHexSide;
125        }
126
127        yPos += 2 * kHexSide * kRoot3Over2;
128    }
129
130    return recorder.endRecording();
131}
132
133// Make an equilateral triangle path with its top corner at (originX, originY)
134static SkPath make_tri_path(SkScalar originX, SkScalar originY) {
135    SkPath tri;
136    tri.moveTo(originX, originY);
137    tri.rLineTo(SkScalarHalf(kTriSide), 1.5f * kTriSide / kRoot3);
138    tri.rLineTo(-kTriSide, 0);
139    tri.close();
140    return tri;
141}
142
143static const SkPicture* make_tri_picture() {
144    SkPath tri = make_tri_path(SkScalarHalf(kTriSide), 0);
145
146    SkPaint fill;
147    fill.setStyle(SkPaint::kFill_Style);
148    fill.setColor(SK_ColorLTGRAY);;
149
150    SkPaint stroke;
151    stroke.setStyle(SkPaint::kStroke_Style);
152    stroke.setStrokeWidth(3);
153
154    SkPictureRecorder recorder;
155
156    SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth),
157                                               SkIntToScalar(kPicHeight));
158    // The saveLayer/restore block is to exercise layer hoisting
159    canvas->saveLayer(NULL, NULL);
160        canvas->drawPath(tri, fill);
161        canvas->drawPath(tri, stroke);
162    canvas->restore();
163
164    return recorder.endRecording();
165}
166
167static const SkPicture* make_sub_picture(const SkPicture* tri) {
168    SkPictureRecorder recorder;
169
170    SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth),
171                                               SkIntToScalar(kPicHeight));
172
173    canvas->scale(1.0f/2.0f, 1.0f/2.0f);
174
175    canvas->save();
176    canvas->translate(SkScalarHalf(kTriSide), 0);
177    canvas->drawPicture(tri);
178    canvas->restore();
179
180    canvas->save();
181    canvas->translate(SkIntToScalar(kTriSide), 1.5f * kTriSide / kRoot3);
182    canvas->drawPicture(tri);
183    canvas->restore();
184
185    canvas->save();
186    canvas->translate(0, 1.5f * kTriSide / kRoot3);
187    canvas->drawPicture(tri);
188    canvas->restore();
189
190    return recorder.endRecording();
191}
192
193// Create a Sierpinkski-like picture that starts with a top row with a picture
194// that just contains a triangle. Subsequent rows take the prior row's picture,
195// shrinks it and replicates it 3 times then draws and appropriate number of
196// copies of it.
197static const SkPicture* make_sierpinski_picture() {
198    SkAutoTUnref<const SkPicture> pic(make_tri_picture());
199
200    SkPictureRecorder recorder;
201
202    SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth),
203                                               SkIntToScalar(kPicHeight));
204
205    static const int kNumLevels = 4;
206    for (int i = 0; i < kNumLevels; ++i) {
207        canvas->save();
208            canvas->translate(kPicWidth/2 - (i+1) * (kTriSide/2.0f), 0.0f);
209            for (int j = 0; j < i+1; ++j) {
210                canvas->drawPicture(pic);
211                canvas->translate(SkIntToScalar(kTriSide), 0);
212            }
213        canvas->restore();
214
215        pic.reset(make_sub_picture(pic));
216
217        canvas->translate(0, 1.5f * kTriSide / kRoot3);
218    }
219
220    return recorder.endRecording();
221}
222
223static SkSurface* create_compat_surface(SkCanvas* canvas, int width, int height) {
224    SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
225
226    SkSurface* surface = canvas->newSurface(info);
227    if (NULL == surface) {
228        // picture canvas returns NULL so fall back to raster
229        surface = SkSurface::NewRaster(info);
230    }
231
232    return surface;
233}
234
235// This class stores the information required to compose all the result
236// fragments potentially generated by the MultiPictureDraw object
237class ComposeStep {
238public:
239    ComposeStep() : fSurf(NULL), fX(0.0f), fY(0.0f), fPaint(NULL) { }
240    ~ComposeStep() { SkSafeUnref(fSurf);  SkDELETE(fPaint); }
241
242    SkSurface* fSurf;
243    SkScalar   fX;
244    SkScalar   fY;
245    SkPaint*   fPaint;
246};
247
248typedef void (*PFContentMtd)(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]);
249
250// Just a single picture with no clip
251static void no_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
252    canvas->drawPicture(pictures[0]);
253}
254
255// Two pictures with a rect clip on the second one
256static void rect_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
257    canvas->drawPicture(pictures[0]);
258
259    SkRect rect = pictures[0]->cullRect();
260    rect.inset(kInset, kInset);
261
262    canvas->clipRect(rect);
263
264    canvas->drawPicture(pictures[1]);
265}
266
267// Two pictures with a round rect clip on the second one
268static void rrect_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
269    canvas->drawPicture(pictures[0]);
270
271    SkRect rect = pictures[0]->cullRect();
272    rect.inset(kInset, kInset);
273
274    SkRRect rrect;
275    rrect.setRectXY(rect, kInset, kInset);
276
277    canvas->clipRRect(rrect);
278
279    canvas->drawPicture(pictures[1]);
280}
281
282// Two pictures with a clip path on the second one
283static void path_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
284    canvas->drawPicture(pictures[0]);
285
286    // Create a hexagon centered on the middle of the hex grid
287    SkPath hex = make_hex_path((kNumHexX / 2.0f) * kHexSide, kNumHexY * kHexSide * kRoot3Over2);
288
289    canvas->clipPath(hex);
290
291    canvas->drawPicture(pictures[1]);
292}
293
294// Two pictures with an inverse clip path on the second one
295static void invpath_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
296    canvas->drawPicture(pictures[0]);
297
298    // Create a hexagon centered on the middle of the hex grid
299    SkPath hex = make_hex_path((kNumHexX / 2.0f) * kHexSide, kNumHexY * kHexSide * kRoot3Over2);
300    hex.setFillType(SkPath::kInverseEvenOdd_FillType);
301
302    canvas->clipPath(hex);
303
304    canvas->drawPicture(pictures[1]);
305}
306
307// Reuse a single base (triangular) picture a _lot_ (rotated, scaled and translated).
308static void sierpinski(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
309    canvas->save();
310        canvas->drawPicture(pictures[2]);
311
312        canvas->rotate(180.0f);
313        canvas->translate(-SkIntToScalar(kPicWidth), -SkIntToScalar(kPicHeight));
314        canvas->drawPicture(pictures[2]);
315    canvas->restore();
316}
317
318static void big_layer(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
319    canvas->drawPicture(pictures[3]);
320}
321
322static const PFContentMtd gContentMthds[] = {
323    no_clip,
324    rect_clip,
325    rrect_clip,
326    path_clip,
327    invpath_clip,
328    sierpinski,
329    big_layer,
330};
331
332static void create_content(SkMultiPictureDraw* mpd, PFContentMtd pfGen,
333                           const SkPicture* pictures[kNumPictures],
334                           SkCanvas* dest, const SkMatrix& xform) {
335    SkAutoTUnref<SkPicture> composite;
336
337    {
338        SkPictureRecorder recorder;
339
340        SkCanvas* pictureCanvas = recorder.beginRecording(SkIntToScalar(kPicWidth),
341                                                          SkIntToScalar(kPicHeight));
342
343        (*pfGen)(pictureCanvas, pictures);
344
345        composite.reset(recorder.endRecording());
346    }
347
348    mpd->add(dest, composite, &xform);
349}
350
351typedef void(*PFLayoutMtd)(SkCanvas* finalCanvas, SkMultiPictureDraw* mpd,
352                           PFContentMtd pfGen, const SkPicture* pictures[kNumPictures],
353                           SkTArray<ComposeStep>* composeSteps);
354
355// Draw the content into a single canvas
356static void simple(SkCanvas* finalCanvas, SkMultiPictureDraw* mpd,
357                   PFContentMtd pfGen,
358                   const SkPicture* pictures[kNumPictures],
359                   SkTArray<ComposeStep> *composeSteps) {
360
361    ComposeStep& step = composeSteps->push_back();
362
363    step.fSurf = create_compat_surface(finalCanvas, kPicWidth, kPicHeight);
364
365    SkCanvas* subCanvas = step.fSurf->getCanvas();
366
367    create_content(mpd, pfGen, pictures, subCanvas, SkMatrix::I());
368}
369
370// Draw the content into multiple canvases/tiles
371static void tiled(SkCanvas* finalCanvas, SkMultiPictureDraw* mpd,
372                  PFContentMtd pfGen,
373                  const SkPicture* pictures[kNumPictures],
374                  SkTArray<ComposeStep> *composeSteps) {
375    static const int kNumTilesX = 2;
376    static const int kNumTilesY = 2;
377    static const int kTileWidth = kPicWidth / kNumTilesX;
378    static const int kTileHeight = kPicHeight / kNumTilesY;
379
380    SkASSERT(kPicWidth == kNumTilesX * kTileWidth);
381    SkASSERT(kPicHeight == kNumTilesY * kTileHeight);
382
383    static const SkColor colors[kNumTilesX][kNumTilesY] = {
384        { SK_ColorCYAN,   SK_ColorMAGENTA },
385        { SK_ColorYELLOW, SK_ColorGREEN   }
386    };
387
388    for (int y = 0; y < kNumTilesY; ++y) {
389        for (int x = 0; x < kNumTilesX; ++x) {
390            ComposeStep& step = composeSteps->push_back();
391
392            step.fX = SkIntToScalar(x*kTileWidth);
393            step.fY = SkIntToScalar(y*kTileHeight);
394            step.fPaint = SkNEW(SkPaint);
395            step.fPaint->setColorFilter(
396                SkColorFilter::CreateModeFilter(colors[x][y], SkXfermode::kModulate_Mode))->unref();
397
398            step.fSurf = create_compat_surface(finalCanvas, kTileWidth, kTileHeight);
399
400            SkCanvas* subCanvas = step.fSurf->getCanvas();
401
402            SkMatrix trans;
403            trans.setTranslate(-SkIntToScalar(x*kTileWidth), -SkIntToScalar(y*kTileHeight));
404
405            create_content(mpd, pfGen, pictures, subCanvas, trans);
406        }
407    }
408}
409
410static const PFLayoutMtd gLayoutMthds[] = { simple, tiled };
411
412namespace skiagm {
413    /**
414     * This GM exercises the SkMultiPictureDraw object. It tests the
415     * cross product of:
416     *      tiled vs. all-at-once rendering (e.g., into many or just 1 canvas)
417     *      different clips (e.g., none, rect, rrect)
418     *      single vs. multiple pictures (e.g., normal vs. picture-pile-style content)
419     */
420    class MultiPictureDraw : public GM {
421    public:
422        enum Content {
423            kNoClipSingle_Content,
424            kRectClipMulti_Content,
425            kRRectClipMulti_Content,
426            kPathClipMulti_Content,
427            kInvPathClipMulti_Content,
428            kSierpinski_Content,
429            kBigLayer_Content,
430
431            kLast_Content = kBigLayer_Content
432        };
433
434        static const int kContentCnt = kLast_Content + 1;
435
436        enum Layout {
437            kSimple_Layout,
438            kTiled_Layout,
439
440            kLast_Layout = kTiled_Layout
441        };
442
443        static const int kLayoutCnt = kLast_Layout + 1;
444
445        MultiPictureDraw(Content content, Layout layout) : fContent(content), fLayout(layout) {
446            SkASSERT(SK_ARRAY_COUNT(gLayoutMthds) == kLayoutCnt);
447            SkASSERT(SK_ARRAY_COUNT(gContentMthds) == kContentCnt);
448
449            for (int i = 0; i < kNumPictures; ++i) {
450                fPictures[i] = NULL;
451            }
452        }
453
454        virtual ~MultiPictureDraw() {
455            for (int i = 0; i < kNumPictures; ++i) {
456                SkSafeUnref(fPictures[i]);
457            }
458        }
459
460    protected:
461        Content          fContent;
462        Layout           fLayout;
463        const SkPicture* fPictures[kNumPictures];
464
465        virtual void onOnceBeforeDraw() SK_OVERRIDE {
466            fPictures[0] = make_hex_plane_picture(SK_ColorWHITE);
467            fPictures[1] = make_hex_plane_picture(SK_ColorGRAY);
468            fPictures[2] = make_sierpinski_picture();
469            fPictures[3] = make_single_layer_hex_plane_picture();
470        }
471
472        virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE {
473            SkMultiPictureDraw mpd;
474            SkTArray<ComposeStep> composeSteps;
475
476            // Fill up the MultiPictureDraw
477            (*gLayoutMthds[fLayout])(canvas, &mpd,
478                                     gContentMthds[fContent],
479                                     fPictures, &composeSteps);
480
481            mpd.draw();
482
483            // Compose all the drawn canvases into the final canvas
484            for (int i = 0; i < composeSteps.count(); ++i) {
485                const ComposeStep& step = composeSteps[i];
486
487                SkAutoTUnref<SkImage> image(step.fSurf->newImageSnapshot());
488
489                canvas->drawImage(image, step.fX, step.fY, step.fPaint);
490            }
491        }
492
493        virtual SkISize onISize() SK_OVERRIDE { return SkISize::Make(kPicWidth, kPicHeight); }
494
495        virtual SkString onShortName() SK_OVERRIDE {
496            static const char* gContentNames[] = {
497                "noclip", "rectclip", "rrectclip", "pathclip",
498                "invpathclip", "sierpinski", "biglayer"
499            };
500            static const char* gLayoutNames[] = { "simple", "tiled" };
501
502            SkASSERT(SK_ARRAY_COUNT(gLayoutNames) == kLayoutCnt);
503            SkASSERT(SK_ARRAY_COUNT(gContentNames) == kContentCnt);
504
505            SkString name("multipicturedraw_");
506
507            name.append(gContentNames[fContent]);
508            name.append("_");
509            name.append(gLayoutNames[fLayout]);
510            return name;
511        }
512
513        virtual uint32_t onGetFlags() const SK_OVERRIDE { return kAsBench_Flag | kSkipTiled_Flag; }
514
515    private:
516        typedef GM INHERITED;
517    };
518
519    DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kNoClipSingle_Content,
520                                                MultiPictureDraw::kSimple_Layout));)
521    DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kRectClipMulti_Content,
522                                                MultiPictureDraw::kSimple_Layout));)
523    DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kRRectClipMulti_Content,
524                                                MultiPictureDraw::kSimple_Layout));)
525    DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kPathClipMulti_Content,
526                                                MultiPictureDraw::kSimple_Layout));)
527    DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kInvPathClipMulti_Content,
528                                                MultiPictureDraw::kSimple_Layout));)
529    DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kSierpinski_Content,
530                                                MultiPictureDraw::kSimple_Layout));)
531    DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kBigLayer_Content,
532                                                MultiPictureDraw::kSimple_Layout));)
533
534    DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kNoClipSingle_Content,
535                                                MultiPictureDraw::kTiled_Layout));)
536    DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kRectClipMulti_Content,
537                                                MultiPictureDraw::kTiled_Layout));)
538    DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kRRectClipMulti_Content,
539                                                MultiPictureDraw::kTiled_Layout));)
540    DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kPathClipMulti_Content,
541                                                MultiPictureDraw::kTiled_Layout));)
542    DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kInvPathClipMulti_Content,
543                                                MultiPictureDraw::kTiled_Layout));)
544    DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kSierpinski_Content,
545                                                MultiPictureDraw::kTiled_Layout));)
546    DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kBigLayer_Content,
547                                                MultiPictureDraw::kTiled_Layout));)
548}
549