2 * Copyright 2020 Google Inc.
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
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/ganesh/GrDirectContextPriv.h"
13 #include "src/gpu/ganesh/mock/GrMockOpTarget.h"
14 #include "src/gpu/ganesh/tessellate/PathTessellator.h"
15 #include "src/gpu/ganesh/tessellate/StrokeTessellator.h"
16 #include "src/gpu/tessellate/AffineMatrix.h"
17 #include "src/gpu/tessellate/MiddleOutPolygonTriangulator.h"
18 #include "src/gpu/tessellate/WangsFormula.h"
19 #include "tools/ToolUtils.h"
24 // This is the number of cubics in desk_chalkboard.skp. (There are no quadratics in the chalkboard.)
25 constexpr static int kNumCubicsInChalkboard = 47182;
27 static sk_sp<GrDirectContext> make_mock_context() {
28 GrMockOptions mockOptions;
29 mockOptions.fDrawInstancedSupport = true;
30 mockOptions.fMapBufferFlags = GrCaps::kCanMap_MapFlag;
31 mockOptions.fConfigOptions[(int)GrColorType::kAlpha_8].fRenderability =
32 GrMockOptions::ConfigOptions::Renderability::kMSAA;
33 mockOptions.fConfigOptions[(int)GrColorType::kAlpha_8].fTexturable = true;
34 mockOptions.fIntegerSupport = true;
36 GrContextOptions ctxOptions;
37 ctxOptions.fGpuPathRenderers = GpuPathRenderers::kTessellation;
39 return GrDirectContext::MakeMock(&mockOptions, ctxOptions);
42 static SkPath make_cubic_path(int maxPow2) {
45 for (int i = 0; i < kNumCubicsInChalkboard/2; ++i) {
46 float x = std::ldexp(rand.nextF(), (i % maxPow2)) / 1e3f;
47 path.cubicTo(111.625f*x, 308.188f*x, 764.62f*x, -435.688f*x, 742.63f*x, 85.187f*x);
48 path.cubicTo(764.62f*x, -435.688f*x, 111.625f*x, 308.188f*x, 0, 0);
53 static SkPath make_conic_path() {
56 for (int i = 0; i < kNumCubicsInChalkboard / 40; ++i) {
57 for (int j = -10; j <= 10; j++) {
58 const float x = std::ldexp(rand.nextF(), (i % 18)) / 1e3f;
59 const float w = std::ldexp(1 + rand.nextF(), j);
60 path.conicTo(111.625f * x, 308.188f * x, 764.62f * x, -435.688f * x, w);
66 SK_MAYBE_UNUSED static SkPath make_quad_path(int maxPow2) {
69 for (int i = 0; i < kNumCubicsInChalkboard; ++i) {
70 float x = std::ldexp(rand.nextF(), (i % maxPow2)) / 1e3f;
71 path.quadTo(111.625f * x, 308.188f * x, 764.62f * x, -435.688f * x);
76 SK_MAYBE_UNUSED static SkPath make_line_path(int maxPow2) {
79 for (int i = 0; i < kNumCubicsInChalkboard; ++i) {
80 float x = std::ldexp(rand.nextF(), (i % maxPow2)) / 1e3f;
81 path.lineTo(764.62f * x, -435.688f * x);
86 // This serves as a base class for benchmarking individual methods on PathTessellateOp.
87 class PathTessellateBenchmark : public Benchmark {
89 PathTessellateBenchmark(const char* subName, const SkPath& p, const SkMatrix& m)
90 : fPath(p), fMatrix(m) {
91 fName.printf("tessellate_%s", subName);
94 const char* onGetName() override { return fName.c_str(); }
95 bool isSuitableFor(Backend backend) final { return backend == kNonRendering_Backend; }
98 void onDelayedSetup() override {
99 fTarget = std::make_unique<GrMockOpTarget>(make_mock_context());
102 void onDraw(int loops, SkCanvas*) final {
103 if (!fTarget->mockContext()) {
104 SkDebugf("ERROR: could not create mock context.");
107 for (int i = 0; i < loops; ++i) {
109 fTarget->resetAllocator();
113 virtual void runBench() = 0;
116 std::unique_ptr<GrMockOpTarget> fTarget;
118 const SkMatrix fMatrix;
121 #define DEF_PATH_TESS_BENCH(NAME, PATH, MATRIX) \
122 class PathTessellateBenchmark_##NAME : public PathTessellateBenchmark { \
124 PathTessellateBenchmark_##NAME() : PathTessellateBenchmark(#NAME, (PATH), (MATRIX)) {} \
125 void runBench() override; \
127 DEF_BENCH( return new PathTessellateBenchmark_##NAME(); ); \
128 void PathTessellateBenchmark_##NAME::runBench()
130 static const SkMatrix gAlmostIdentity = SkMatrix::MakeAll(
131 1.0001f, 0.0001f, 0.0001f,
132 -.0001f, 0.9999f, -.0001f,
135 DEF_PATH_TESS_BENCH(GrPathCurveTessellator, make_cubic_path(8), SkMatrix::I()) {
136 SkArenaAlloc arena(1024);
137 GrPipeline noVaryingsPipeline(GrScissorTest::kDisabled, SkBlendMode::kSrcOver,
138 skgpu::Swizzle::RGBA());
139 auto tess = PathCurveTessellator::Make(&arena,
140 fTarget->caps().shaderCaps()->infinitySupport());
141 tess->prepare(fTarget.get(),
143 {gAlmostIdentity, fPath, SK_PMColor4fTRANSPARENT},
147 DEF_PATH_TESS_BENCH(GrPathWedgeTessellator, make_cubic_path(8), SkMatrix::I()) {
148 SkArenaAlloc arena(1024);
149 GrPipeline noVaryingsPipeline(GrScissorTest::kDisabled, SkBlendMode::kSrcOver,
150 skgpu::Swizzle::RGBA());
151 auto tess = PathWedgeTessellator::Make(&arena,
152 fTarget->caps().shaderCaps()->infinitySupport());
153 tess->prepare(fTarget.get(),
155 {gAlmostIdentity, fPath, SK_PMColor4fTRANSPARENT},
159 static void benchmark_wangs_formula_cubic_log2(const SkMatrix& matrix, const SkPath& path) {
161 wangs_formula::VectorXform xform(matrix);
162 for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
163 if (verb == SkPathVerb::kCubic) {
164 sum += wangs_formula::cubic_log2(4, pts, xform);
167 // Don't let the compiler optimize away wangs_formula::cubic_log2.
169 SK_ABORT("sum should be > 0.");
173 DEF_PATH_TESS_BENCH(wangs_formula_cubic_log2, make_cubic_path(18), SkMatrix::I()) {
174 benchmark_wangs_formula_cubic_log2(fMatrix, fPath);
177 DEF_PATH_TESS_BENCH(wangs_formula_cubic_log2_scale, make_cubic_path(18),
178 SkMatrix::Scale(1.1f, 0.9f)) {
179 benchmark_wangs_formula_cubic_log2(fMatrix, fPath);
182 DEF_PATH_TESS_BENCH(wangs_formula_cubic_log2_affine, make_cubic_path(18),
183 SkMatrix::MakeAll(.9f,0.9f,0, 1.1f,1.1f,0, 0,0,1)) {
184 benchmark_wangs_formula_cubic_log2(fMatrix, fPath);
187 static void benchmark_wangs_formula_conic(const SkMatrix& matrix, const SkPath& path) {
189 wangs_formula::VectorXform xform(matrix);
190 for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
191 if (verb == SkPathVerb::kConic) {
192 sum += wangs_formula::conic(4, pts, *w, xform);
195 // Don't let the compiler optimize away wangs_formula::conic.
197 SK_ABORT("sum should be > 0.");
201 static void benchmark_wangs_formula_conic_log2(const SkMatrix& matrix, const SkPath& path) {
203 wangs_formula::VectorXform xform(matrix);
204 for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
205 if (verb == SkPathVerb::kConic) {
206 sum += wangs_formula::conic_log2(4, pts, *w, xform);
209 // Don't let the compiler optimize away wangs_formula::conic.
211 SK_ABORT("sum should be > 0.");
215 DEF_PATH_TESS_BENCH(wangs_formula_conic, make_conic_path(), SkMatrix::I()) {
216 benchmark_wangs_formula_conic(fMatrix, fPath);
219 DEF_PATH_TESS_BENCH(wangs_formula_conic_log2, make_conic_path(), SkMatrix::I()) {
220 benchmark_wangs_formula_conic_log2(fMatrix, fPath);
223 DEF_PATH_TESS_BENCH(middle_out_triangulation,
224 ToolUtils::make_star(SkRect::MakeWH(500, 500), kNumCubicsInChalkboard),
226 // Conservative estimate of triangulation (see PathStencilCoverOp)
227 const int maxVerts = 3 * (kNumCubicsInChalkboard - 2);
229 sk_sp<const GrBuffer> buffer;
231 VertexWriter vertexWriter = fTarget->makeVertexWriter(
232 sizeof(SkPoint), maxVerts, &buffer, &baseVertex);
233 tess::AffineMatrix m(gAlmostIdentity);
234 for (tess::PathMiddleOutFanIter it(fPath); !it.done();) {
235 for (auto [p0, p1, p2] : it.nextStack()) {
236 vertexWriter << m.map2Points(p0, p1) << m.mapPoint(p2);
241 using PathStrokeList = StrokeTessellator::PathStrokeList;
242 using MakePathStrokesFn = std::vector<PathStrokeList>(*)();
244 static std::vector<PathStrokeList> make_simple_cubic_path() {
245 auto path = SkPath().moveTo(0, 0);
246 for (int i = 0; i < kNumCubicsInChalkboard/2; ++i) {
247 path.cubicTo(100, 0, 50, 100, 100, 100);
248 path.cubicTo(0, -100, 200, 100, 0, 0);
250 SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
251 stroke.setStrokeStyle(8);
252 stroke.setStrokeParams(SkPaint::kButt_Cap, SkPaint::kMiter_Join, 4);
253 return {{path, stroke, SK_PMColor4fWHITE}};
256 // Generates a list of paths that resemble the MotionMark benchmark.
257 static std::vector<PathStrokeList> make_motionmark_paths() {
258 std::vector<PathStrokeList> pathStrokes;
260 for (int i = 0; i < 8702; ++i) {
261 // The number of paths with a given number of verbs in the MotionMark bench gets cut in half
262 // every time the number of verbs increases by 1.
263 int numVerbs = 28 - SkNextLog2(rand.nextRangeU(0, (1 << 27) - 1));
265 for (int j = 0; j < numVerbs; ++j) {
266 switch (rand.nextU() & 3) {
269 path.lineTo(rand.nextRangeF(0, 150), rand.nextRangeF(0, 150));
272 if (rand.nextULessThan(10) == 0) {
274 auto [x, y] = (path.isEmpty())
276 : SkPathPriv::PointData(path)[path.countPoints() - 1];
277 path.quadTo(x + rand.nextRangeF(0, 150), y, x - rand.nextRangeF(0, 150), y);
279 path.quadTo(rand.nextRangeF(0, 150), rand.nextRangeF(0, 150),
280 rand.nextRangeF(0, 150), rand.nextRangeF(0, 150));
284 if (rand.nextULessThan(10) == 0) {
286 float y = (path.isEmpty())
287 ? 0 : SkPathPriv::PointData(path)[path.countPoints() - 1].fY;
288 path.cubicTo(rand.nextRangeF(0, 150), y, rand.nextRangeF(0, 150), y,
289 rand.nextRangeF(0, 150), y);
291 path.cubicTo(rand.nextRangeF(0, 150), rand.nextRangeF(0, 150),
292 rand.nextRangeF(0, 150), rand.nextRangeF(0, 150),
293 rand.nextRangeF(0, 150), rand.nextRangeF(0, 150));
298 SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
299 // The number of paths with a given stroke width in the MotionMark bench gets cut in half
300 // every time the stroke width increases by 1.
301 float strokeWidth = 21 - log2f(rand.nextRangeF(0, 1 << 20));
302 stroke.setStrokeStyle(strokeWidth);
303 stroke.setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 0);
304 pathStrokes.emplace_back(path, stroke, SK_PMColor4fWHITE);
309 using PatchAttribs = tess::PatchAttribs;
311 class TessPrepareBench : public Benchmark {
313 TessPrepareBench(MakePathStrokesFn makePathStrokesFn,
314 PatchAttribs attribs,
317 : fMakePathStrokesFn(makePathStrokesFn)
318 , fPatchAttribs(attribs)
319 , fMatrixScale(matrixScale) {
320 fName.printf("tessellate_%s", suffix);
324 const char* onGetName() override { return fName.c_str(); }
325 bool isSuitableFor(Backend backend) final { return backend == kNonRendering_Backend; }
327 void onDelayedSetup() override {
328 fTarget = std::make_unique<GrMockOpTarget>(make_mock_context());
329 if (!fTarget->mockContext()) {
330 SkDebugf("ERROR: could not create mock context.");
334 fPathStrokes = fMakePathStrokesFn();
335 for (size_t i = 0; i < fPathStrokes.size(); ++i) {
336 if (i + 1 < fPathStrokes.size()) {
337 fPathStrokes[i].fNext = &fPathStrokes[i + 1];
339 fTotalVerbCount += fPathStrokes[i].fPath.countVerbs();
342 fTessellator = std::make_unique<StrokeTessellator>(fPatchAttribs);
345 void onDraw(int loops, SkCanvas*) final {
346 for (int i = 0; i < loops; ++i) {
347 fTessellator->prepare(fTarget.get(),
348 SkMatrix::Scale(fMatrixScale, fMatrixScale),
351 fTarget->resetAllocator();
356 MakePathStrokesFn fMakePathStrokesFn;
357 const PatchAttribs fPatchAttribs;
359 std::unique_ptr<GrMockOpTarget> fTarget;
360 std::vector<PathStrokeList> fPathStrokes;
361 std::unique_ptr<StrokeTessellator> fTessellator;
362 SkArenaAlloc fPersistentArena{1024};
363 int fTotalVerbCount = 0;
366 DEF_BENCH(return new TessPrepareBench(
367 make_simple_cubic_path, PatchAttribs::kNone, 1,
368 "GrStrokeFixedCountTessellator");
371 DEF_BENCH(return new TessPrepareBench(
372 make_simple_cubic_path, PatchAttribs::kNone, 5,
373 "GrStrokeFixedCountTessellator_one_chop");
376 DEF_BENCH(return new TessPrepareBench(
377 make_motionmark_paths, PatchAttribs::kStrokeParams, 1,
378 "GrStrokeFixedCountTessellator_motionmark");
381 } // namespace skgpu::v1