/* * Copyright 2014 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "gm.h" #include "SkColorFilter.h" #include "SkMultiPictureDraw.h" #include "SkPictureRecorder.h" #include "SkSurface.h" static const SkScalar kRoot3Over2 = 0.86602545f; // sin(60) static const SkScalar kRoot3 = 1.73205081f; static const int kHexSide = 30; static const int kNumHexX = 6; static const int kNumHexY = 6; static const int kPicWidth = kNumHexX * kHexSide; static const int kPicHeight = SkScalarCeilToInt((kNumHexY - 0.5f) * 2 * kHexSide * kRoot3Over2); static const SkScalar kInset = 20.0f; static const int kNumPictures = 4; static const int kTriSide = 40; // Create a hexagon centered at (originX, originY) static SkPath make_hex_path(SkScalar originX, SkScalar originY) { SkPath hex; hex.moveTo(originX-kHexSide, originY); hex.rLineTo(SkScalarHalf(kHexSide), kRoot3Over2 * kHexSide); hex.rLineTo(SkIntToScalar(kHexSide), 0); hex.rLineTo(SkScalarHalf(kHexSide), -kHexSide * kRoot3Over2); hex.rLineTo(-SkScalarHalf(kHexSide), -kHexSide * kRoot3Over2); hex.rLineTo(-SkIntToScalar(kHexSide), 0); hex.close(); return hex; } // Make a picture that is a tiling of the plane with stroked hexagons where // each hexagon is in its own layer. The layers are to exercise Ganesh's // layer hoisting. static const SkPicture* make_hex_plane_picture(SkColor fillColor) { // Create a hexagon with its center at the origin SkPath hex = make_hex_path(0, 0); SkPaint fill; fill.setStyle(SkPaint::kFill_Style); fill.setColor(fillColor); SkPaint stroke; stroke.setStyle(SkPaint::kStroke_Style); stroke.setStrokeWidth(3); SkPictureRecorder recorder; SkRTreeFactory bbhFactory; SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth), SkIntToScalar(kPicHeight), &bbhFactory, SkPictureRecorder::kComputeSaveLayerInfo_RecordFlag); SkScalar xPos, yPos = 0; for (int y = 0; y < kNumHexY; ++y) { xPos = 0; for (int x = 0; x < kNumHexX; ++x) { canvas->saveLayer(NULL, NULL); canvas->translate(xPos, yPos + ((x % 2) ? kRoot3Over2 * kHexSide : 0)); canvas->drawPath(hex, fill); canvas->drawPath(hex, stroke); canvas->restore(); xPos += 1.5f * kHexSide; } yPos += 2 * kHexSide * kRoot3Over2; } return recorder.endRecording(); } // Create a picture that consists of a single large layer that is tiled // with hexagons. // This is intended to exercise the layer hoisting code's clip handling (in // tile mode). static const SkPicture* make_single_layer_hex_plane_picture() { // Create a hexagon with its center at the origin SkPath hex = make_hex_path(0, 0); SkPaint whiteFill; whiteFill.setStyle(SkPaint::kFill_Style); whiteFill.setColor(SK_ColorWHITE); SkPaint greyFill; greyFill.setStyle(SkPaint::kFill_Style); greyFill.setColor(SK_ColorLTGRAY); SkPaint stroke; stroke.setStyle(SkPaint::kStroke_Style); stroke.setStrokeWidth(3); SkPictureRecorder recorder; SkRTreeFactory bbhFactory; static const SkScalar kBig = 10000.0f; SkCanvas* canvas = recorder.beginRecording(kBig, kBig, &bbhFactory, SkPictureRecorder::kComputeSaveLayerInfo_RecordFlag); canvas->saveLayer(NULL, NULL); SkScalar xPos = 0.0f, yPos = 0.0f; for (int y = 0; yPos < kBig; ++y) { xPos = 0; for (int x = 0; xPos < kBig; ++x) { canvas->save(); canvas->translate(xPos, yPos + ((x % 2) ? kRoot3Over2 * kHexSide : 0)); // The color of the filled hex is swapped to yield a different // pattern in each tile. This allows an error in layer hoisting (e.g., // the clip isn't blocking cache reuse) to cause a visual discrepancy. canvas->drawPath(hex, ((x+y) % 3) ? whiteFill : greyFill); canvas->drawPath(hex, stroke); canvas->restore(); xPos += 1.5f * kHexSide; } yPos += 2 * kHexSide * kRoot3Over2; } canvas->restore(); return recorder.endRecording(); } // Make an equilateral triangle path with its top corner at (originX, originY) static SkPath make_tri_path(SkScalar originX, SkScalar originY) { SkPath tri; tri.moveTo(originX, originY); tri.rLineTo(SkScalarHalf(kTriSide), 1.5f * kTriSide / kRoot3); tri.rLineTo(-kTriSide, 0); tri.close(); return tri; } static const SkPicture* make_tri_picture() { SkPath tri = make_tri_path(SkScalarHalf(kTriSide), 0); SkPaint fill; fill.setStyle(SkPaint::kFill_Style); fill.setColor(SK_ColorLTGRAY);; SkPaint stroke; stroke.setStyle(SkPaint::kStroke_Style); stroke.setStrokeWidth(3); SkPictureRecorder recorder; SkRTreeFactory bbhFactory; SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth), SkIntToScalar(kPicHeight), &bbhFactory, SkPictureRecorder::kComputeSaveLayerInfo_RecordFlag); SkRect r = tri.getBounds(); r.outset(2.0f, 2.0f); // outset for stroke canvas->clipRect(r); // The saveLayer/restore block is to exercise layer hoisting canvas->saveLayer(NULL, NULL); canvas->drawPath(tri, fill); canvas->drawPath(tri, stroke); canvas->restore(); return recorder.endRecording(); } static const SkPicture* make_sub_picture(const SkPicture* tri) { SkPictureRecorder recorder; SkRTreeFactory bbhFactory; SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth), SkIntToScalar(kPicHeight), &bbhFactory, SkPictureRecorder::kComputeSaveLayerInfo_RecordFlag); canvas->scale(1.0f/2.0f, 1.0f/2.0f); canvas->save(); canvas->translate(SkScalarHalf(kTriSide), 0); canvas->drawPicture(tri); canvas->restore(); canvas->save(); canvas->translate(SkIntToScalar(kTriSide), 1.5f * kTriSide / kRoot3); canvas->drawPicture(tri); canvas->restore(); canvas->save(); canvas->translate(0, 1.5f * kTriSide / kRoot3); canvas->drawPicture(tri); canvas->restore(); return recorder.endRecording(); } // Create a Sierpinkski-like picture that starts with a top row with a picture // that just contains a triangle. Subsequent rows take the prior row's picture, // shrinks it and replicates it 3 times then draws and appropriate number of // copies of it. static const SkPicture* make_sierpinski_picture() { SkAutoTUnref pic(make_tri_picture()); SkPictureRecorder recorder; SkRTreeFactory bbhFactory; SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth), SkIntToScalar(kPicHeight), &bbhFactory, SkPictureRecorder::kComputeSaveLayerInfo_RecordFlag); static const int kNumLevels = 4; for (int i = 0; i < kNumLevels; ++i) { canvas->save(); canvas->translate(kPicWidth/2 - (i+1) * (kTriSide/2.0f), 0.0f); for (int j = 0; j < i+1; ++j) { canvas->drawPicture(pic); canvas->translate(SkIntToScalar(kTriSide), 0); } canvas->restore(); pic.reset(make_sub_picture(pic)); canvas->translate(0, 1.5f * kTriSide / kRoot3); } return recorder.endRecording(); } static SkSurface* create_compat_surface(SkCanvas* canvas, int width, int height) { SkImageInfo info = SkImageInfo::MakeN32Premul(width, height); SkSurface* surface = canvas->newSurface(info); if (NULL == surface) { // picture canvas returns NULL so fall back to raster surface = SkSurface::NewRaster(info); } return surface; } // This class stores the information required to compose all the result // fragments potentially generated by the MultiPictureDraw object class ComposeStep { public: ComposeStep() : fSurf(NULL), fX(0.0f), fY(0.0f), fPaint(NULL) { } ~ComposeStep() { SkSafeUnref(fSurf); SkDELETE(fPaint); } SkSurface* fSurf; SkScalar fX; SkScalar fY; SkPaint* fPaint; }; typedef void (*PFContentMtd)(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]); // Just a single picture with no clip static void no_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) { canvas->drawPicture(pictures[0]); } // Two pictures with a rect clip on the second one static void rect_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) { canvas->drawPicture(pictures[0]); SkRect rect = pictures[0]->cullRect(); rect.inset(kInset, kInset); canvas->clipRect(rect); canvas->drawPicture(pictures[1]); } // Two pictures with a round rect clip on the second one static void rrect_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) { canvas->drawPicture(pictures[0]); SkRect rect = pictures[0]->cullRect(); rect.inset(kInset, kInset); SkRRect rrect; rrect.setRectXY(rect, kInset, kInset); canvas->clipRRect(rrect); canvas->drawPicture(pictures[1]); } // Two pictures with a clip path on the second one static void path_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) { canvas->drawPicture(pictures[0]); // Create a hexagon centered on the middle of the hex grid SkPath hex = make_hex_path((kNumHexX / 2.0f) * kHexSide, kNumHexY * kHexSide * kRoot3Over2); canvas->clipPath(hex); canvas->drawPicture(pictures[1]); } // Two pictures with an inverse clip path on the second one static void invpath_clip(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) { canvas->drawPicture(pictures[0]); // Create a hexagon centered on the middle of the hex grid SkPath hex = make_hex_path((kNumHexX / 2.0f) * kHexSide, kNumHexY * kHexSide * kRoot3Over2); hex.setFillType(SkPath::kInverseEvenOdd_FillType); canvas->clipPath(hex); canvas->drawPicture(pictures[1]); } // Reuse a single base (triangular) picture a _lot_ (rotated, scaled and translated). static void sierpinski(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) { canvas->save(); canvas->drawPicture(pictures[2]); canvas->rotate(180.0f); canvas->translate(-SkIntToScalar(kPicWidth), -SkIntToScalar(kPicHeight)); canvas->drawPicture(pictures[2]); canvas->restore(); } static void big_layer(SkCanvas* canvas, const SkPicture* pictures[kNumPictures]) { canvas->drawPicture(pictures[3]); } static const PFContentMtd gContentMthds[] = { no_clip, rect_clip, rrect_clip, path_clip, invpath_clip, sierpinski, big_layer, }; static void create_content(SkMultiPictureDraw* mpd, PFContentMtd pfGen, const SkPicture* pictures[kNumPictures], SkCanvas* dest, const SkMatrix& xform) { SkAutoTUnref composite; { SkPictureRecorder recorder; SkRTreeFactory bbhFactory; SkCanvas* pictureCanvas = recorder.beginRecording(SkIntToScalar(kPicWidth), SkIntToScalar(kPicHeight), &bbhFactory, SkPictureRecorder::kComputeSaveLayerInfo_RecordFlag); (*pfGen)(pictureCanvas, pictures); composite.reset(recorder.endRecording()); } mpd->add(dest, composite, &xform); } typedef void(*PFLayoutMtd)(SkCanvas* finalCanvas, SkMultiPictureDraw* mpd, PFContentMtd pfGen, const SkPicture* pictures[kNumPictures], SkTArray* composeSteps); // Draw the content into a single canvas static void simple(SkCanvas* finalCanvas, SkMultiPictureDraw* mpd, PFContentMtd pfGen, const SkPicture* pictures[kNumPictures], SkTArray *composeSteps) { ComposeStep& step = composeSteps->push_back(); step.fSurf = create_compat_surface(finalCanvas, kPicWidth, kPicHeight); SkCanvas* subCanvas = step.fSurf->getCanvas(); create_content(mpd, pfGen, pictures, subCanvas, SkMatrix::I()); } // Draw the content into multiple canvases/tiles static void tiled(SkCanvas* finalCanvas, SkMultiPictureDraw* mpd, PFContentMtd pfGen, const SkPicture* pictures[kNumPictures], SkTArray *composeSteps) { static const int kNumTilesX = 2; static const int kNumTilesY = 2; static const int kTileWidth = kPicWidth / kNumTilesX; static const int kTileHeight = kPicHeight / kNumTilesY; SkASSERT(kPicWidth == kNumTilesX * kTileWidth); SkASSERT(kPicHeight == kNumTilesY * kTileHeight); static const SkColor colors[kNumTilesX][kNumTilesY] = { { SK_ColorCYAN, SK_ColorMAGENTA }, { SK_ColorYELLOW, SK_ColorGREEN } }; for (int y = 0; y < kNumTilesY; ++y) { for (int x = 0; x < kNumTilesX; ++x) { ComposeStep& step = composeSteps->push_back(); step.fX = SkIntToScalar(x*kTileWidth); step.fY = SkIntToScalar(y*kTileHeight); step.fPaint = SkNEW(SkPaint); step.fPaint->setColorFilter( SkColorFilter::CreateModeFilter(colors[x][y], SkXfermode::kModulate_Mode))->unref(); step.fSurf = create_compat_surface(finalCanvas, kTileWidth, kTileHeight); SkCanvas* subCanvas = step.fSurf->getCanvas(); SkMatrix trans; trans.setTranslate(-SkIntToScalar(x*kTileWidth), -SkIntToScalar(y*kTileHeight)); create_content(mpd, pfGen, pictures, subCanvas, trans); } } } static const PFLayoutMtd gLayoutMthds[] = { simple, tiled }; namespace skiagm { /** * This GM exercises the SkMultiPictureDraw object. It tests the * cross product of: * tiled vs. all-at-once rendering (e.g., into many or just 1 canvas) * different clips (e.g., none, rect, rrect) * single vs. multiple pictures (e.g., normal vs. picture-pile-style content) */ class MultiPictureDraw : public GM { public: enum Content { kNoClipSingle_Content, kRectClipMulti_Content, kRRectClipMulti_Content, kPathClipMulti_Content, kInvPathClipMulti_Content, kSierpinski_Content, kBigLayer_Content, kLast_Content = kBigLayer_Content }; static const int kContentCnt = kLast_Content + 1; enum Layout { kSimple_Layout, kTiled_Layout, kLast_Layout = kTiled_Layout }; static const int kLayoutCnt = kLast_Layout + 1; MultiPictureDraw(Content content, Layout layout) : fContent(content), fLayout(layout) { SkASSERT(SK_ARRAY_COUNT(gLayoutMthds) == kLayoutCnt); SkASSERT(SK_ARRAY_COUNT(gContentMthds) == kContentCnt); for (int i = 0; i < kNumPictures; ++i) { fPictures[i] = NULL; } } virtual ~MultiPictureDraw() { for (int i = 0; i < kNumPictures; ++i) { SkSafeUnref(fPictures[i]); } } protected: Content fContent; Layout fLayout; const SkPicture* fPictures[kNumPictures]; void onOnceBeforeDraw() SK_OVERRIDE { fPictures[0] = make_hex_plane_picture(SK_ColorWHITE); fPictures[1] = make_hex_plane_picture(SK_ColorGRAY); fPictures[2] = make_sierpinski_picture(); fPictures[3] = make_single_layer_hex_plane_picture(); } void onDraw(SkCanvas* canvas) SK_OVERRIDE { SkMultiPictureDraw mpd; SkTArray composeSteps; // Fill up the MultiPictureDraw (*gLayoutMthds[fLayout])(canvas, &mpd, gContentMthds[fContent], fPictures, &composeSteps); mpd.draw(); // Compose all the drawn canvases into the final canvas for (int i = 0; i < composeSteps.count(); ++i) { const ComposeStep& step = composeSteps[i]; SkAutoTUnref image(step.fSurf->newImageSnapshot()); canvas->drawImage(image, step.fX, step.fY, step.fPaint); } } SkISize onISize() SK_OVERRIDE { return SkISize::Make(kPicWidth, kPicHeight); } SkString onShortName() SK_OVERRIDE { static const char* gContentNames[] = { "noclip", "rectclip", "rrectclip", "pathclip", "invpathclip", "sierpinski", "biglayer" }; static const char* gLayoutNames[] = { "simple", "tiled" }; SkASSERT(SK_ARRAY_COUNT(gLayoutNames) == kLayoutCnt); SkASSERT(SK_ARRAY_COUNT(gContentNames) == kContentCnt); SkString name("multipicturedraw_"); name.append(gContentNames[fContent]); name.append("_"); name.append(gLayoutNames[fLayout]); return name; } uint32_t onGetFlags() const SK_OVERRIDE { return kAsBench_Flag | kSkipTiled_Flag; } private: typedef GM INHERITED; }; DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kNoClipSingle_Content, MultiPictureDraw::kSimple_Layout));) DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kRectClipMulti_Content, MultiPictureDraw::kSimple_Layout));) DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kRRectClipMulti_Content, MultiPictureDraw::kSimple_Layout));) DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kPathClipMulti_Content, MultiPictureDraw::kSimple_Layout));) DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kInvPathClipMulti_Content, MultiPictureDraw::kSimple_Layout));) DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kSierpinski_Content, MultiPictureDraw::kSimple_Layout));) DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kBigLayer_Content, MultiPictureDraw::kSimple_Layout));) DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kNoClipSingle_Content, MultiPictureDraw::kTiled_Layout));) DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kRectClipMulti_Content, MultiPictureDraw::kTiled_Layout));) DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kRRectClipMulti_Content, MultiPictureDraw::kTiled_Layout));) DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kPathClipMulti_Content, MultiPictureDraw::kTiled_Layout));) DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kInvPathClipMulti_Content, MultiPictureDraw::kTiled_Layout));) DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kSierpinski_Content, MultiPictureDraw::kTiled_Layout));) DEF_GM(return SkNEW_ARGS(MultiPictureDraw, (MultiPictureDraw::kBigLayer_Content, MultiPictureDraw::kTiled_Layout));) }