/* * Copyright 2020 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "bench/Benchmark.h" #include "include/gpu/GrDirectContext.h" #include "src/core/SkPathPriv.h" #include "src/gpu/GrDirectContextPriv.h" #include "src/gpu/GrOpFlushState.h" #include "src/gpu/geometry/GrWangsFormula.h" #include "src/gpu/mock/GrMockOpTarget.h" #include "src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h" #include "src/gpu/tessellate/GrPathTessellator.h" #include "src/gpu/tessellate/GrStrokeFixedCountTessellator.h" #include "src/gpu/tessellate/GrStrokeHardwareTessellator.h" #include "src/gpu/tessellate/GrStrokeIndirectTessellator.h" #include "tools/ToolUtils.h" #include using ShaderFlags = GrStrokeTessellateShader::ShaderFlags; // This is the number of cubics in desk_chalkboard.skp. (There are no quadratics in the chalkboard.) constexpr static int kNumCubicsInChalkboard = 47182; static sk_sp make_mock_context() { GrMockOptions mockOptions; mockOptions.fDrawInstancedSupport = true; mockOptions.fMaxTessellationSegments = 64; mockOptions.fMapBufferFlags = GrCaps::kCanMap_MapFlag; mockOptions.fConfigOptions[(int)GrColorType::kAlpha_8].fRenderability = GrMockOptions::ConfigOptions::Renderability::kMSAA; mockOptions.fConfigOptions[(int)GrColorType::kAlpha_8].fTexturable = true; mockOptions.fIntegerSupport = true; GrContextOptions ctxOptions; ctxOptions.fGpuPathRenderers = GpuPathRenderers::kTessellation; ctxOptions.fEnableExperimentalHardwareTessellation = true; return GrDirectContext::MakeMock(&mockOptions, ctxOptions); } static SkPath make_cubic_path() { SkRandom rand; SkPath path; for (int i = 0; i < kNumCubicsInChalkboard/2; ++i) { float x = std::ldexp(rand.nextF(), (i % 18)) / 1e3f; path.cubicTo(111.625f*x, 308.188f*x, 764.62f*x, -435.688f*x, 742.63f*x, 85.187f*x); path.cubicTo(764.62f*x, -435.688f*x, 111.625f*x, 308.188f*x, 0, 0); } return path; } static SkPath make_conic_path() { SkRandom rand; SkPath path; for (int i = 0; i < kNumCubicsInChalkboard / 40; ++i) { for (int j = -10; j <= 10; j++) { const float x = std::ldexp(rand.nextF(), (i % 18)) / 1e3f; const float w = std::ldexp(1 + rand.nextF(), j); path.conicTo(111.625f * x, 308.188f * x, 764.62f * x, -435.688f * x, w); } } return path; } // This serves as a base class for benchmarking individual methods on GrPathTessellateOp. class PathTessellateBenchmark : public Benchmark { public: PathTessellateBenchmark(const char* subName, const SkPath& p, const SkMatrix& m) : fPath(p), fMatrix(m) { fName.printf("tessellate_%s", subName); } const char* onGetName() override { return fName.c_str(); } bool isSuitableFor(Backend backend) final { return backend == kNonRendering_Backend; } protected: void onDelayedSetup() override { fTarget = std::make_unique(make_mock_context()); } void onDraw(int loops, SkCanvas*) final { if (!fTarget->mockContext()) { SkDebugf("ERROR: could not create mock context."); return; } for (int i = 0; i < loops; ++i) { this->runBench(); fTarget->resetAllocator(); } } virtual void runBench() = 0; SkString fName; std::unique_ptr fTarget; const SkPath fPath; const SkMatrix fMatrix; }; #define DEF_PATH_TESS_BENCH(NAME, PATH, MATRIX) \ class PathTessellateBenchmark_##NAME : public PathTessellateBenchmark { \ public: \ PathTessellateBenchmark_##NAME() : PathTessellateBenchmark(#NAME, (PATH), (MATRIX)) {} \ void runBench() override; \ }; \ DEF_BENCH( return new PathTessellateBenchmark_##NAME(); ); \ void PathTessellateBenchmark_##NAME::runBench() DEF_PATH_TESS_BENCH(GrPathIndirectTessellator, make_cubic_path(), SkMatrix::I()) { GrPathIndirectTessellator tess(fMatrix, fPath, GrPathIndirectTessellator::DrawInnerFan::kNo); tess.prepare(fTarget.get(), fMatrix, fPath, nullptr); } DEF_PATH_TESS_BENCH(GrPathOuterCurveTessellator, make_cubic_path(), SkMatrix::I()) { GrPathOuterCurveTessellator tess; tess.prepare(fTarget.get(), fMatrix, fPath, nullptr); } DEF_PATH_TESS_BENCH(GrPathWedgeTessellator, make_cubic_path(), SkMatrix::I()) { GrPathWedgeTessellator tess; tess.prepare(fTarget.get(), fMatrix, fPath, nullptr); } static void benchmark_wangs_formula_cubic_log2(const SkMatrix& matrix, const SkPath& path) { int sum = 0; GrVectorXform xform(matrix); for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) { if (verb == SkPathVerb::kCubic) { sum += GrWangsFormula::cubic_log2(4, pts, xform); } } // Don't let the compiler optimize away GrWangsFormula::cubic_log2. if (sum <= 0) { SK_ABORT("sum should be > 0."); } } DEF_PATH_TESS_BENCH(wangs_formula_cubic_log2, make_cubic_path(), SkMatrix::I()) { benchmark_wangs_formula_cubic_log2(fMatrix, fPath); } DEF_PATH_TESS_BENCH(wangs_formula_cubic_log2_scale, make_cubic_path(), SkMatrix::Scale(1.1f, 0.9f)) { benchmark_wangs_formula_cubic_log2(fMatrix, fPath); } DEF_PATH_TESS_BENCH(wangs_formula_cubic_log2_affine, make_cubic_path(), SkMatrix::MakeAll(.9f,0.9f,0, 1.1f,1.1f,0, 0,0,1)) { benchmark_wangs_formula_cubic_log2(fMatrix, fPath); } static void benchmark_wangs_formula_conic(const SkMatrix& matrix, const SkPath& path) { // Conic version expects tolerance, not "precision" constexpr float kTolerance = 4; int sum = 0; GrVectorXform xform(matrix); for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) { if (verb == SkPathVerb::kConic) { sum += GrWangsFormula::conic(kTolerance, pts, *w, xform); } } // Don't let the compiler optimize away GrWangsFormula::conic. if (sum <= 0) { SK_ABORT("sum should be > 0."); } } static void benchmark_wangs_formula_conic_log2(const SkMatrix& matrix, const SkPath& path) { // Conic version expects tolerance, not "precision" constexpr float kTolerance = 4; int sum = 0; GrVectorXform xform(matrix); for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) { if (verb == SkPathVerb::kConic) { sum += GrWangsFormula::conic_log2(kTolerance, pts, *w, xform); } } // Don't let the compiler optimize away GrWangsFormula::conic. if (sum <= 0) { SK_ABORT("sum should be > 0."); } } DEF_PATH_TESS_BENCH(wangs_formula_conic, make_conic_path(), SkMatrix::I()) { benchmark_wangs_formula_conic(fMatrix, fPath); } DEF_PATH_TESS_BENCH(wangs_formula_conic_log2, make_conic_path(), SkMatrix::I()) { benchmark_wangs_formula_conic_log2(fMatrix, fPath); } DEF_PATH_TESS_BENCH(middle_out_triangulation, ToolUtils::make_star(SkRect::MakeWH(500, 500), kNumCubicsInChalkboard), SkMatrix::I()) { sk_sp buffer; int baseVertex; auto vertexData = static_cast(fTarget->makeVertexSpace( sizeof(SkPoint), kNumCubicsInChalkboard, &buffer, &baseVertex)); GrMiddleOutPolygonTriangulator::WritePathInnerFan(vertexData, 3, fPath); } using PathStrokeList = GrStrokeTessellator::PathStrokeList; using MakeTessellatorFn = std::unique_ptr(*)(ShaderFlags, const SkMatrix&, PathStrokeList*, const GrShaderCaps&); static std::unique_ptr make_hw_tessellator(ShaderFlags shaderFlags, const SkMatrix& viewMatrix, PathStrokeList* pathStrokeList, const GrShaderCaps& shaderCaps) { return std::make_unique(shaderFlags, viewMatrix, pathStrokeList, shaderCaps); } static std::unique_ptr make_fixed_count_tessellator( ShaderFlags shaderFlags, const SkMatrix& viewMatrix, PathStrokeList* pathStrokeList, const GrShaderCaps& shaderCaps) { return std::make_unique(shaderFlags, viewMatrix, pathStrokeList); } using MakePathStrokesFn = std::vector(*)(); static std::vector make_simple_cubic_path() { auto path = SkPath().moveTo(0, 0); for (int i = 0; i < kNumCubicsInChalkboard/2; ++i) { path.cubicTo(100, 0, 50, 100, 100, 100); path.cubicTo(0, -100, 200, 100, 0, 0); } SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle); stroke.setStrokeStyle(8); stroke.setStrokeParams(SkPaint::kButt_Cap, SkPaint::kMiter_Join, 4); return {{path, stroke, SK_PMColor4fWHITE}}; } // Generates a list of paths that resemble the MotionMark benchmark. static std::vector make_motionmark_paths() { std::vector pathStrokes; SkRandom rand; for (int i = 0; i < 8702; ++i) { // The number of paths with a given number of verbs in the MotionMark bench gets cut in half // every time the number of verbs increases by 1. int numVerbs = 28 - SkNextLog2(rand.nextRangeU(0, (1 << 27) - 1)); SkPath path; for (int j = 0; j < numVerbs; ++j) { switch (rand.nextU() & 3) { case 0: case 1: path.lineTo(rand.nextRangeF(0, 150), rand.nextRangeF(0, 150)); break; case 2: if (rand.nextULessThan(10) == 0) { // Cusp. auto [x, y] = (path.isEmpty()) ? SkPoint{0,0} : SkPathPriv::PointData(path)[path.countPoints() - 1]; path.quadTo(x + rand.nextRangeF(0, 150), y, x - rand.nextRangeF(0, 150), y); } else { path.quadTo(rand.nextRangeF(0, 150), rand.nextRangeF(0, 150), rand.nextRangeF(0, 150), rand.nextRangeF(0, 150)); } break; case 3: if (rand.nextULessThan(10) == 0) { // Cusp. float y = (path.isEmpty()) ? 0 : SkPathPriv::PointData(path)[path.countPoints() - 1].fY; path.cubicTo(rand.nextRangeF(0, 150), y, rand.nextRangeF(0, 150), y, rand.nextRangeF(0, 150), y); } else { path.cubicTo(rand.nextRangeF(0, 150), rand.nextRangeF(0, 150), rand.nextRangeF(0, 150), rand.nextRangeF(0, 150), rand.nextRangeF(0, 150), rand.nextRangeF(0, 150)); } break; } } SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle); // The number of paths with a given stroke width in the MotionMark bench gets cut in half // every time the stroke width increases by 1. float strokeWidth = 21 - log2f(rand.nextRangeF(0, 1 << 20)); stroke.setStrokeStyle(strokeWidth); stroke.setStrokeParams(SkPaint::kButt_Cap, SkPaint::kBevel_Join, 0); pathStrokes.emplace_back(path, stroke, SK_PMColor4fWHITE); } return pathStrokes; } class TessPrepareBench : public Benchmark { public: TessPrepareBench(MakePathStrokesFn makePathStrokesFn, MakeTessellatorFn makeTessellatorFn, ShaderFlags shaderFlags, float matrixScale, const char* suffix) : fMakePathStrokesFn(makePathStrokesFn) , fMakeTessellatorFn(makeTessellatorFn) , fShaderFlags(shaderFlags) , fMatrixScale(matrixScale) { fName.printf("tessellate_%s", suffix); } private: const char* onGetName() override { return fName.c_str(); } bool isSuitableFor(Backend backend) final { return backend == kNonRendering_Backend; } void onDelayedSetup() override { fTarget = std::make_unique(make_mock_context()); if (!fTarget->mockContext()) { SkDebugf("ERROR: could not create mock context."); return; } fPathStrokes = fMakePathStrokesFn(); for (size_t i = 0; i < fPathStrokes.size(); ++i) { if (i + 1 < fPathStrokes.size()) { fPathStrokes[i].fNext = &fPathStrokes[i + 1]; } fTotalVerbCount += fPathStrokes[i].fPath.countVerbs(); } fTessellator = fMakeTessellatorFn(fShaderFlags, SkMatrix::Scale(fMatrixScale, fMatrixScale), fPathStrokes.data(), *fTarget->caps().shaderCaps()); } void onDraw(int loops, SkCanvas*) final { for (int i = 0; i < loops; ++i) { fTessellator->prepare(fTarget.get(), fTotalVerbCount); fTarget->resetAllocator(); } } SkString fName; MakePathStrokesFn fMakePathStrokesFn; MakeTessellatorFn fMakeTessellatorFn; const ShaderFlags fShaderFlags; float fMatrixScale; std::unique_ptr fTarget; std::vector fPathStrokes; std::unique_ptr fTessellator; SkArenaAlloc fPersistentArena{1024}; int fTotalVerbCount = 0; }; DEF_BENCH(return new TessPrepareBench( make_simple_cubic_path, make_hw_tessellator, ShaderFlags::kNone, 1, "GrStrokeHardwareTessellator"); ) DEF_BENCH(return new TessPrepareBench( make_simple_cubic_path, make_hw_tessellator, ShaderFlags::kNone, 5, "GrStrokeHardwareTessellator_one_chop"); ) DEF_BENCH(return new TessPrepareBench( make_motionmark_paths, make_hw_tessellator, ShaderFlags::kDynamicStroke, 1, "GrStrokeHardwareTessellator_motionmark"); ) DEF_BENCH(return new TessPrepareBench( make_simple_cubic_path, make_fixed_count_tessellator, ShaderFlags::kNone, 1, "GrStrokeFixedCountTessellator"); ) DEF_BENCH(return new TessPrepareBench( make_simple_cubic_path, make_fixed_count_tessellator, ShaderFlags::kNone, 5, "GrStrokeFixedCountTessellator_one_chop"); ) DEF_BENCH(return new TessPrepareBench( make_motionmark_paths, make_fixed_count_tessellator, ShaderFlags::kDynamicStroke, 1, "GrStrokeFixedCountTessellator_motionmark"); ) class GrStrokeIndirectTessellator::Benchmark : public ::Benchmark { protected: Benchmark(const char* nameSuffix, SkPaint::Join join) : fJoin(join) { fName.printf("tessellate_GrStrokeIndirectTessellator%s", nameSuffix); } const SkPaint::Join fJoin; private: const char* onGetName() final { return fName.c_str(); } bool isSuitableFor(Backend backend) final { return backend == kNonRendering_Backend; } void onDelayedSetup() final { fTarget = std::make_unique(make_mock_context()); fStrokeRec.setStrokeStyle(8); fStrokeRec.setStrokeParams(SkPaint::kButt_Cap, fJoin, 4); this->setupPaths(&fPaths); } void onDraw(int loops, SkCanvas*) final { if (!fTarget->mockContext()) { SkDebugf("ERROR: could not create mock context."); return; } for (int i = 0; i < loops; ++i) { for (const SkPath& path : fPaths) { GrStrokeTessellator::PathStrokeList pathStroke(path, fStrokeRec, SK_PMColor4fWHITE); GrStrokeIndirectTessellator tessellator(ShaderFlags::kNone, SkMatrix::I(), &pathStroke, path.countVerbs(), fTarget->allocator()); tessellator.prepare(fTarget.get(), path.countVerbs()); } fTarget->resetAllocator(); } } virtual void setupPaths(SkTArray*) = 0; SkString fName; std::unique_ptr fTarget; SkTArray fPaths; SkStrokeRec fStrokeRec{SkStrokeRec::kHairline_InitStyle}; }; class StrokeIndirectBenchmark : public GrStrokeIndirectTessellator::Benchmark { public: StrokeIndirectBenchmark(const char* nameSuffix, SkPaint::Join join, std::vector pts) : Benchmark(nameSuffix, join), fPts(std::move(pts)) {} private: void setupPaths(SkTArray* paths) final { SkPath& path = paths->push_back(); if (fJoin == SkPaint::kRound_Join) { path.reset().moveTo(fPts.back()); for (size_t i = 0; i < kNumCubicsInChalkboard/fPts.size(); ++i) { for (size_t j = 0; j < fPts.size(); ++j) { path.lineTo(fPts[j]); } } } else { path.reset().moveTo(fPts[0]); for (int i = 0; i < kNumCubicsInChalkboard/2; ++i) { if (fPts.size() == 4) { path.cubicTo(fPts[1], fPts[2], fPts[3]); path.cubicTo(fPts[2], fPts[1], fPts[0]); } else { SkASSERT(fPts.size() == 3); path.quadTo(fPts[1], fPts[2]); path.quadTo(fPts[2], fPts[1]); } } } } const std::vector fPts; }; DEF_BENCH( return new StrokeIndirectBenchmark( "_inflect1", SkPaint::kBevel_Join, {{0,0}, {100,0}, {0,100}, {100,100}}); ) DEF_BENCH( return new StrokeIndirectBenchmark( "_inflect2", SkPaint::kBevel_Join, {{37,162}, {412,160}, {249,65}, {112,360}}); ) DEF_BENCH( return new StrokeIndirectBenchmark( "_loop", SkPaint::kBevel_Join, {{0,0}, {100,0}, {0,100}, {0,0}}); ) DEF_BENCH( return new StrokeIndirectBenchmark( "_nochop", SkPaint::kBevel_Join, {{0,0}, {50,0}, {100,50}, {100,100}}); ) DEF_BENCH( return new StrokeIndirectBenchmark( "_quad", SkPaint::kBevel_Join, {{0,0}, {50,100}, {100,0}}); ) DEF_BENCH( return new StrokeIndirectBenchmark( "_roundjoin", SkPaint::kRound_Join, {{0,0}, {50,100}, {100,0}}); ) class SingleVerbStrokeIndirectBenchmark : public GrStrokeIndirectTessellator::Benchmark { public: SingleVerbStrokeIndirectBenchmark(const char* nameSuffix, SkPathVerb verb) : Benchmark(nameSuffix, SkPaint::kBevel_Join), fVerb(verb) {} private: void setupPaths(SkTArray* paths) override { SkRandom rand; for (int i = 0; i < kNumCubicsInChalkboard; ++i) { switch (fVerb) { case SkPathVerb::kQuad: paths->push_back().quadTo(rand.nextF(), rand.nextF(), rand.nextF(), rand.nextF()); break; case SkPathVerb::kCubic: switch (i % 3) { case 0: paths->push_back().cubicTo(100, 0, 0, 100, 100, 100); // 1 inflection. break; case 1: paths->push_back().cubicTo(100, 0, 0, 100, 0, 0); // loop. break; case 2: paths->push_back().cubicTo(50, 0, 100, 50, 100, 100); // no chop. break; } break; default: SkUNREACHABLE; } } } const SkPathVerb fVerb; }; DEF_BENCH( return new SingleVerbStrokeIndirectBenchmark("_singlequads", SkPathVerb::kQuad); ) DEF_BENCH( return new SingleVerbStrokeIndirectBenchmark("_singlecubics", SkPathVerb::kCubic); )