aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar cdalton <cdalton@nvidia.com>2016-02-10 14:54:21 -0800
committerGravatar Commit bot <commit-bot@chromium.org>2016-02-10 14:54:21 -0800
commit4a6e40d9d2e0eb81e00d8b2bd20532aa28f61afb (patch)
tree0c3b292d130b40135b587b57a960c1b77913e733
parent12d47ce49e65e27f39fa20b78445e637c95b19b8 (diff)
Add bench and gm for shapes
-rw-r--r--bench/ShapesBench.cpp289
-rw-r--r--gm/shapes.cpp164
2 files changed, 453 insertions, 0 deletions
diff --git a/bench/ShapesBench.cpp b/bench/ShapesBench.cpp
new file mode 100644
index 0000000000..1a3702fb54
--- /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 0000000000..02b6a9827b
--- /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); )
+
+}