• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 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 "bench/Benchmark.h"
9 #include "include/gpu/GrDirectContext.h"
10 #include "src/core/SkPathPriv.h"
11 #include "src/gpu/GrDirectContextPriv.h"
12 #include "src/gpu/GrOpFlushState.h"
13 #include "src/gpu/geometry/GrWangsFormula.h"
14 #include "src/gpu/mock/GrMockOpTarget.h"
15 #include "src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h"
16 #include "src/gpu/tessellate/GrPathTessellator.h"
17 #include "src/gpu/tessellate/GrStrokeFixedCountTessellator.h"
18 #include "src/gpu/tessellate/GrStrokeHardwareTessellator.h"
19 #include "src/gpu/tessellate/GrStrokeIndirectTessellator.h"
20 #include "tools/ToolUtils.h"
21 #include <vector>
22 
23 using ShaderFlags = GrStrokeTessellateShader::ShaderFlags;
24 
25 // This is the number of cubics in desk_chalkboard.skp. (There are no quadratics in the chalkboard.)
26 constexpr static int kNumCubicsInChalkboard = 47182;
27 
make_mock_context()28 static sk_sp<GrDirectContext> make_mock_context() {
29     GrMockOptions mockOptions;
30     mockOptions.fDrawInstancedSupport = true;
31     mockOptions.fMaxTessellationSegments = 64;
32     mockOptions.fMapBufferFlags = GrCaps::kCanMap_MapFlag;
33     mockOptions.fConfigOptions[(int)GrColorType::kAlpha_8].fRenderability =
34             GrMockOptions::ConfigOptions::Renderability::kMSAA;
35     mockOptions.fConfigOptions[(int)GrColorType::kAlpha_8].fTexturable = true;
36     mockOptions.fIntegerSupport = true;
37 
38     GrContextOptions ctxOptions;
39     ctxOptions.fGpuPathRenderers = GpuPathRenderers::kTessellation;
40     ctxOptions.fEnableExperimentalHardwareTessellation = true;
41 
42     return GrDirectContext::MakeMock(&mockOptions, ctxOptions);
43 }
44 
make_cubic_path()45 static SkPath make_cubic_path() {
46     SkRandom rand;
47     SkPath path;
48     for (int i = 0; i < kNumCubicsInChalkboard/2; ++i) {
49         float x = std::ldexp(rand.nextF(), (i % 18)) / 1e3f;
50         path.cubicTo(111.625f*x, 308.188f*x, 764.62f*x, -435.688f*x, 742.63f*x, 85.187f*x);
51         path.cubicTo(764.62f*x, -435.688f*x, 111.625f*x, 308.188f*x, 0, 0);
52     }
53     return path;
54 }
55 
make_conic_path()56 static SkPath make_conic_path() {
57     SkRandom rand;
58     SkPath path;
59     for (int i = 0; i < kNumCubicsInChalkboard / 40; ++i) {
60         for (int j = -10; j <= 10; j++) {
61             const float x = std::ldexp(rand.nextF(), (i % 18)) / 1e3f;
62             const float w = std::ldexp(1 + rand.nextF(), j);
63             path.conicTo(111.625f * x, 308.188f * x, 764.62f * x, -435.688f * x, w);
64         }
65     }
66     return path;
67 }
68 
69 // This serves as a base class for benchmarking individual methods on GrPathTessellateOp.
70 class PathTessellateBenchmark : public Benchmark {
71 public:
PathTessellateBenchmark(const char * subName,const SkPath & p,const SkMatrix & m)72     PathTessellateBenchmark(const char* subName, const SkPath& p, const SkMatrix& m)
73             : fPath(p), fMatrix(m) {
74         fName.printf("tessellate_%s", subName);
75     }
76 
onGetName()77     const char* onGetName() override { return fName.c_str(); }
isSuitableFor(Backend backend)78     bool isSuitableFor(Backend backend) final { return backend == kNonRendering_Backend; }
79 
80 protected:
onDelayedSetup()81     void onDelayedSetup() override {
82         fTarget = std::make_unique<GrMockOpTarget>(make_mock_context());
83     }
84 
onDraw(int loops,SkCanvas *)85     void onDraw(int loops, SkCanvas*) final {
86         if (!fTarget->mockContext()) {
87             SkDebugf("ERROR: could not create mock context.");
88             return;
89         }
90         for (int i = 0; i < loops; ++i) {
91             this->runBench();
92             fTarget->resetAllocator();
93         }
94     }
95 
96     virtual void runBench() = 0;
97 
98     SkString fName;
99     std::unique_ptr<GrMockOpTarget> fTarget;
100     const SkPath fPath;
101     const SkMatrix fMatrix;
102 };
103 
104 #define DEF_PATH_TESS_BENCH(NAME, PATH, MATRIX) \
105     class PathTessellateBenchmark_##NAME : public PathTessellateBenchmark { \
106     public: \
107         PathTessellateBenchmark_##NAME() : PathTessellateBenchmark(#NAME, (PATH), (MATRIX)) {} \
108         void runBench() override; \
109     }; \
110     DEF_BENCH( return new PathTessellateBenchmark_##NAME(); ); \
111     void PathTessellateBenchmark_##NAME::runBench()
112 
DEF_PATH_TESS_BENCH(GrPathIndirectTessellator,make_cubic_path (),SkMatrix::I ())113 DEF_PATH_TESS_BENCH(GrPathIndirectTessellator, make_cubic_path(), SkMatrix::I()) {
114     GrPathIndirectTessellator tess(fMatrix, fPath, GrPathIndirectTessellator::DrawInnerFan::kNo);
115     tess.prepare(fTarget.get(), fMatrix, fPath, nullptr);
116 }
117 
DEF_PATH_TESS_BENCH(GrPathOuterCurveTessellator,make_cubic_path (),SkMatrix::I ())118 DEF_PATH_TESS_BENCH(GrPathOuterCurveTessellator, make_cubic_path(), SkMatrix::I()) {
119     GrPathOuterCurveTessellator tess;
120     tess.prepare(fTarget.get(), fMatrix, fPath, nullptr);
121 }
122 
DEF_PATH_TESS_BENCH(GrPathWedgeTessellator,make_cubic_path (),SkMatrix::I ())123 DEF_PATH_TESS_BENCH(GrPathWedgeTessellator, make_cubic_path(), SkMatrix::I()) {
124     GrPathWedgeTessellator tess;
125     tess.prepare(fTarget.get(), fMatrix, fPath, nullptr);
126 }
127 
benchmark_wangs_formula_cubic_log2(const SkMatrix & matrix,const SkPath & path)128 static void benchmark_wangs_formula_cubic_log2(const SkMatrix& matrix, const SkPath& path) {
129     int sum = 0;
130     GrVectorXform xform(matrix);
131     for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
132         if (verb == SkPathVerb::kCubic) {
133             sum += GrWangsFormula::cubic_log2(4, pts, xform);
134         }
135     }
136     // Don't let the compiler optimize away GrWangsFormula::cubic_log2.
137     if (sum <= 0) {
138         SK_ABORT("sum should be > 0.");
139     }
140 }
141 
DEF_PATH_TESS_BENCH(wangs_formula_cubic_log2,make_cubic_path (),SkMatrix::I ())142 DEF_PATH_TESS_BENCH(wangs_formula_cubic_log2, make_cubic_path(), SkMatrix::I()) {
143     benchmark_wangs_formula_cubic_log2(fMatrix, fPath);
144 }
145 
146 DEF_PATH_TESS_BENCH(wangs_formula_cubic_log2_scale, make_cubic_path(),
147                     SkMatrix::Scale(1.1f, 0.9f)) {
148     benchmark_wangs_formula_cubic_log2(fMatrix, fPath);
149 }
150 
151 DEF_PATH_TESS_BENCH(wangs_formula_cubic_log2_affine, make_cubic_path(),
152                     SkMatrix::MakeAll(.9f,0.9f,0,  1.1f,1.1f,0, 0,0,1)) {
153     benchmark_wangs_formula_cubic_log2(fMatrix, fPath);
154 }
155 
benchmark_wangs_formula_conic(const SkMatrix & matrix,const SkPath & path)156 static void benchmark_wangs_formula_conic(const SkMatrix& matrix, const SkPath& path) {
157     // Conic version expects tolerance, not "precision"
158     constexpr float kTolerance = 4;
159     int sum = 0;
160     GrVectorXform xform(matrix);
161     for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
162         if (verb == SkPathVerb::kConic) {
163             sum += GrWangsFormula::conic(kTolerance, pts, *w, xform);
164         }
165     }
166     // Don't let the compiler optimize away GrWangsFormula::conic.
167     if (sum <= 0) {
168         SK_ABORT("sum should be > 0.");
169     }
170 }
171 
benchmark_wangs_formula_conic_log2(const SkMatrix & matrix,const SkPath & path)172 static void benchmark_wangs_formula_conic_log2(const SkMatrix& matrix, const SkPath& path) {
173     // Conic version expects tolerance, not "precision"
174     constexpr float kTolerance = 4;
175     int sum = 0;
176     GrVectorXform xform(matrix);
177     for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
178         if (verb == SkPathVerb::kConic) {
179             sum += GrWangsFormula::conic_log2(kTolerance, pts, *w, xform);
180         }
181     }
182     // Don't let the compiler optimize away GrWangsFormula::conic.
183     if (sum <= 0) {
184         SK_ABORT("sum should be > 0.");
185     }
186 }
187 
DEF_PATH_TESS_BENCH(wangs_formula_conic,make_conic_path (),SkMatrix::I ())188 DEF_PATH_TESS_BENCH(wangs_formula_conic, make_conic_path(), SkMatrix::I()) {
189     benchmark_wangs_formula_conic(fMatrix, fPath);
190 }
191 
DEF_PATH_TESS_BENCH(wangs_formula_conic_log2,make_conic_path (),SkMatrix::I ())192 DEF_PATH_TESS_BENCH(wangs_formula_conic_log2, make_conic_path(), SkMatrix::I()) {
193     benchmark_wangs_formula_conic_log2(fMatrix, fPath);
194 }
195 
196 DEF_PATH_TESS_BENCH(middle_out_triangulation,
197                     ToolUtils::make_star(SkRect::MakeWH(500, 500), kNumCubicsInChalkboard),
198                     SkMatrix::I()) {
199     sk_sp<const GrBuffer> buffer;
200     int baseVertex;
201     auto vertexData = static_cast<SkPoint*>(fTarget->makeVertexSpace(
202             sizeof(SkPoint), kNumCubicsInChalkboard, &buffer, &baseVertex));
203     GrMiddleOutPolygonTriangulator::WritePathInnerFan(vertexData, 3, fPath);
204 }
205 
206 using PathStrokeList = GrStrokeTessellator::PathStrokeList;
207 using MakeTessellatorFn = std::unique_ptr<GrStrokeTessellator>(*)(ShaderFlags, const SkMatrix&,
208                                                                   PathStrokeList*,
209                                                                   const GrShaderCaps&);
210 
make_hw_tessellator(ShaderFlags shaderFlags,const SkMatrix & viewMatrix,PathStrokeList * pathStrokeList,const GrShaderCaps & shaderCaps)211 static std::unique_ptr<GrStrokeTessellator> make_hw_tessellator(ShaderFlags shaderFlags,
212                                                                 const SkMatrix& viewMatrix,
213                                                                 PathStrokeList* pathStrokeList,
214                                                                 const GrShaderCaps& shaderCaps) {
215     return std::make_unique<GrStrokeHardwareTessellator>(shaderFlags, viewMatrix, pathStrokeList,
216                                                          shaderCaps);
217 }
218 
make_fixed_count_tessellator(ShaderFlags shaderFlags,const SkMatrix & viewMatrix,PathStrokeList * pathStrokeList,const GrShaderCaps & shaderCaps)219 static std::unique_ptr<GrStrokeTessellator> make_fixed_count_tessellator(
220         ShaderFlags shaderFlags, const SkMatrix& viewMatrix, PathStrokeList* pathStrokeList,
221         const GrShaderCaps& shaderCaps) {
222     return std::make_unique<GrStrokeFixedCountTessellator>(shaderFlags, viewMatrix, pathStrokeList);
223 }
224 
225 using MakePathStrokesFn = std::vector<PathStrokeList>(*)();
226 
make_simple_cubic_path()227 static std::vector<PathStrokeList> make_simple_cubic_path() {
228     auto path = SkPath().moveTo(0, 0);
229     for (int i = 0; i < kNumCubicsInChalkboard/2; ++i) {
230         path.cubicTo(100, 0, 50, 100, 100, 100);
231         path.cubicTo(0, -100, 200, 100, 0, 0);
232     }
233     SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
234     stroke.setStrokeStyle(8);
235     stroke.setStrokeParams(SkPaint::kButt_Cap, SkPaint::kMiter_Join, 4);
236     return {{path, stroke, SK_PMColor4fWHITE}};
237 }
238 
239 // Generates a list of paths that resemble the MotionMark benchmark.
make_motionmark_paths()240 static std::vector<PathStrokeList> make_motionmark_paths() {
241     std::vector<PathStrokeList> pathStrokes;
242     SkRandom rand;
243     for (int i = 0; i < 8702; ++i) {
244         // The number of paths with a given number of verbs in the MotionMark bench gets cut in half
245         // every time the number of verbs increases by 1.
246         int numVerbs = 28 - SkNextLog2(rand.nextRangeU(0, (1 << 27) - 1));
247         SkPath path;
248         for (int j = 0; j < numVerbs; ++j) {
249             switch (rand.nextU() & 3) {
250                 case 0:
251                 case 1:
252                     path.lineTo(rand.nextRangeF(0, 150), rand.nextRangeF(0, 150));
253                     break;
254                 case 2:
255                     if (rand.nextULessThan(10) == 0) {
256                         // Cusp.
257                         auto [x, y] = (path.isEmpty())
258                                 ? SkPoint{0,0}
259                                 : SkPathPriv::PointData(path)[path.countPoints() - 1];
260                         path.quadTo(x + rand.nextRangeF(0, 150), y, x - rand.nextRangeF(0, 150), y);
261                     } else {
262                         path.quadTo(rand.nextRangeF(0, 150), rand.nextRangeF(0, 150),
263                                     rand.nextRangeF(0, 150), rand.nextRangeF(0, 150));
264                     }
265                     break;
266                 case 3:
267                     if (rand.nextULessThan(10) == 0) {
268                         // Cusp.
269                         float y = (path.isEmpty())
270                                 ? 0 : SkPathPriv::PointData(path)[path.countPoints() - 1].fY;
271                         path.cubicTo(rand.nextRangeF(0, 150), y, rand.nextRangeF(0, 150), y,
272                                      rand.nextRangeF(0, 150), y);
273                     } else {
274                         path.cubicTo(rand.nextRangeF(0, 150), rand.nextRangeF(0, 150),
275                                      rand.nextRangeF(0, 150), rand.nextRangeF(0, 150),
276                                      rand.nextRangeF(0, 150), rand.nextRangeF(0, 150));
277                     }
278                     break;
279             }
280         }
281         SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
282         // The number of paths with a given stroke width in the MotionMark bench gets cut in half
283         // every time the stroke width increases by 1.
284         float strokeWidth = 21 - log2f(rand.nextRangeF(0, 1 << 20));
285         stroke.setStrokeStyle(strokeWidth);
286         stroke.setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 0);
287         pathStrokes.emplace_back(path, stroke, SK_PMColor4fWHITE);
288     }
289     return pathStrokes;
290 }
291 
292 class TessPrepareBench : public Benchmark {
293 public:
TessPrepareBench(MakePathStrokesFn makePathStrokesFn,MakeTessellatorFn makeTessellatorFn,ShaderFlags shaderFlags,float matrixScale,const char * suffix)294     TessPrepareBench(MakePathStrokesFn makePathStrokesFn, MakeTessellatorFn makeTessellatorFn,
295                      ShaderFlags shaderFlags, float matrixScale, const char* suffix)
296             : fMakePathStrokesFn(makePathStrokesFn)
297             , fMakeTessellatorFn(makeTessellatorFn)
298             , fShaderFlags(shaderFlags)
299             , fMatrixScale(matrixScale) {
300         fName.printf("tessellate_%s", suffix);
301     }
302 
303 private:
onGetName()304     const char* onGetName() override { return fName.c_str(); }
isSuitableFor(Backend backend)305     bool isSuitableFor(Backend backend) final { return backend == kNonRendering_Backend; }
306 
onDelayedSetup()307     void onDelayedSetup() override {
308         fTarget = std::make_unique<GrMockOpTarget>(make_mock_context());
309         if (!fTarget->mockContext()) {
310             SkDebugf("ERROR: could not create mock context.");
311             return;
312         }
313 
314         fPathStrokes = fMakePathStrokesFn();
315         for (size_t i = 0; i < fPathStrokes.size(); ++i) {
316             if (i + 1 < fPathStrokes.size()) {
317                 fPathStrokes[i].fNext = &fPathStrokes[i + 1];
318             }
319             fTotalVerbCount += fPathStrokes[i].fPath.countVerbs();
320         }
321 
322         fTessellator = fMakeTessellatorFn(fShaderFlags, SkMatrix::Scale(fMatrixScale, fMatrixScale),
323                                           fPathStrokes.data(), *fTarget->caps().shaderCaps());
324     }
325 
onDraw(int loops,SkCanvas *)326     void onDraw(int loops, SkCanvas*) final {
327         for (int i = 0; i < loops; ++i) {
328             fTessellator->prepare(fTarget.get(), fTotalVerbCount);
329             fTarget->resetAllocator();
330         }
331     }
332 
333     SkString fName;
334     MakePathStrokesFn fMakePathStrokesFn;
335     MakeTessellatorFn fMakeTessellatorFn;
336     const ShaderFlags fShaderFlags;
337     float fMatrixScale;
338     std::unique_ptr<GrMockOpTarget> fTarget;
339     std::vector<PathStrokeList> fPathStrokes;
340     std::unique_ptr<GrStrokeTessellator> fTessellator;
341     SkArenaAlloc fPersistentArena{1024};
342     int fTotalVerbCount = 0;
343 };
344 
345 DEF_BENCH(return new TessPrepareBench(
346         make_simple_cubic_path, make_hw_tessellator, ShaderFlags::kNone, 1,
347         "GrStrokeHardwareTessellator");
348 )
349 
350 DEF_BENCH(return new TessPrepareBench(
351         make_simple_cubic_path, make_hw_tessellator, ShaderFlags::kNone, 5,
352         "GrStrokeHardwareTessellator_one_chop");
353 )
354 
355 DEF_BENCH(return new TessPrepareBench(
356         make_motionmark_paths, make_hw_tessellator, ShaderFlags::kDynamicStroke, 1,
357         "GrStrokeHardwareTessellator_motionmark");
358 )
359 
360 DEF_BENCH(return new TessPrepareBench(
361         make_simple_cubic_path, make_fixed_count_tessellator, ShaderFlags::kNone, 1,
362         "GrStrokeFixedCountTessellator");
363 )
364 
365 DEF_BENCH(return new TessPrepareBench(
366         make_simple_cubic_path, make_fixed_count_tessellator, ShaderFlags::kNone, 5,
367         "GrStrokeFixedCountTessellator_one_chop");
368 )
369 
370 DEF_BENCH(return new TessPrepareBench(
371         make_motionmark_paths, make_fixed_count_tessellator, ShaderFlags::kDynamicStroke, 1,
372         "GrStrokeFixedCountTessellator_motionmark");
373 )
374 
375 class GrStrokeIndirectTessellator::Benchmark : public ::Benchmark {
376 protected:
Benchmark(const char * nameSuffix,SkPaint::Join join)377     Benchmark(const char* nameSuffix, SkPaint::Join join) : fJoin(join) {
378         fName.printf("tessellate_GrStrokeIndirectTessellator%s", nameSuffix);
379     }
380 
381     const SkPaint::Join fJoin;
382 
383 private:
onGetName()384     const char* onGetName() final { return fName.c_str(); }
isSuitableFor(Backend backend)385     bool isSuitableFor(Backend backend) final { return backend == kNonRendering_Backend; }
onDelayedSetup()386     void onDelayedSetup() final {
387         fTarget = std::make_unique<GrMockOpTarget>(make_mock_context());
388         fStrokeRec.setStrokeStyle(8);
389         fStrokeRec.setStrokeParams(SkPaint::kButt_Cap, fJoin, 4);
390         this->setupPaths(&fPaths);
391     }
onDraw(int loops,SkCanvas *)392     void onDraw(int loops, SkCanvas*) final {
393         if (!fTarget->mockContext()) {
394             SkDebugf("ERROR: could not create mock context.");
395             return;
396         }
397         for (int i = 0; i < loops; ++i) {
398             for (const SkPath& path : fPaths) {
399                 GrStrokeTessellator::PathStrokeList pathStroke(path, fStrokeRec, SK_PMColor4fWHITE);
400                 GrStrokeIndirectTessellator tessellator(ShaderFlags::kNone, SkMatrix::I(),
401                                                         &pathStroke, path.countVerbs(),
402                                                         fTarget->allocator());
403                 tessellator.prepare(fTarget.get(), path.countVerbs());
404             }
405             fTarget->resetAllocator();
406         }
407     }
408     virtual void setupPaths(SkTArray<SkPath>*) = 0;
409 
410     SkString fName;
411     std::unique_ptr<GrMockOpTarget> fTarget;
412     SkTArray<SkPath> fPaths;
413     SkStrokeRec fStrokeRec{SkStrokeRec::kHairline_InitStyle};
414 };
415 
416 class StrokeIndirectBenchmark : public GrStrokeIndirectTessellator::Benchmark {
417 public:
StrokeIndirectBenchmark(const char * nameSuffix,SkPaint::Join join,std::vector<SkPoint> pts)418     StrokeIndirectBenchmark(const char* nameSuffix, SkPaint::Join join, std::vector<SkPoint> pts)
419             : Benchmark(nameSuffix, join), fPts(std::move(pts)) {}
420 
421 private:
setupPaths(SkTArray<SkPath> * paths)422     void setupPaths(SkTArray<SkPath>* paths) final {
423         SkPath& path = paths->push_back();
424         if (fJoin == SkPaint::kRound_Join) {
425             path.reset().moveTo(fPts.back());
426             for (size_t i = 0; i < kNumCubicsInChalkboard/fPts.size(); ++i) {
427                 for (size_t j = 0; j < fPts.size(); ++j) {
428                     path.lineTo(fPts[j]);
429                 }
430             }
431         } else {
432             path.reset().moveTo(fPts[0]);
433             for (int i = 0; i < kNumCubicsInChalkboard/2; ++i) {
434                 if (fPts.size() == 4) {
435                     path.cubicTo(fPts[1], fPts[2], fPts[3]);
436                     path.cubicTo(fPts[2], fPts[1], fPts[0]);
437                 } else {
438                     SkASSERT(fPts.size() == 3);
439                     path.quadTo(fPts[1], fPts[2]);
440                     path.quadTo(fPts[2], fPts[1]);
441                 }
442             }
443         }
444     }
445 
446     const std::vector<SkPoint> fPts;
447 };
448 
449 DEF_BENCH( return new StrokeIndirectBenchmark(
450         "_inflect1", SkPaint::kBevel_Join, {{0,0}, {100,0}, {0,100}, {100,100}}); )
451 
452 DEF_BENCH( return new StrokeIndirectBenchmark(
453         "_inflect2", SkPaint::kBevel_Join, {{37,162}, {412,160}, {249,65}, {112,360}}); )
454 
455 DEF_BENCH( return new StrokeIndirectBenchmark(
456         "_loop", SkPaint::kBevel_Join, {{0,0}, {100,0}, {0,100}, {0,0}}); )
457 
458 DEF_BENCH( return new StrokeIndirectBenchmark(
459         "_nochop", SkPaint::kBevel_Join, {{0,0}, {50,0}, {100,50}, {100,100}}); )
460 
461 DEF_BENCH( return new StrokeIndirectBenchmark(
462         "_quad", SkPaint::kBevel_Join, {{0,0}, {50,100}, {100,0}}); )
463 
464 DEF_BENCH( return new StrokeIndirectBenchmark(
465         "_roundjoin", SkPaint::kRound_Join, {{0,0}, {50,100}, {100,0}}); )
466 
467 class SingleVerbStrokeIndirectBenchmark : public GrStrokeIndirectTessellator::Benchmark {
468 public:
SingleVerbStrokeIndirectBenchmark(const char * nameSuffix,SkPathVerb verb)469     SingleVerbStrokeIndirectBenchmark(const char* nameSuffix, SkPathVerb verb)
470             : Benchmark(nameSuffix, SkPaint::kBevel_Join), fVerb(verb) {}
471 
472 private:
setupPaths(SkTArray<SkPath> * paths)473     void setupPaths(SkTArray<SkPath>* paths) override {
474         SkRandom rand;
475         for (int i = 0; i < kNumCubicsInChalkboard; ++i)   {
476             switch (fVerb) {
477                 case SkPathVerb::kQuad:
478                     paths->push_back().quadTo(rand.nextF(), rand.nextF(), rand.nextF(),
479                                               rand.nextF());
480                     break;
481                 case SkPathVerb::kCubic:
482                     switch (i % 3) {
483                         case 0:
484                             paths->push_back().cubicTo(100, 0, 0, 100, 100, 100);  // 1 inflection.
485                             break;
486                         case 1:
487                             paths->push_back().cubicTo(100, 0, 0, 100, 0, 0);  // loop.
488                             break;
489                         case 2:
490                             paths->push_back().cubicTo(50, 0, 100, 50, 100, 100);  // no chop.
491                             break;
492                     }
493                     break;
494                 default:
495                     SkUNREACHABLE;
496             }
497         }
498     }
499 
500     const SkPathVerb fVerb;
501 };
502 
503 DEF_BENCH( return new SingleVerbStrokeIndirectBenchmark("_singlequads", SkPathVerb::kQuad); )
504 DEF_BENCH( return new SingleVerbStrokeIndirectBenchmark("_singlecubics", SkPathVerb::kCubic); )
505