• 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/core/SkRectPriv.h"
12 #include "src/gpu/GrDirectContextPriv.h"
13 #include "src/gpu/mock/GrMockOpTarget.h"
14 #include "src/gpu/tessellate/AffineMatrix.h"
15 #include "src/gpu/tessellate/MiddleOutPolygonTriangulator.h"
16 #include "src/gpu/tessellate/PathCurveTessellator.h"
17 #include "src/gpu/tessellate/PathWedgeTessellator.h"
18 #include "src/gpu/tessellate/StrokeFixedCountTessellator.h"
19 #include "src/gpu/tessellate/StrokeHardwareTessellator.h"
20 #include "src/gpu/tessellate/WangsFormula.h"
21 #include "tools/ToolUtils.h"
22 #include <vector>
23 
24 namespace skgpu {
25 
26 // This is the number of cubics in desk_chalkboard.skp. (There are no quadratics in the chalkboard.)
27 constexpr static int kNumCubicsInChalkboard = 47182;
28 
make_mock_context()29 static sk_sp<GrDirectContext> make_mock_context() {
30     GrMockOptions mockOptions;
31     mockOptions.fDrawInstancedSupport = true;
32     mockOptions.fMaxTessellationSegments = 64;
33     mockOptions.fMapBufferFlags = GrCaps::kCanMap_MapFlag;
34     mockOptions.fConfigOptions[(int)GrColorType::kAlpha_8].fRenderability =
35             GrMockOptions::ConfigOptions::Renderability::kMSAA;
36     mockOptions.fConfigOptions[(int)GrColorType::kAlpha_8].fTexturable = true;
37     mockOptions.fIntegerSupport = true;
38 
39     GrContextOptions ctxOptions;
40     ctxOptions.fGpuPathRenderers = GpuPathRenderers::kTessellation;
41     ctxOptions.fEnableExperimentalHardwareTessellation = true;
42 
43     return GrDirectContext::MakeMock(&mockOptions, ctxOptions);
44 }
45 
make_cubic_path(int maxPow2)46 static SkPath make_cubic_path(int maxPow2) {
47     SkRandom rand;
48     SkPath path;
49     for (int i = 0; i < kNumCubicsInChalkboard/2; ++i) {
50         float x = std::ldexp(rand.nextF(), (i % maxPow2)) / 1e3f;
51         path.cubicTo(111.625f*x, 308.188f*x, 764.62f*x, -435.688f*x, 742.63f*x, 85.187f*x);
52         path.cubicTo(764.62f*x, -435.688f*x, 111.625f*x, 308.188f*x, 0, 0);
53     }
54     return path;
55 }
56 
make_conic_path()57 static SkPath make_conic_path() {
58     SkRandom rand;
59     SkPath path;
60     for (int i = 0; i < kNumCubicsInChalkboard / 40; ++i) {
61         for (int j = -10; j <= 10; j++) {
62             const float x = std::ldexp(rand.nextF(), (i % 18)) / 1e3f;
63             const float w = std::ldexp(1 + rand.nextF(), j);
64             path.conicTo(111.625f * x, 308.188f * x, 764.62f * x, -435.688f * x, w);
65         }
66     }
67     return path;
68 }
69 
make_quad_path(int maxPow2)70 SK_MAYBE_UNUSED static SkPath make_quad_path(int maxPow2) {
71     SkRandom rand;
72     SkPath path;
73     for (int i = 0; i < kNumCubicsInChalkboard; ++i) {
74         float x = std::ldexp(rand.nextF(), (i % maxPow2)) / 1e3f;
75         path.quadTo(111.625f * x, 308.188f * x, 764.62f * x, -435.688f * x);
76     }
77     return path;
78 }
79 
make_line_path(int maxPow2)80 SK_MAYBE_UNUSED static SkPath make_line_path(int maxPow2) {
81     SkRandom rand;
82     SkPath path;
83     for (int i = 0; i < kNumCubicsInChalkboard; ++i) {
84         float x = std::ldexp(rand.nextF(), (i % maxPow2)) / 1e3f;
85         path.lineTo(764.62f * x, -435.688f * x);
86     }
87     return path;
88 }
89 
90 // This serves as a base class for benchmarking individual methods on PathTessellateOp.
91 class PathTessellateBenchmark : public Benchmark {
92 public:
PathTessellateBenchmark(const char * subName,const SkPath & p,const SkMatrix & m)93     PathTessellateBenchmark(const char* subName, const SkPath& p, const SkMatrix& m)
94             : fPath(p), fMatrix(m) {
95         fName.printf("tessellate_%s", subName);
96     }
97 
onGetName()98     const char* onGetName() override { return fName.c_str(); }
isSuitableFor(Backend backend)99     bool isSuitableFor(Backend backend) final { return backend == kNonRendering_Backend; }
100 
101 protected:
onDelayedSetup()102     void onDelayedSetup() override {
103         fTarget = std::make_unique<GrMockOpTarget>(make_mock_context());
104     }
105 
onDraw(int loops,SkCanvas *)106     void onDraw(int loops, SkCanvas*) final {
107         if (!fTarget->mockContext()) {
108             SkDebugf("ERROR: could not create mock context.");
109             return;
110         }
111         for (int i = 0; i < loops; ++i) {
112             this->runBench();
113             fTarget->resetAllocator();
114         }
115     }
116 
117     virtual void runBench() = 0;
118 
119     SkString fName;
120     std::unique_ptr<GrMockOpTarget> fTarget;
121     const SkPath fPath;
122     const SkMatrix fMatrix;
123 };
124 
125 #define DEF_PATH_TESS_BENCH(NAME, PATH, MATRIX) \
126     class PathTessellateBenchmark_##NAME : public PathTessellateBenchmark { \
127     public: \
128         PathTessellateBenchmark_##NAME() : PathTessellateBenchmark(#NAME, (PATH), (MATRIX)) {} \
129         void runBench() override; \
130     }; \
131     DEF_BENCH( return new PathTessellateBenchmark_##NAME(); ); \
132     void PathTessellateBenchmark_##NAME::runBench()
133 
134 static const SkMatrix gAlmostIdentity = SkMatrix::MakeAll(
135         1.0001f, 0.0001f, 0.0001f,
136         -.0001f, 0.9999f, -.0001f,
137               0,       0,       1);
138 
139 DEF_PATH_TESS_BENCH(GrPathCurveTessellator, make_cubic_path(8), SkMatrix::I()) {
140     SkArenaAlloc arena(1024);
141     GrPipeline noVaryingsPipeline(GrScissorTest::kDisabled, SkBlendMode::kSrcOver,
142                                   GrSwizzle::RGBA());
143     auto tess = PathCurveTessellator::Make(&arena,
144                                            fTarget->caps().shaderCaps()->infinitySupport());
145     tess->prepare(fTarget.get(),
146                   1 << PathCurveTessellator::kMaxFixedResolveLevel,
147                   fMatrix,
148                   {gAlmostIdentity, fPath, SK_PMColor4fTRANSPARENT},
149                   fPath.countVerbs(),
150                   true);
151 }
152 
153 DEF_PATH_TESS_BENCH(GrPathWedgeTessellator, make_cubic_path(8), SkMatrix::I()) {
154     SkArenaAlloc arena(1024);
155     GrPipeline noVaryingsPipeline(GrScissorTest::kDisabled, SkBlendMode::kSrcOver,
156                                   GrSwizzle::RGBA());
157     auto tess = PathWedgeTessellator::Make(&arena,
158                                            fTarget->caps().shaderCaps()->infinitySupport());
159     tess->prepare(fTarget.get(),
160                   1 << PathCurveTessellator::kMaxFixedResolveLevel,
161                   fMatrix,
162                   {gAlmostIdentity, fPath, SK_PMColor4fTRANSPARENT},
163                   fPath.countVerbs(),
164                   true);
165 }
166 
benchmark_wangs_formula_cubic_log2(const SkMatrix & matrix,const SkPath & path)167 static void benchmark_wangs_formula_cubic_log2(const SkMatrix& matrix, const SkPath& path) {
168     int sum = 0;
169     wangs_formula::VectorXform xform(matrix);
170     for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
171         if (verb == SkPathVerb::kCubic) {
172             sum += wangs_formula::cubic_log2(4, pts, xform);
173         }
174     }
175     // Don't let the compiler optimize away wangs_formula::cubic_log2.
176     if (sum <= 0) {
177         SK_ABORT("sum should be > 0.");
178     }
179 }
180 
181 DEF_PATH_TESS_BENCH(wangs_formula_cubic_log2, make_cubic_path(18), SkMatrix::I()) {
182     benchmark_wangs_formula_cubic_log2(fMatrix, fPath);
183 }
184 
185 DEF_PATH_TESS_BENCH(wangs_formula_cubic_log2_scale, make_cubic_path(18),
186                     SkMatrix::Scale(1.1f, 0.9f)) {
187     benchmark_wangs_formula_cubic_log2(fMatrix, fPath);
188 }
189 
190 DEF_PATH_TESS_BENCH(wangs_formula_cubic_log2_affine, make_cubic_path(18),
191                     SkMatrix::MakeAll(.9f,0.9f,0,  1.1f,1.1f,0, 0,0,1)) {
192     benchmark_wangs_formula_cubic_log2(fMatrix, fPath);
193 }
194 
benchmark_wangs_formula_conic(const SkMatrix & matrix,const SkPath & path)195 static void benchmark_wangs_formula_conic(const SkMatrix& matrix, const SkPath& path) {
196     int sum = 0;
197     wangs_formula::VectorXform xform(matrix);
198     for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
199         if (verb == SkPathVerb::kConic) {
200             sum += wangs_formula::conic(4, pts, *w, xform);
201         }
202     }
203     // Don't let the compiler optimize away wangs_formula::conic.
204     if (sum <= 0) {
205         SK_ABORT("sum should be > 0.");
206     }
207 }
208 
benchmark_wangs_formula_conic_log2(const SkMatrix & matrix,const SkPath & path)209 static void benchmark_wangs_formula_conic_log2(const SkMatrix& matrix, const SkPath& path) {
210     int sum = 0;
211     wangs_formula::VectorXform xform(matrix);
212     for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
213         if (verb == SkPathVerb::kConic) {
214             sum += wangs_formula::conic_log2(4, pts, *w, xform);
215         }
216     }
217     // Don't let the compiler optimize away wangs_formula::conic.
218     if (sum <= 0) {
219         SK_ABORT("sum should be > 0.");
220     }
221 }
222 
DEF_PATH_TESS_BENCH(wangs_formula_conic,make_conic_path (),SkMatrix::I ())223 DEF_PATH_TESS_BENCH(wangs_formula_conic, make_conic_path(), SkMatrix::I()) {
224     benchmark_wangs_formula_conic(fMatrix, fPath);
225 }
226 
DEF_PATH_TESS_BENCH(wangs_formula_conic_log2,make_conic_path (),SkMatrix::I ())227 DEF_PATH_TESS_BENCH(wangs_formula_conic_log2, make_conic_path(), SkMatrix::I()) {
228     benchmark_wangs_formula_conic_log2(fMatrix, fPath);
229 }
230 
231 DEF_PATH_TESS_BENCH(middle_out_triangulation,
232                     ToolUtils::make_star(SkRect::MakeWH(500, 500), kNumCubicsInChalkboard),
233                     SkMatrix::I()) {
234     sk_sp<const GrBuffer> buffer;
235     int baseVertex;
236     VertexWriter vertexWriter = static_cast<SkPoint*>(fTarget->makeVertexSpace(
237             sizeof(SkPoint), kNumCubicsInChalkboard, &buffer, &baseVertex));
238     AffineMatrix m(gAlmostIdentity);
239     for (PathMiddleOutFanIter it(fPath); !it.done();) {
240         for (auto [p0, p1, p2] : it.nextStack()) {
241             vertexWriter << m.map2Points(p0, p1) << m.mapPoint(p2);
242         }
243     }
244 }
245 
246 using PathStrokeList = StrokeTessellator::PathStrokeList;
247 using MakeTessellatorFn = std::unique_ptr<StrokeTessellator>(*)(PatchAttribs);
248 
make_hw_tessellator(PatchAttribs attribs)249 static std::unique_ptr<StrokeTessellator> make_hw_tessellator(PatchAttribs attribs) {
250     return std::make_unique<StrokeHardwareTessellator>(attribs);
251 }
252 
make_fixed_count_tessellator(PatchAttribs attribs)253 static std::unique_ptr<StrokeTessellator> make_fixed_count_tessellator(PatchAttribs attribs) {
254     return std::make_unique<StrokeFixedCountTessellator>(attribs);
255 }
256 
257 using MakePathStrokesFn = std::vector<PathStrokeList>(*)();
258 
make_simple_cubic_path()259 static std::vector<PathStrokeList> make_simple_cubic_path() {
260     auto path = SkPath().moveTo(0, 0);
261     for (int i = 0; i < kNumCubicsInChalkboard/2; ++i) {
262         path.cubicTo(100, 0, 50, 100, 100, 100);
263         path.cubicTo(0, -100, 200, 100, 0, 0);
264     }
265     SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
266     stroke.setStrokeStyle(8);
267     stroke.setStrokeParams(SkPaint::kButt_Cap, SkPaint::kMiter_Join, 4);
268     return {{path, stroke, SK_PMColor4fWHITE}};
269 }
270 
271 // Generates a list of paths that resemble the MotionMark benchmark.
make_motionmark_paths()272 static std::vector<PathStrokeList> make_motionmark_paths() {
273     std::vector<PathStrokeList> pathStrokes;
274     SkRandom rand;
275     for (int i = 0; i < 8702; ++i) {
276         // The number of paths with a given number of verbs in the MotionMark bench gets cut in half
277         // every time the number of verbs increases by 1.
278         int numVerbs = 28 - SkNextLog2(rand.nextRangeU(0, (1 << 27) - 1));
279         SkPath path;
280         for (int j = 0; j < numVerbs; ++j) {
281             switch (rand.nextU() & 3) {
282                 case 0:
283                 case 1:
284                     path.lineTo(rand.nextRangeF(0, 150), rand.nextRangeF(0, 150));
285                     break;
286                 case 2:
287                     if (rand.nextULessThan(10) == 0) {
288                         // Cusp.
289                         auto [x, y] = (path.isEmpty())
290                                 ? SkPoint{0,0}
291                                 : SkPathPriv::PointData(path)[path.countPoints() - 1];
292                         path.quadTo(x + rand.nextRangeF(0, 150), y, x - rand.nextRangeF(0, 150), y);
293                     } else {
294                         path.quadTo(rand.nextRangeF(0, 150), rand.nextRangeF(0, 150),
295                                     rand.nextRangeF(0, 150), rand.nextRangeF(0, 150));
296                     }
297                     break;
298                 case 3:
299                     if (rand.nextULessThan(10) == 0) {
300                         // Cusp.
301                         float y = (path.isEmpty())
302                                 ? 0 : SkPathPriv::PointData(path)[path.countPoints() - 1].fY;
303                         path.cubicTo(rand.nextRangeF(0, 150), y, rand.nextRangeF(0, 150), y,
304                                      rand.nextRangeF(0, 150), y);
305                     } else {
306                         path.cubicTo(rand.nextRangeF(0, 150), rand.nextRangeF(0, 150),
307                                      rand.nextRangeF(0, 150), rand.nextRangeF(0, 150),
308                                      rand.nextRangeF(0, 150), rand.nextRangeF(0, 150));
309                     }
310                     break;
311             }
312         }
313         SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
314         // The number of paths with a given stroke width in the MotionMark bench gets cut in half
315         // every time the stroke width increases by 1.
316         float strokeWidth = 21 - log2f(rand.nextRangeF(0, 1 << 20));
317         stroke.setStrokeStyle(strokeWidth);
318         stroke.setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 0);
319         pathStrokes.emplace_back(path, stroke, SK_PMColor4fWHITE);
320     }
321     return pathStrokes;
322 }
323 
324 class TessPrepareBench : public Benchmark {
325 public:
TessPrepareBench(MakePathStrokesFn makePathStrokesFn,MakeTessellatorFn makeTessellatorFn,PatchAttribs attribs,float matrixScale,const char * suffix)326     TessPrepareBench(MakePathStrokesFn makePathStrokesFn, MakeTessellatorFn makeTessellatorFn,
327                      PatchAttribs attribs, float matrixScale, const char* suffix)
328             : fMakePathStrokesFn(makePathStrokesFn)
329             , fMakeTessellatorFn(makeTessellatorFn)
330             , fPatchAttribs(attribs)
331             , fMatrixScale(matrixScale) {
332         fName.printf("tessellate_%s", suffix);
333     }
334 
335 private:
onGetName()336     const char* onGetName() override { return fName.c_str(); }
isSuitableFor(Backend backend)337     bool isSuitableFor(Backend backend) final { return backend == kNonRendering_Backend; }
338 
onDelayedSetup()339     void onDelayedSetup() override {
340         fTarget = std::make_unique<GrMockOpTarget>(make_mock_context());
341         if (!fTarget->mockContext()) {
342             SkDebugf("ERROR: could not create mock context.");
343             return;
344         }
345 
346         fPathStrokes = fMakePathStrokesFn();
347         for (size_t i = 0; i < fPathStrokes.size(); ++i) {
348             if (i + 1 < fPathStrokes.size()) {
349                 fPathStrokes[i].fNext = &fPathStrokes[i + 1];
350             }
351             fTotalVerbCount += fPathStrokes[i].fPath.countVerbs();
352         }
353 
354         fTessellator = fMakeTessellatorFn(fPatchAttribs);
355     }
356 
onDraw(int loops,SkCanvas *)357     void onDraw(int loops, SkCanvas*) final {
358         for (int i = 0; i < loops; ++i) {
359             fTessellator->prepare(fTarget.get(),
360                                   SkMatrix::Scale(fMatrixScale, fMatrixScale),
361                                   {fMatrixScale, fMatrixScale},
362                                   fPathStrokes.data(),
363                                   fTotalVerbCount);
364             fTarget->resetAllocator();
365         }
366     }
367 
368     SkString fName;
369     MakePathStrokesFn fMakePathStrokesFn;
370     MakeTessellatorFn fMakeTessellatorFn;
371     const PatchAttribs fPatchAttribs;
372     float fMatrixScale;
373     std::unique_ptr<GrMockOpTarget> fTarget;
374     std::vector<PathStrokeList> fPathStrokes;
375     std::unique_ptr<StrokeTessellator> fTessellator;
376     SkArenaAlloc fPersistentArena{1024};
377     int fTotalVerbCount = 0;
378 };
379 
380 DEF_BENCH(return new TessPrepareBench(
381         make_simple_cubic_path, make_hw_tessellator, PatchAttribs::kNone, 1,
382         "GrStrokeHardwareTessellator");
383 )
384 
385 DEF_BENCH(return new TessPrepareBench(
386         make_simple_cubic_path, make_hw_tessellator, PatchAttribs::kNone, 5,
387         "GrStrokeHardwareTessellator_one_chop");
388 )
389 
390 DEF_BENCH(return new TessPrepareBench(
391         make_motionmark_paths, make_hw_tessellator, PatchAttribs::kStrokeParams, 1,
392         "GrStrokeHardwareTessellator_motionmark");
393 )
394 
395 DEF_BENCH(return new TessPrepareBench(
396         make_simple_cubic_path, make_fixed_count_tessellator, PatchAttribs::kNone, 1,
397         "GrStrokeFixedCountTessellator");
398 )
399 
400 DEF_BENCH(return new TessPrepareBench(
401         make_simple_cubic_path, make_fixed_count_tessellator, PatchAttribs::kNone, 5,
402         "GrStrokeFixedCountTessellator_one_chop");
403 )
404 
405 DEF_BENCH(return new TessPrepareBench(
406         make_motionmark_paths, make_fixed_count_tessellator, PatchAttribs::kStrokeParams, 1,
407         "GrStrokeFixedCountTessellator_motionmark");
408 )
409 
410 }  // namespace skgpu
411