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 skgpu::Swizzle::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 skgpu::Swizzle::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 // Conservative estimate of triangulation (see PathStencilCoverOp)
235 const int maxVerts =
236 3 * (PathTessellator::MaxCombinedFanEdgesInPathDrawList(kNumCubicsInChalkboard) - 2);
237
238 sk_sp<const GrBuffer> buffer;
239 int baseVertex;
240 VertexWriter vertexWriter = fTarget->makeVertexWriter(
241 sizeof(SkPoint), maxVerts, &buffer, &baseVertex);
242 AffineMatrix m(gAlmostIdentity);
243 for (PathMiddleOutFanIter it(fPath); !it.done();) {
244 for (auto [p0, p1, p2] : it.nextStack()) {
245 vertexWriter << m.map2Points(p0, p1) << m.mapPoint(p2);
246 }
247 }
248 }
249
250 using PathStrokeList = StrokeTessellator::PathStrokeList;
251 using MakeTessellatorFn = std::unique_ptr<StrokeTessellator>(*)(PatchAttribs);
252
make_hw_tessellator(PatchAttribs attribs)253 static std::unique_ptr<StrokeTessellator> make_hw_tessellator(PatchAttribs attribs) {
254 return std::make_unique<StrokeHardwareTessellator>(attribs, 64);
255 }
256
make_fixed_count_tessellator(PatchAttribs attribs)257 static std::unique_ptr<StrokeTessellator> make_fixed_count_tessellator(PatchAttribs attribs) {
258 return std::make_unique<StrokeFixedCountTessellator>(attribs);
259 }
260
261 using MakePathStrokesFn = std::vector<PathStrokeList>(*)();
262
make_simple_cubic_path()263 static std::vector<PathStrokeList> make_simple_cubic_path() {
264 auto path = SkPath().moveTo(0, 0);
265 for (int i = 0; i < kNumCubicsInChalkboard/2; ++i) {
266 path.cubicTo(100, 0, 50, 100, 100, 100);
267 path.cubicTo(0, -100, 200, 100, 0, 0);
268 }
269 SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
270 stroke.setStrokeStyle(8);
271 stroke.setStrokeParams(SkPaint::kButt_Cap, SkPaint::kMiter_Join, 4);
272 return {{path, stroke, SK_PMColor4fWHITE}};
273 }
274
275 // Generates a list of paths that resemble the MotionMark benchmark.
make_motionmark_paths()276 static std::vector<PathStrokeList> make_motionmark_paths() {
277 std::vector<PathStrokeList> pathStrokes;
278 SkRandom rand;
279 for (int i = 0; i < 8702; ++i) {
280 // The number of paths with a given number of verbs in the MotionMark bench gets cut in half
281 // every time the number of verbs increases by 1.
282 int numVerbs = 28 - SkNextLog2(rand.nextRangeU(0, (1 << 27) - 1));
283 SkPath path;
284 for (int j = 0; j < numVerbs; ++j) {
285 switch (rand.nextU() & 3) {
286 case 0:
287 case 1:
288 path.lineTo(rand.nextRangeF(0, 150), rand.nextRangeF(0, 150));
289 break;
290 case 2:
291 if (rand.nextULessThan(10) == 0) {
292 // Cusp.
293 auto [x, y] = (path.isEmpty())
294 ? SkPoint{0,0}
295 : SkPathPriv::PointData(path)[path.countPoints() - 1];
296 path.quadTo(x + rand.nextRangeF(0, 150), y, x - rand.nextRangeF(0, 150), y);
297 } else {
298 path.quadTo(rand.nextRangeF(0, 150), rand.nextRangeF(0, 150),
299 rand.nextRangeF(0, 150), rand.nextRangeF(0, 150));
300 }
301 break;
302 case 3:
303 if (rand.nextULessThan(10) == 0) {
304 // Cusp.
305 float y = (path.isEmpty())
306 ? 0 : SkPathPriv::PointData(path)[path.countPoints() - 1].fY;
307 path.cubicTo(rand.nextRangeF(0, 150), y, rand.nextRangeF(0, 150), y,
308 rand.nextRangeF(0, 150), y);
309 } else {
310 path.cubicTo(rand.nextRangeF(0, 150), rand.nextRangeF(0, 150),
311 rand.nextRangeF(0, 150), rand.nextRangeF(0, 150),
312 rand.nextRangeF(0, 150), rand.nextRangeF(0, 150));
313 }
314 break;
315 }
316 }
317 SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
318 // The number of paths with a given stroke width in the MotionMark bench gets cut in half
319 // every time the stroke width increases by 1.
320 float strokeWidth = 21 - log2f(rand.nextRangeF(0, 1 << 20));
321 stroke.setStrokeStyle(strokeWidth);
322 stroke.setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 0);
323 pathStrokes.emplace_back(path, stroke, SK_PMColor4fWHITE);
324 }
325 return pathStrokes;
326 }
327
328 class TessPrepareBench : public Benchmark {
329 public:
TessPrepareBench(MakePathStrokesFn makePathStrokesFn,MakeTessellatorFn makeTessellatorFn,PatchAttribs attribs,float matrixScale,const char * suffix)330 TessPrepareBench(MakePathStrokesFn makePathStrokesFn, MakeTessellatorFn makeTessellatorFn,
331 PatchAttribs attribs, float matrixScale, const char* suffix)
332 : fMakePathStrokesFn(makePathStrokesFn)
333 , fMakeTessellatorFn(makeTessellatorFn)
334 , fPatchAttribs(attribs)
335 , fMatrixScale(matrixScale) {
336 fName.printf("tessellate_%s", suffix);
337 }
338
339 private:
onGetName()340 const char* onGetName() override { return fName.c_str(); }
isSuitableFor(Backend backend)341 bool isSuitableFor(Backend backend) final { return backend == kNonRendering_Backend; }
342
onDelayedSetup()343 void onDelayedSetup() override {
344 fTarget = std::make_unique<GrMockOpTarget>(make_mock_context());
345 if (!fTarget->mockContext()) {
346 SkDebugf("ERROR: could not create mock context.");
347 return;
348 }
349
350 fPathStrokes = fMakePathStrokesFn();
351 for (size_t i = 0; i < fPathStrokes.size(); ++i) {
352 if (i + 1 < fPathStrokes.size()) {
353 fPathStrokes[i].fNext = &fPathStrokes[i + 1];
354 }
355 fTotalVerbCount += fPathStrokes[i].fPath.countVerbs();
356 }
357
358 fTessellator = fMakeTessellatorFn(fPatchAttribs);
359 }
360
onDraw(int loops,SkCanvas *)361 void onDraw(int loops, SkCanvas*) final {
362 for (int i = 0; i < loops; ++i) {
363 fTessellator->prepare(fTarget.get(),
364 SkMatrix::Scale(fMatrixScale, fMatrixScale),
365 {fMatrixScale, fMatrixScale},
366 fPathStrokes.data(),
367 fTotalVerbCount);
368 fTarget->resetAllocator();
369 }
370 }
371
372 SkString fName;
373 MakePathStrokesFn fMakePathStrokesFn;
374 MakeTessellatorFn fMakeTessellatorFn;
375 const PatchAttribs fPatchAttribs;
376 float fMatrixScale;
377 std::unique_ptr<GrMockOpTarget> fTarget;
378 std::vector<PathStrokeList> fPathStrokes;
379 std::unique_ptr<StrokeTessellator> fTessellator;
380 SkArenaAlloc fPersistentArena{1024};
381 int fTotalVerbCount = 0;
382 };
383
384 DEF_BENCH(return new TessPrepareBench(
385 make_simple_cubic_path, make_hw_tessellator, PatchAttribs::kNone, 1,
386 "GrStrokeHardwareTessellator");
387 )
388
389 DEF_BENCH(return new TessPrepareBench(
390 make_simple_cubic_path, make_hw_tessellator, PatchAttribs::kNone, 5,
391 "GrStrokeHardwareTessellator_one_chop");
392 )
393
394 DEF_BENCH(return new TessPrepareBench(
395 make_motionmark_paths, make_hw_tessellator, PatchAttribs::kStrokeParams, 1,
396 "GrStrokeHardwareTessellator_motionmark");
397 )
398
399 DEF_BENCH(return new TessPrepareBench(
400 make_simple_cubic_path, make_fixed_count_tessellator, PatchAttribs::kNone, 1,
401 "GrStrokeFixedCountTessellator");
402 )
403
404 DEF_BENCH(return new TessPrepareBench(
405 make_simple_cubic_path, make_fixed_count_tessellator, PatchAttribs::kNone, 5,
406 "GrStrokeFixedCountTessellator_one_chop");
407 )
408
409 DEF_BENCH(return new TessPrepareBench(
410 make_motionmark_paths, make_fixed_count_tessellator, PatchAttribs::kStrokeParams, 1,
411 "GrStrokeFixedCountTessellator_motionmark");
412 )
413
414 } // namespace skgpu
415