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