Add bench and gm for shapes

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1677253002

patch from issue 1686673002 at patchset 20001 (http://crrev.com/1686673002#ps20001)

Review URL: https://codereview.chromium.org/1677253002
diff --git a/bench/ShapesBench.cpp b/bench/ShapesBench.cpp
new file mode 100644
index 0000000..1a3702f
--- /dev/null
+++ b/bench/ShapesBench.cpp
@@ -0,0 +1,289 @@
+
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "Benchmark.h"
+#include "SkCanvas.h"
+#include "SkCommandLineFlags.h"
+#include "SkPaint.h"
+#include "SkRandom.h"
+#include "SkRRect.h"
+#include "SkString.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <functional>
+
+#define ENABLE_COMMAND_LINE_SHAPES_BENCH 0
+
+#if ENABLE_COMMAND_LINE_SHAPES_BENCH
+DEFINE_string(shapesType, "mixed", "Type of shape to use in ShapesBench. Must be one of: "
+                                   "rect, oval, rrect, mixed.");
+DEFINE_string(innerShapesType, "none", "Type of inner shape to use in ShapesBench. Must be one of: "
+                                       "none, rect, oval, rrect, mixed.");
+DEFINE_int32(numShapes, 10000, "Number of shapes to draw in ShapesBench.");
+DEFINE_string(shapesSize, "32x32", "Size of shapes to draw in ShapesBench.");
+DEFINE_bool(shapesPersp, false, "Use slight perspective tilt in ShapesBench?");
+#endif
+
+/*
+ * This class is used for several benchmarks that draw different primitive Skia shapes at various
+ * sizes. It is used to test both CPU-bound and GPU-bound rendering situations. It draws large
+ * amounts of shapes internally (rather than relying on nanobench selecting lots of loops) in order
+ * to take advantage of instanced rendering approaches.
+ */
+class ShapesBench : public Benchmark {
+public:
+    enum ShapesType {
+        kNone_ShapesType,
+        kRect_ShapesType,
+        kOval_ShapesType,
+        kRRect_ShapesType,
+        kMixed_ShapesType
+    };
+
+    ShapesBench(ShapesType shapesType, ShapesType innerShapesType,
+                int numShapes, const SkISize& shapesSize, bool perspective)
+        : fShapesType(shapesType)
+        , fInnerShapesType(innerShapesType)
+        , fNumShapes(numShapes)
+        , fShapesSize(shapesSize)
+        , fPerspective(perspective) {
+        clampShapeSize();
+    }
+
+#if ENABLE_COMMAND_LINE_SHAPES_BENCH
+    ShapesBench() {
+        if (!strcmp(FLAGS_shapesType[0], "rect")) {
+            fShapesType = kRect_ShapesType;
+        } else if (!strcmp(FLAGS_shapesType[0], "oval")) {
+            fShapesType = kOval_ShapesType;
+        } else if (!strcmp(FLAGS_shapesType[0], "rrect")) {
+            fShapesType = kRRect_ShapesType;
+        } else if (!strcmp(FLAGS_shapesType[0], "mixed")) {
+            fShapesType = kMixed_ShapesType;
+        } else {
+            SkDebugf("Invalid shapesType \"%s\". Must be one of: rect, oval, rrect, mixed.",
+                     FLAGS_shapesType[0]);
+            exit(-1);
+        }
+        if (!strcmp(FLAGS_innerShapesType[0], "none")) {
+            fInnerShapesType = kNone_ShapesType;
+        } else if (!strcmp(FLAGS_innerShapesType[0], "rect")) {
+            fInnerShapesType = kRect_ShapesType;
+        } else if (!strcmp(FLAGS_innerShapesType[0], "oval")) {
+            fInnerShapesType = kOval_ShapesType;
+        } else if (!strcmp(FLAGS_innerShapesType[0], "rrect")) {
+            fInnerShapesType = kRRect_ShapesType;
+        } else if (!strcmp(FLAGS_innerShapesType[0], "mixed")) {
+            fInnerShapesType = kMixed_ShapesType;
+        } else {
+            SkDebugf("Invalid innerShapesType \"%s\". Must be one of: "
+                     "none, rect, oval, rrect, mixed.", FLAGS_innerShapesType[0]);
+            exit(-1);
+        }
+        if (2 != sscanf(FLAGS_shapesSize[0], "%ix%i", &fShapesSize.fWidth, &fShapesSize.fHeight)) {
+            SkDebugf("Could not parse shapesSize from \"%s\". Expected \"%%ix%%i\"\n",
+                     FLAGS_shapesSize[0]);
+            exit(-1);
+        }
+
+        fNumShapes = FLAGS_numShapes;
+        fPerspective = FLAGS_shapesPersp;
+
+        clampShapeSize();
+    }
+#endif
+
+    bool isVisual() override { return true; }
+
+private:
+    void clampShapeSize() {
+        float maxDiagonal = static_cast<float>(SkTMin(kBenchWidth, kBenchHeight));
+        float diagonal = sqrtf(static_cast<float>(fShapesSize.width() * fShapesSize.width()) +
+                               static_cast<float>(fShapesSize.height() * fShapesSize.height()));
+        if (diagonal > maxDiagonal) {
+            fShapesSize.fWidth = static_cast<int>(fShapesSize.width() * maxDiagonal / diagonal);
+            fShapesSize.fHeight = static_cast<int>(fShapesSize.height() * maxDiagonal / diagonal);
+        }
+    }
+
+    const char* onGetName() override {
+        const char* shapeTypeNames[] = {
+            "none", "rect", "oval", "rrect", "mixed"
+        };
+
+        fName.printf("shapes_%s", shapeTypeNames[fShapesType]);
+
+        if (kNone_ShapesType != fInnerShapesType) {
+            fName.appendf("_inner_%s", shapeTypeNames[fInnerShapesType]);
+        }
+
+        fName.appendf("_%i_%ix%i", fNumShapes, fShapesSize.width(), fShapesSize.height());
+
+        if (fPerspective) {
+            fName.append("_persp");
+        }
+
+        return fName.c_str();
+    }
+    SkIPoint onGetSize() override { return SkIPoint::Make(kBenchWidth, kBenchHeight); }
+
+    void onDelayedSetup() override {
+        SkScalar w = SkIntToScalar(fShapesSize.width());
+        SkScalar h = SkIntToScalar(fShapesSize.height());
+
+        fRect.setRect(SkRect::MakeXYWH(-w / 2, -h / 2, w, h));
+        fOval.setOval(fRect.rect());
+        fRRect.setNinePatch(fRect.rect(), w / 8, h / 13, w / 11, h / 7);
+
+        if (kNone_ShapesType != fInnerShapesType) {
+            fRect.inset(w / 7, h / 11, &fInnerRect);
+            fInnerRect.offset(w / 28, h / 44);
+            fInnerOval.setOval(fInnerRect.rect());
+            fInnerRRect.setRectXY(fInnerRect.rect(), w / 13, w / 7);
+        }
+
+        SkRandom rand;
+        fShapes.push_back_n(fNumShapes);
+        for (int i = 0; i < fNumShapes; i++) {
+            float pad = sqrtf(static_cast<float>(fShapesSize.width() * fShapesSize.width()) +
+                              static_cast<float>(fShapesSize.height() * fShapesSize.height()));
+            fShapes[i].fMatrix.setTranslate(0.5f * pad + rand.nextF() * (kBenchWidth - pad),
+                                            0.5f * pad + rand.nextF() * (kBenchHeight - pad));
+            fShapes[i].fMatrix.preRotate(rand.nextF() * 360.0f);
+            if (fPerspective) {
+                fShapes[i].fMatrix.setPerspX(0.00015f);
+                fShapes[i].fMatrix.setPerspY(-0.00015f);
+            }
+            fShapes[i].fColor = rand.nextU() | 0xff808080;
+        }
+        for (int i = 0; i < fNumShapes; i++) {
+            // Do this in a separate loop so mixed shapes get the same random numbers during
+            // placement as non-mixed do.
+            int shapeType = fShapesType;
+            if (kMixed_ShapesType == shapeType) {
+                shapeType = rand.nextRangeU(kRect_ShapesType, kRRect_ShapesType);
+            }
+            int innerShapeType = fInnerShapesType;
+            if (kMixed_ShapesType == innerShapeType) {
+                innerShapeType = rand.nextRangeU(kRect_ShapesType, kRRect_ShapesType);
+            }
+            if (kNone_ShapesType == innerShapeType) {
+                switch (shapeType) {
+                    using namespace std;
+                    using namespace std::placeholders;
+                    case kRect_ShapesType:
+                        fShapes[i].fDraw = bind(&SkCanvas::drawRect, _1, cref(fRect.rect()), _2);
+                        break;
+                    case kOval_ShapesType:
+                        fShapes[i].fDraw = bind(&SkCanvas::drawOval, _1, cref(fOval.rect()), _2);
+                        break;
+                    case kRRect_ShapesType:
+                        fShapes[i].fDraw = bind(&SkCanvas::drawRRect, _1, cref(fRRect), _2);
+                        break;
+                }
+            } else {
+                const SkRRect* outer;
+                switch (shapeType) {
+                    case kRect_ShapesType: outer = &fRect; break;
+                    case kOval_ShapesType: outer = &fOval; break;
+                    case kRRect_ShapesType: outer = &fRRect; break;
+                }
+                const SkRRect* inner;
+                switch (innerShapeType) {
+                    case kRect_ShapesType: inner = &fInnerRect; break;
+                    case kOval_ShapesType: inner = &fInnerOval; break;
+                    case kRRect_ShapesType: inner = &fInnerRRect; break;
+                }
+                fShapes[i].fDraw = std::bind(&SkCanvas::drawDRRect, std::placeholders::_1,
+                                             std::cref(*outer), std::cref(*inner),
+                                             std::placeholders::_2);
+            }
+        }
+    }
+
+    void onDraw(int loops, SkCanvas* canvas) override {
+        SkPaint paint;
+        this->setupPaint(&paint);
+        for (int j = 0; j < loops; j++) {
+            for (int i = 0; i < fNumShapes; i++) {
+                canvas->save();
+                canvas->setMatrix(fShapes[i].fMatrix);
+                paint.setColor(fShapes[i].fColor);
+                fShapes[i].fDraw(canvas, paint);
+                canvas->restore();
+            }
+        }
+    }
+
+    enum {
+        kBenchWidth = 1000,
+        kBenchHeight = 1000
+    };
+
+    struct ShapeInfo {
+        SkMatrix   fMatrix;
+        SkColor    fColor;
+        std::function<void(SkCanvas*, const SkPaint&)> fDraw;
+    };
+
+    ShapesType            fShapesType;
+    ShapesType            fInnerShapesType;
+    int                   fNumShapes;
+    SkISize               fShapesSize;
+    bool                  fPerspective;
+    SkString              fName;
+    SkRRect               fRect;
+    SkRRect               fOval;
+    SkRRect               fRRect;
+    SkRRect               fInnerRect;
+    SkRRect               fInnerOval;
+    SkRRect               fInnerRRect;
+    SkTArray<ShapeInfo>   fShapes;
+
+
+    typedef Benchmark INHERITED;
+};
+
+// Small primitives (CPU bound, in theory):
+DEF_BENCH(return new ShapesBench(ShapesBench::kRect_ShapesType, ShapesBench::kNone_ShapesType,
+                                 10000, SkISize::Make(32, 32), false);)
+DEF_BENCH(return new ShapesBench(ShapesBench::kOval_ShapesType, ShapesBench::kNone_ShapesType,
+                                 10000, SkISize::Make(32, 32), false);)
+DEF_BENCH(return new ShapesBench(ShapesBench::kOval_ShapesType, ShapesBench::kNone_ShapesType,
+                                 10000, SkISize::Make(32, 33), false);)
+DEF_BENCH(return new ShapesBench(ShapesBench::kRRect_ShapesType, ShapesBench::kNone_ShapesType,
+                                 10000, SkISize::Make(32, 32), false);)
+DEF_BENCH(return new ShapesBench(ShapesBench::kMixed_ShapesType, ShapesBench::kNone_ShapesType,
+                                 10000, SkISize::Make(32, 33), false);)
+
+// Large primitives (GPU bound, in theory):
+DEF_BENCH(return new ShapesBench(ShapesBench::kRect_ShapesType, ShapesBench::kNone_ShapesType,
+                                 1000, SkISize::Make(500, 500), false);)
+DEF_BENCH(return new ShapesBench(ShapesBench::kOval_ShapesType, ShapesBench::kNone_ShapesType,
+                                 1000, SkISize::Make(500, 500), false);)
+DEF_BENCH(return new ShapesBench(ShapesBench::kOval_ShapesType, ShapesBench::kNone_ShapesType,
+                                 1000, SkISize::Make(500, 501), false);)
+DEF_BENCH(return new ShapesBench(ShapesBench::kRRect_ShapesType, ShapesBench::kNone_ShapesType,
+                                 1000, SkISize::Make(500, 500), false);)
+DEF_BENCH(return new ShapesBench(ShapesBench::kMixed_ShapesType, ShapesBench::kNone_ShapesType,
+                                 1000, SkISize::Make(500, 501), false);)
+
+// Donuts (small and large). These fall-back to path rendering due to non-orthogonal rotation
+// making them quite slow. Thus, reduce the counts substantially:
+DEF_BENCH(return new ShapesBench(ShapesBench::kRect_ShapesType, ShapesBench::kRect_ShapesType,
+                                 2000, SkISize::Make(32, 32), false);)
+DEF_BENCH(return new ShapesBench(ShapesBench::kRRect_ShapesType, ShapesBench::kRRect_ShapesType,
+                                 2000, SkISize::Make(32, 32), false);)
+DEF_BENCH(return new ShapesBench(ShapesBench::kRect_ShapesType, ShapesBench::kRect_ShapesType,
+                                 200, SkISize::Make(500, 500), false);)
+DEF_BENCH(return new ShapesBench(ShapesBench::kRRect_ShapesType, ShapesBench::kRRect_ShapesType,
+                                 200, SkISize::Make(500, 500), false);)
+
+#if ENABLE_COMMAND_LINE_SHAPES_BENCH
+DEF_BENCH(return new ShapesBench;)
+#endif
diff --git a/gm/shapes.cpp b/gm/shapes.cpp
new file mode 100644
index 0000000..02b6a98
--- /dev/null
+++ b/gm/shapes.cpp
@@ -0,0 +1,164 @@
+
+/*
+ * Copyright 2016 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 "SkRandom.h"
+#include "SkRRect.h"
+
+namespace skiagm {
+
+/*
+ * This is the base class for two GMs that cover various corner cases with primitive Skia shapes
+ * (zero radius, near-zero radius, inner shape overlap, etc.) It uses an xfermode of darken to help
+ * double-blended and/or dropped pixels stand out.
+ */
+class ShapesGM : public GM {
+protected:
+    ShapesGM(const char* name, bool antialias) : fName(name), fAntialias(antialias) {
+        fShapes.push_back().setOval(SkRect::MakeXYWH(-5, 25, 200, 100));
+        fRotations.push_back(21);
+
+        fShapes.push_back().setRect(SkRect::MakeXYWH(95, 75, 125, 100));
+        fRotations.push_back(94);
+
+        fShapes.push_back().setRectXY(SkRect::MakeXYWH(0, 75, 150, 100), 1e-5f, 1e-5f);
+        fRotations.push_back(132);
+
+        fShapes.push_back().setRectXY(SkRect::MakeXYWH(15, -20, 100, 100), 20, 15);
+        fRotations.push_back(282);
+
+        fSimpleShapeCount = fShapes.count();
+
+        fShapes.push_back().setNinePatch(SkRect::MakeXYWH(140, -50, 90, 110), 10, 5, 25, 35);
+        fRotations.push_back(0);
+
+        fShapes.push_back().setNinePatch(SkRect::MakeXYWH(160, -60, 60, 90), 10, 60, 50, 30);
+        fRotations.push_back(-35);
+
+        fShapes.push_back().setNinePatch(SkRect::MakeXYWH(220, -120, 60, 90), 1, 89, 59, 1);
+        fRotations.push_back(65);
+
+        SkVector radii[4] = {{4, 6}, {12, 8}, {24, 16}, {32, 48}};
+        fShapes.push_back().setRectRadii(SkRect::MakeXYWH(150, -129, 80, 160), radii);
+        fRotations.push_back(265);
+
+        SkVector radii2[4] = {{0, 0}, {80, 60}, {0, 0}, {80, 60}};
+        fShapes.push_back().setRectRadii(SkRect::MakeXYWH(180, -30, 80, 60), radii2);
+        fRotations.push_back(295);
+
+        if (!antialias) {
+            fName.append("_bw");
+        }
+    }
+
+    SkString onShortName() override final { return fName; }
+    SkISize onISize() override { return SkISize::Make(500, 500); }
+
+    void onOnceBeforeDraw() override {
+        fPaint.setXfermodeMode(SkXfermode::kDarken_Mode);
+        fPaint.setAntiAlias(fAntialias);
+    }
+
+    void onDraw(SkCanvas* canvas) override {
+        canvas->clear(SK_ColorWHITE);
+
+        canvas->save();
+        canvas->translate(canvas->imageInfo().width() / 2.f, canvas->imageInfo().height() / 2.f);
+        this->drawShapes(canvas);
+        canvas->restore();
+    }
+
+    virtual void drawShapes(SkCanvas* canvas) const = 0;
+
+protected:
+    SkString             fName;
+    bool                 fAntialias;
+    SkPaint              fPaint;
+    SkTArray<SkRRect>    fShapes;
+    SkTArray<SkScalar>   fRotations;
+    int                  fSimpleShapeCount;
+
+private:
+    typedef GM INHERITED;
+};
+
+class SimpleShapesGM : public ShapesGM {
+public:
+    SimpleShapesGM(bool antialias) : INHERITED("simpleshapes", antialias) {}
+
+private:
+    void drawShapes(SkCanvas* canvas) const override {
+        SkRandom rand(2);
+        for (int i = 0; i < fShapes.count(); i++) {
+            SkPaint paint(fPaint);
+            paint.setColor(rand.nextU() & ~0x808080);
+            paint.setAlpha(128);  // Use alpha to detect double blends.
+            const SkRRect& shape = fShapes[i];
+            canvas->save();
+            canvas->rotate(fRotations[i]);
+            switch (shape.getType()) {
+                case SkRRect::kRect_Type:
+                    canvas->drawRect(shape.rect(), paint);
+                    break;
+                case SkRRect::kOval_Type:
+                    canvas->drawOval(shape.rect(), paint);
+                    break;
+                default:
+                    canvas->drawRRect(shape, paint);
+                    break;
+            }
+            canvas->restore();
+        }
+    }
+
+    typedef ShapesGM INHERITED;
+};
+
+class InnerShapesGM : public ShapesGM {
+public:
+    InnerShapesGM(bool antialias) : INHERITED("innershapes", antialias) {}
+
+private:
+    void drawShapes(SkCanvas* canvas) const override {
+        SkRandom rand;
+        for (int i = 0; i < fShapes.count(); i++) {
+            const SkRRect& outer = fShapes[i];
+            const SkRRect& inner = fShapes[(i * 7 + 11) % fSimpleShapeCount];
+            float s = 0.95f * SkTMin(outer.rect().width() / inner.rect().width(),
+                                     outer.rect().height() / inner.rect().height());
+            SkMatrix innerXform;
+            float dx = (rand.nextF() - 0.5f) * (outer.rect().width() - s * inner.rect().width());
+            float dy = (rand.nextF() - 0.5f) * (outer.rect().height() - s * inner.rect().height());
+            innerXform.setTranslate(outer.rect().centerX() + dx, outer.rect().centerY() + dy);
+            if (s < 1) {
+                innerXform.preScale(s, s);
+            }
+            innerXform.preTranslate(-inner.rect().centerX(), -inner.rect().centerY());
+            SkRRect xformedInner;
+            inner.transform(innerXform, &xformedInner);
+            SkPaint paint(fPaint);
+            paint.setColor(rand.nextU() & ~0x808080);
+            paint.setAlpha(128);  // Use alpha to detect double blends.
+            canvas->save();
+            canvas->rotate(fRotations[i]);
+            canvas->drawDRRect(outer, xformedInner, paint);
+            canvas->restore();
+        }
+    }
+
+    typedef ShapesGM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+DEF_GM( return new SimpleShapesGM(true); )
+DEF_GM( return new SimpleShapesGM(false); )
+DEF_GM( return new InnerShapesGM(true); )
+DEF_GM( return new InnerShapesGM(false); )
+
+}