skia2/bench/ShapesBench.cpp
csmartdalton a7f29640f6 Begin instanced rendering for simple shapes
Adds a module that performs instanced rendering and starts using it
for a select subset of draws on Mac GL platforms. The instance
processor can currently handle rects, ovals, round rects, and double
round rects. It can generalize shapes as round rects in order to
improve batching. The instance processor also employs new drawing
algorithms, irrespective of instanced rendering, that improve GPU-side
performance (e.g. sample mask, different triangle layouts, etc.).

This change only scratches the surface of instanced rendering. The
majority of draws still only have one instance. Future work may
include:

 * Passing coord transforms through the texel buffer.
 * Sending FP uniforms through instanced vertex attribs.
 * Using instanced rendering for more draws (stencil writes,
   drawAtlas, etc.).
 * Adding more shapes to the instance processor’s repertoire.
 * Batching draws that have mismatched scissors (analyzing draw
   bounds, inserting clip planes, etc.).
 * Bindless textures.
 * Uber shaders.

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2066993003

Committed: https://skia.googlesource.com/skia/+/42eafa4bc00354b132ad114d22ed6b95d8849891
Review-Url: https://codereview.chromium.org/2066993003
2016-07-07 08:49:11 -07:00

289 lines
12 KiB
C++

/*
* 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;
};
#if ENABLE_COMMAND_LINE_SHAPES_BENCH
DEF_BENCH(return new ShapesBench;)
#else
// 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,
100, SkISize::Make(500, 500), false);)
DEF_BENCH(return new ShapesBench(ShapesBench::kOval_ShapesType, ShapesBench::kNone_ShapesType,
100, SkISize::Make(500, 500), false);)
DEF_BENCH(return new ShapesBench(ShapesBench::kOval_ShapesType, ShapesBench::kNone_ShapesType,
100, SkISize::Make(500, 501), false);)
DEF_BENCH(return new ShapesBench(ShapesBench::kRRect_ShapesType, ShapesBench::kNone_ShapesType,
100, SkISize::Make(500, 500), false);)
DEF_BENCH(return new ShapesBench(ShapesBench::kMixed_ShapesType, ShapesBench::kNone_ShapesType,
100, 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,
500, SkISize::Make(32, 32), false);)
DEF_BENCH(return new ShapesBench(ShapesBench::kRRect_ShapesType, ShapesBench::kRRect_ShapesType,
500, SkISize::Make(32, 32), false);)
DEF_BENCH(return new ShapesBench(ShapesBench::kRect_ShapesType, ShapesBench::kRect_ShapesType,
50, SkISize::Make(500, 500), false);)
DEF_BENCH(return new ShapesBench(ShapesBench::kRRect_ShapesType, ShapesBench::kRRect_ShapesType,
50, SkISize::Make(500, 500), false);)
#endif