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