| /* |
| * 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 "sk_tool_utils.h" |
| |
| #include "SkColorFilter.h" |
| #include "SkMultiPictureDraw.h" |
| #include "SkPictureRecorder.h" |
| #include "SkSurface.h" |
| |
| constexpr SkScalar kRoot3Over2 = 0.86602545f; // sin(60) |
| constexpr SkScalar kRoot3 = 1.73205081f; |
| |
| constexpr int kHexSide = 30; |
| constexpr int kNumHexX = 6; |
| constexpr int kNumHexY = 6; |
| constexpr int kPicWidth = kNumHexX * kHexSide; |
| constexpr int kPicHeight = (int)((kNumHexY - 0.5f) * 2 * kHexSide * kRoot3Over2 + 0.5f); |
| constexpr SkScalar kInset = 20.0f; |
| constexpr int kNumPictures = 4; |
| |
| constexpr 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 sk_sp<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); |
| |
| SkScalar xPos, yPos = 0; |
| |
| for (int y = 0; y < kNumHexY; ++y) { |
| xPos = 0; |
| |
| for (int x = 0; x < kNumHexX; ++x) { |
| canvas->saveLayer(nullptr, nullptr); |
| 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.finishRecordingAsPicture(); |
| } |
| |
| // 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 sk_sp<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_tool_utils::color_to_565(SK_ColorLTGRAY)); |
| |
| SkPaint stroke; |
| stroke.setStyle(SkPaint::kStroke_Style); |
| stroke.setStrokeWidth(3); |
| |
| SkPictureRecorder recorder; |
| SkRTreeFactory bbhFactory; |
| |
| constexpr SkScalar kBig = 10000.0f; |
| SkCanvas* canvas = recorder.beginRecording(kBig, kBig, &bbhFactory); |
| |
| canvas->saveLayer(nullptr, nullptr); |
| |
| 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.finishRecordingAsPicture(); |
| } |
| |
| // 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 sk_sp<SkPicture> make_tri_picture() { |
| SkPath tri = make_tri_path(SkScalarHalf(kTriSide), 0); |
| |
| SkPaint fill; |
| fill.setStyle(SkPaint::kFill_Style); |
| fill.setColor(sk_tool_utils::color_to_565(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); |
| 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(nullptr, nullptr); |
| canvas->drawPath(tri, fill); |
| canvas->drawPath(tri, stroke); |
| canvas->restore(); |
| |
| return recorder.finishRecordingAsPicture(); |
| } |
| |
| static sk_sp<SkPicture> make_sub_picture(const SkPicture* tri) { |
| SkPictureRecorder recorder; |
| SkRTreeFactory bbhFactory; |
| |
| SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth), |
| SkIntToScalar(kPicHeight), |
| &bbhFactory); |
| |
| 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.finishRecordingAsPicture(); |
| } |
| |
| // 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 sk_sp<SkPicture> make_sierpinski_picture() { |
| sk_sp<SkPicture> pic(make_tri_picture()); |
| |
| SkPictureRecorder recorder; |
| SkRTreeFactory bbhFactory; |
| |
| SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kPicWidth), |
| SkIntToScalar(kPicHeight), |
| &bbhFactory); |
| |
| constexpr 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 = make_sub_picture(pic.get()); |
| |
| canvas->translate(0, 1.5f * kTriSide / kRoot3); |
| } |
| |
| return recorder.finishRecordingAsPicture(); |
| } |
| |
| static sk_sp<SkSurface> create_compat_surface(SkCanvas* canvas, int width, int height) { |
| SkImageInfo info = SkImageInfo::MakeN32Premul(width, height); |
| |
| auto surface = canvas->makeSurface(info); |
| if (nullptr == surface) { |
| // picture canvas returns nullptr so fall back to raster |
| surface = SkSurface::MakeRaster(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() : fX(0.0f), fY(0.0f), fPaint(nullptr) { } |
| ~ComposeStep() { |
| delete fPaint; |
| } |
| |
| sk_sp<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]); |
| } |
| |
| constexpr 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) { |
| sk_sp<SkPicture> composite; |
| |
| { |
| SkPictureRecorder recorder; |
| SkRTreeFactory bbhFactory; |
| |
| SkCanvas* pictureCanvas = recorder.beginRecording(SkIntToScalar(kPicWidth), |
| SkIntToScalar(kPicHeight), |
| &bbhFactory); |
| |
| (*pfGen)(pictureCanvas, pictures); |
| |
| composite = recorder.finishRecordingAsPicture(); |
| } |
| |
| mpd->add(dest, composite.get(), &xform); |
| } |
| |
| typedef void(*PFLayoutMtd)(SkCanvas* finalCanvas, SkMultiPictureDraw* mpd, |
| PFContentMtd pfGen, const SkPicture* pictures[kNumPictures], |
| SkTArray<ComposeStep>* composeSteps); |
| |
| // Draw the content into a single canvas |
| static void simple(SkCanvas* finalCanvas, SkMultiPictureDraw* mpd, |
| PFContentMtd pfGen, |
| const SkPicture* pictures[kNumPictures], |
| SkTArray<ComposeStep> *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<ComposeStep> *composeSteps) { |
| const int kNumTilesX = 2; |
| const int kNumTilesY = 2; |
| const int kTileWidth = kPicWidth / kNumTilesX; |
| const int kTileHeight = kPicHeight / kNumTilesY; |
| |
| SkASSERT(kPicWidth == kNumTilesX * kTileWidth); |
| SkASSERT(kPicHeight == kNumTilesY * kTileHeight); |
| |
| 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 = new SkPaint; |
| step.fPaint->setColorFilter( |
| SkColorFilter::MakeModeFilter(colors[x][y], SkBlendMode::kModulate)); |
| |
| step.fSurf = create_compat_surface(finalCanvas, kTileWidth, kTileHeight); |
| |
| SkCanvas* subCanvas = step.fSurf->getCanvas(); |
| |
| const SkMatrix trans = SkMatrix::MakeTrans(-SkIntToScalar(x*kTileWidth), |
| -SkIntToScalar(y*kTileHeight)); |
| |
| create_content(mpd, pfGen, pictures, subCanvas, trans); |
| } |
| } |
| } |
| |
| constexpr 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 |
| }; |
| |
| const int kContentCnt = kLast_Content + 1; |
| |
| enum Layout { |
| kSimple_Layout, |
| kTiled_Layout, |
| |
| kLast_Layout = kTiled_Layout |
| }; |
| |
| 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] = nullptr; |
| } |
| } |
| |
| ~MultiPictureDraw() override { |
| for (int i = 0; i < kNumPictures; ++i) { |
| SkSafeUnref(fPictures[i]); |
| } |
| } |
| |
| protected: |
| Content fContent; |
| Layout fLayout; |
| const SkPicture* fPictures[kNumPictures]; |
| |
| void onOnceBeforeDraw() override { |
| fPictures[0] = make_hex_plane_picture(SK_ColorWHITE).release(); |
| fPictures[1] = make_hex_plane_picture(sk_tool_utils::color_to_565(SK_ColorGRAY)).release(); |
| fPictures[2] = make_sierpinski_picture().release(); |
| fPictures[3] = make_single_layer_hex_plane_picture().release(); |
| } |
| |
| void onDraw(SkCanvas* canvas) override { |
| SkMultiPictureDraw mpd; |
| SkTArray<ComposeStep> 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]; |
| |
| canvas->drawImage(step.fSurf->makeImageSnapshot().get(), |
| step.fX, step.fY, step.fPaint); |
| } |
| } |
| |
| SkISize onISize() override { return SkISize::Make(kPicWidth, kPicHeight); } |
| |
| SkString onShortName() override { |
| const char* gContentNames[] = { |
| "noclip", "rectclip", "rrectclip", "pathclip", |
| "invpathclip", "sierpinski", "biglayer" |
| }; |
| 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; |
| } |
| |
| bool runAsBench() const override { return true; } |
| |
| private: |
| typedef GM INHERITED; |
| }; |
| |
| DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kNoClipSingle_Content, |
| MultiPictureDraw::kSimple_Layout);) |
| DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kRectClipMulti_Content, |
| MultiPictureDraw::kSimple_Layout);) |
| DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kRRectClipMulti_Content, |
| MultiPictureDraw::kSimple_Layout);) |
| DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kPathClipMulti_Content, |
| MultiPictureDraw::kSimple_Layout);) |
| DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kInvPathClipMulti_Content, |
| MultiPictureDraw::kSimple_Layout);) |
| DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kSierpinski_Content, |
| MultiPictureDraw::kSimple_Layout);) |
| DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kBigLayer_Content, |
| MultiPictureDraw::kSimple_Layout);) |
| |
| DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kNoClipSingle_Content, |
| MultiPictureDraw::kTiled_Layout);) |
| DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kRectClipMulti_Content, |
| MultiPictureDraw::kTiled_Layout);) |
| DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kRRectClipMulti_Content, |
| MultiPictureDraw::kTiled_Layout);) |
| DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kPathClipMulti_Content, |
| MultiPictureDraw::kTiled_Layout);) |
| DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kInvPathClipMulti_Content, |
| MultiPictureDraw::kTiled_Layout);) |
| DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kSierpinski_Content, |
| MultiPictureDraw::kTiled_Layout);) |
| DEF_GM(return new MultiPictureDraw(MultiPictureDraw::kBigLayer_Content, |
| MultiPictureDraw::kTiled_Layout);) |
| } |