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 "SkColorFilter.h"
11 #include "SkMultiPictureDraw.h"
12 #include "SkPictureRecorder.h"
13 #include "SkSurface.h"
15 static const SkScalar kRoot3Over2 = 0.86602545f; // sin(60)
16 static const SkScalar kRoot3 = 1.73205081f;
18 static const int kHexSide = 30;
19 static const int kNumHexX = 6;
20 static const int kNumHexY = 6;
21 static const int kPicWidth = kNumHexX * kHexSide;
22 static const int kPicHeight = SkScalarCeilToInt((kNumHexY - 0.5f) * 2 * kHexSide * kRoot3Over2);
23 static const SkScalar kInset = 20.0f;
24 static const int kNumPictures = 4;
26 static const int kTriSide = 40;
28 // Create a hexagon centered at (originX, originY)
29 static SkPath make_hex_path(SkScalar originX, SkScalar originY) {
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);
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
44 static const SkPicture* make_hex_plane_picture(SkColor fillColor) {
46 // Create a hexagon with its center at the origin
47 SkPath hex = make_hex_path(0, 0);
50 fill.setStyle(SkPaint::kFill_Style);
51 fill.setColor(fillColor);
54 stroke.setStyle(SkPaint::kStroke_Style);
55 stroke.setStrokeWidth(3);
57 SkPictureRecorder recorder;
58 SkRTreeFactory bbhFactory;
60 SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth),
61 SkIntToScalar(kPicHeight),
63 SkPictureRecorder::kComputeSaveLayerInfo_RecordFlag);
65 SkScalar xPos, yPos = 0;
67 for (int y = 0; y < kNumHexY; ++y) {
70 for (int x = 0; x < kNumHexX; ++x) {
71 canvas->saveLayer(NULL, NULL);
72 canvas->translate(xPos, yPos + ((x % 2) ? kRoot3Over2 * kHexSide : 0));
73 canvas->drawPath(hex, fill);
74 canvas->drawPath(hex, stroke);
77 xPos += 1.5f * kHexSide;
80 yPos += 2 * kHexSide * kRoot3Over2;
83 return recorder.endRecording();
86 // Create a picture that consists of a single large layer that is tiled
88 // This is intended to exercise the layer hoisting code's clip handling (in
90 static const SkPicture* make_single_layer_hex_plane_picture() {
92 // Create a hexagon with its center at the origin
93 SkPath hex = make_hex_path(0, 0);
96 whiteFill.setStyle(SkPaint::kFill_Style);
97 whiteFill.setColor(SK_ColorWHITE);
100 greyFill.setStyle(SkPaint::kFill_Style);
101 greyFill.setColor(SK_ColorLTGRAY);
104 stroke.setStyle(SkPaint::kStroke_Style);
105 stroke.setStrokeWidth(3);
107 SkPictureRecorder recorder;
108 SkRTreeFactory bbhFactory;
110 static const SkScalar kBig = 10000.0f;
111 SkCanvas* canvas = recorder.beginRecording(kBig, kBig, &bbhFactory,
112 SkPictureRecorder::kComputeSaveLayerInfo_RecordFlag);
114 canvas->saveLayer(NULL, NULL);
116 SkScalar xPos = 0.0f, yPos = 0.0f;
118 for (int y = 0; yPos < kBig; ++y) {
121 for (int x = 0; xPos < kBig; ++x) {
123 canvas->translate(xPos, yPos + ((x % 2) ? kRoot3Over2 * kHexSide : 0));
124 // The color of the filled hex is swapped to yield a different
125 // pattern in each tile. This allows an error in layer hoisting (e.g.,
126 // the clip isn't blocking cache reuse) to cause a visual discrepancy.
127 canvas->drawPath(hex, ((x+y) % 3) ? whiteFill : greyFill);
128 canvas->drawPath(hex, stroke);
131 xPos += 1.5f * kHexSide;
134 yPos += 2 * kHexSide * kRoot3Over2;
139 return recorder.endRecording();
142 // Make an equilateral triangle path with its top corner at (originX, originY)
143 static SkPath make_tri_path(SkScalar originX, SkScalar originY) {
145 tri.moveTo(originX, originY);
146 tri.rLineTo(SkScalarHalf(kTriSide), 1.5f * kTriSide / kRoot3);
147 tri.rLineTo(-kTriSide, 0);
152 static const SkPicture* make_tri_picture() {
153 SkPath tri = make_tri_path(SkScalarHalf(kTriSide), 0);
156 fill.setStyle(SkPaint::kFill_Style);
157 fill.setColor(SK_ColorLTGRAY);;
160 stroke.setStyle(SkPaint::kStroke_Style);
161 stroke.setStrokeWidth(3);
163 SkPictureRecorder recorder;
164 SkRTreeFactory bbhFactory;
166 SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth),
167 SkIntToScalar(kPicHeight),
169 SkPictureRecorder::kComputeSaveLayerInfo_RecordFlag);
170 SkRect r = tri.getBounds();
171 r.outset(2.0f, 2.0f); // outset for stroke
173 // The saveLayer/restore block is to exercise layer hoisting
174 canvas->saveLayer(NULL, NULL);
175 canvas->drawPath(tri, fill);
176 canvas->drawPath(tri, stroke);
179 return recorder.endRecording();
182 static const SkPicture* make_sub_picture(const SkPicture* tri) {
183 SkPictureRecorder recorder;
184 SkRTreeFactory bbhFactory;
186 SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth),
187 SkIntToScalar(kPicHeight),
189 SkPictureRecorder::kComputeSaveLayerInfo_RecordFlag);
191 canvas->scale(1.0f/2.0f, 1.0f/2.0f);
194 canvas->translate(SkScalarHalf(kTriSide), 0);
195 canvas->drawPicture(tri);
199 canvas->translate(SkIntToScalar(kTriSide), 1.5f * kTriSide / kRoot3);
200 canvas->drawPicture(tri);
204 canvas->translate(0, 1.5f * kTriSide / kRoot3);
205 canvas->drawPicture(tri);
208 return recorder.endRecording();
211 // Create a Sierpinkski-like picture that starts with a top row with a picture
212 // that just contains a triangle. Subsequent rows take the prior row's picture,
213 // shrinks it and replicates it 3 times then draws and appropriate number of
215 static const SkPicture* make_sierpinski_picture() {
216 SkAutoTUnref<const SkPicture> pic(make_tri_picture());
218 SkPictureRecorder recorder;
219 SkRTreeFactory bbhFactory;
221 SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth),
222 SkIntToScalar(kPicHeight),
224 SkPictureRecorder::kComputeSaveLayerInfo_RecordFlag);
226 static const int kNumLevels = 4;
227 for (int i = 0; i < kNumLevels; ++i) {
229 canvas->translate(kPicWidth/2 - (i+1) * (kTriSide/2.0f), 0.0f);
230 for (int j = 0; j < i+1; ++j) {
231 canvas->drawPicture(pic);
232 canvas->translate(SkIntToScalar(kTriSide), 0);
236 pic.reset(make_sub_picture(pic));
238 canvas->translate(0, 1.5f * kTriSide / kRoot3);
241 return recorder.endRecording();
244 static SkSurface* create_compat_surface(SkCanvas* canvas, int width, int height) {
245 SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
247 SkSurface* surface = canvas->newSurface(info);
248 if (NULL == surface) {
249 // picture canvas returns NULL so fall back to raster
250 surface = SkSurface::NewRaster(info);
256 // This class stores the information required to compose all the result
257 // fragments potentially generated by the MultiPictureDraw object
260 ComposeStep() : fSurf(NULL), fX(0.0f), fY(0.0f), fPaint(NULL) { }
261 ~ComposeStep() { SkSafeUnref(fSurf); SkDELETE(fPaint); }
269 typedef void (*PFContentMtd)(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]);
271 // Just a single picture with no clip
272 static void no_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
273 canvas->drawPicture(pictures[0]);
276 // Two pictures with a rect clip on the second one
277 static void rect_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
278 canvas->drawPicture(pictures[0]);
280 SkRect rect = pictures[0]->cullRect();
281 rect.inset(kInset, kInset);
283 canvas->clipRect(rect);
285 canvas->drawPicture(pictures[1]);
288 // Two pictures with a round rect clip on the second one
289 static void rrect_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
290 canvas->drawPicture(pictures[0]);
292 SkRect rect = pictures[0]->cullRect();
293 rect.inset(kInset, kInset);
296 rrect.setRectXY(rect, kInset, kInset);
298 canvas->clipRRect(rrect);
300 canvas->drawPicture(pictures[1]);
303 // Two pictures with a clip path on the second one
304 static void path_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
305 canvas->drawPicture(pictures[0]);
307 // Create a hexagon centered on the middle of the hex grid
308 SkPath hex = make_hex_path((kNumHexX / 2.0f) * kHexSide, kNumHexY * kHexSide * kRoot3Over2);
310 canvas->clipPath(hex);
312 canvas->drawPicture(pictures[1]);
315 // Two pictures with an inverse clip path on the second one
316 static void invpath_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
317 canvas->drawPicture(pictures[0]);
319 // Create a hexagon centered on the middle of the hex grid
320 SkPath hex = make_hex_path((kNumHexX / 2.0f) * kHexSide, kNumHexY * kHexSide * kRoot3Over2);
321 hex.setFillType(SkPath::kInverseEvenOdd_FillType);
323 canvas->clipPath(hex);
325 canvas->drawPicture(pictures[1]);
328 // Reuse a single base (triangular) picture a _lot_ (rotated, scaled and translated).
329 static void sierpinski(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
331 canvas->drawPicture(pictures[2]);
333 canvas->rotate(180.0f);
334 canvas->translate(-SkIntToScalar(kPicWidth), -SkIntToScalar(kPicHeight));
335 canvas->drawPicture(pictures[2]);
339 static void big_layer(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) {
340 canvas->drawPicture(pictures[3]);
343 static const PFContentMtd gContentMthds[] = {
353 static void create_content(SkMultiPictureDraw* mpd, PFContentMtd pfGen,
354 const SkPicture* pictures[kNumPictures],
355 SkCanvas* dest, const SkMatrix& xform) {
356 SkAutoTUnref<SkPicture> composite;
359 SkPictureRecorder recorder;
360 SkRTreeFactory bbhFactory;
362 SkCanvas* pictureCanvas = recorder.beginRecording(SkIntToScalar(kPicWidth),
363 SkIntToScalar(kPicHeight),
365 SkPictureRecorder::kComputeSaveLayerInfo_RecordFlag);
367 (*pfGen)(pictureCanvas, pictures);
369 composite.reset(recorder.endRecording());
372 mpd->add(dest, composite, &xform);
375 typedef void(*PFLayoutMtd)(SkCanvas* finalCanvas, SkMultiPictureDraw* mpd,
376 PFContentMtd pfGen, const SkPicture* pictures[kNumPictures],
377 SkTArray<ComposeStep>* composeSteps);
379 // Draw the content into a single canvas
380 static void simple(SkCanvas* finalCanvas, SkMultiPictureDraw* mpd,
382 const SkPicture* pictures[kNumPictures],
383 SkTArray<ComposeStep> *composeSteps) {
385 ComposeStep& step = composeSteps->push_back();
387 step.fSurf = create_compat_surface(finalCanvas, kPicWidth, kPicHeight);
389 SkCanvas* subCanvas = step.fSurf->getCanvas();
391 create_content(mpd, pfGen, pictures, subCanvas, SkMatrix::I());
394 // Draw the content into multiple canvases/tiles
395 static void tiled(SkCanvas* finalCanvas, SkMultiPictureDraw* mpd,
397 const SkPicture* pictures[kNumPictures],
398 SkTArray<ComposeStep> *composeSteps) {
399 static const int kNumTilesX = 2;
400 static const int kNumTilesY = 2;
401 static const int kTileWidth = kPicWidth / kNumTilesX;
402 static const int kTileHeight = kPicHeight / kNumTilesY;
404 SkASSERT(kPicWidth == kNumTilesX * kTileWidth);
405 SkASSERT(kPicHeight == kNumTilesY * kTileHeight);
407 static const SkColor colors[kNumTilesX][kNumTilesY] = {
408 { SK_ColorCYAN, SK_ColorMAGENTA },
409 { SK_ColorYELLOW, SK_ColorGREEN }
412 for (int y = 0; y < kNumTilesY; ++y) {
413 for (int x = 0; x < kNumTilesX; ++x) {
414 ComposeStep& step = composeSteps->push_back();
416 step.fX = SkIntToScalar(x*kTileWidth);
417 step.fY = SkIntToScalar(y*kTileHeight);
418 step.fPaint = SkNEW(SkPaint);
419 step.fPaint->setColorFilter(
420 SkColorFilter::CreateModeFilter(colors[x][y], SkXfermode::kModulate_Mode))->unref();
422 step.fSurf = create_compat_surface(finalCanvas, kTileWidth, kTileHeight);
424 SkCanvas* subCanvas = step.fSurf->getCanvas();
427 trans.setTranslate(-SkIntToScalar(x*kTileWidth), -SkIntToScalar(y*kTileHeight));
429 create_content(mpd, pfGen, pictures, subCanvas, trans);
434 static const PFLayoutMtd gLayoutMthds[] = { simple, tiled };
438 * This GM exercises the SkMultiPictureDraw object. It tests the
440 * tiled vs. all-at-once rendering (e.g., into many or just 1 canvas)
441 * different clips (e.g., none, rect, rrect)
442 * single vs. multiple pictures (e.g., normal vs. picture-pile-style content)
444 class MultiPictureDraw : public GM {
447 kNoClipSingle_Content,
448 kRectClipMulti_Content,
449 kRRectClipMulti_Content,
450 kPathClipMulti_Content,
451 kInvPathClipMulti_Content,
455 kLast_Content = kBigLayer_Content
458 static const int kContentCnt = kLast_Content + 1;
464 kLast_Layout = kTiled_Layout
467 static const int kLayoutCnt = kLast_Layout + 1;
469 MultiPictureDraw(Content content, Layout layout) : fContent(content), fLayout(layout) {
470 SkASSERT(SK_ARRAY_COUNT(gLayoutMthds) == kLayoutCnt);
471 SkASSERT(SK_ARRAY_COUNT(gContentMthds) == kContentCnt);
473 for (int i = 0; i < kNumPictures; ++i) {
478 virtual ~MultiPictureDraw() {
479 for (int i = 0; i < kNumPictures; ++i) {
480 SkSafeUnref(fPictures[i]);
487 const SkPicture* fPictures[kNumPictures];
489 void onOnceBeforeDraw() SK_OVERRIDE {
490 fPictures[0] = make_hex_plane_picture(SK_ColorWHITE);
491 fPictures[1] = make_hex_plane_picture(SK_ColorGRAY);
492 fPictures[2] = make_sierpinski_picture();
493 fPictures[3] = make_single_layer_hex_plane_picture();
496 void onDraw(SkCanvas* canvas) SK_OVERRIDE {
497 SkMultiPictureDraw mpd;
498 SkTArray<ComposeStep> composeSteps;
500 // Fill up the MultiPictureDraw
501 (*gLayoutMthds[fLayout])(canvas, &mpd,
502 gContentMthds[fContent],
503 fPictures, &composeSteps);
507 // Compose all the drawn canvases into the final canvas
508 for (int i = 0; i < composeSteps.count(); ++i) {
509 const ComposeStep& step = composeSteps[i];
511 SkAutoTUnref<SkImage> image(step.fSurf->newImageSnapshot());
513 canvas->drawImage(image, step.fX, step.fY, step.fPaint);
517 SkISize onISize() SK_OVERRIDE { return SkISize::Make(kPicWidth, kPicHeight); }
519 SkString onShortName() SK_OVERRIDE {
520 static const char* gContentNames[] = {
521 "noclip", "rectclip", "rrectclip", "pathclip",
522 "invpathclip", "sierpinski", "biglayer"
524 static const char* gLayoutNames[] = { "simple", "tiled" };
526 SkASSERT(SK_ARRAY_COUNT(gLayoutNames) == kLayoutCnt);
527 SkASSERT(SK_ARRAY_COUNT(gContentNames) == kContentCnt);
529 SkString name("multipicturedraw_");
531 name.append(gContentNames[fContent]);
533 name.append(gLayoutNames[fLayout]);
537 bool runAsBench() const SK_OVERRIDE { return true; }
540 typedef GM INHERITED;
543 DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kNoClipSingle_Content,
544 MultiPictureDraw::kSimple_Layout));)
545 DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kRectClipMulti_Content,
546 MultiPictureDraw::kSimple_Layout));)
547 DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kRRectClipMulti_Content,
548 MultiPictureDraw::kSimple_Layout));)
549 DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kPathClipMulti_Content,
550 MultiPictureDraw::kSimple_Layout));)
551 DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kInvPathClipMulti_Content,
552 MultiPictureDraw::kSimple_Layout));)
553 DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kSierpinski_Content,
554 MultiPictureDraw::kSimple_Layout));)
555 DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kBigLayer_Content,
556 MultiPictureDraw::kSimple_Layout));)
558 DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kNoClipSingle_Content,
559 MultiPictureDraw::kTiled_Layout));)
560 DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kRectClipMulti_Content,
561 MultiPictureDraw::kTiled_Layout));)
562 DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kRRectClipMulti_Content,
563 MultiPictureDraw::kTiled_Layout));)
564 DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kPathClipMulti_Content,
565 MultiPictureDraw::kTiled_Layout));)
566 DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kInvPathClipMulti_Content,
567 MultiPictureDraw::kTiled_Layout));)
568 DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kSierpinski_Content,
569 MultiPictureDraw::kTiled_Layout));)
570 DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kBigLayer_Content,
571 MultiPictureDraw::kTiled_Layout));)