| /* | |
| * Copyright 2011 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/core/SkBitmap.h" | |
| #include "include/core/SkCanvas.h" | |
| #include "include/core/SkPaint.h" | |
| #include "include/core/SkPath.h" | |
| #include "include/core/SkPathBuilder.h" | |
| #include "include/core/SkPathEffect.h" | |
| #include "include/core/SkString.h" | |
| #include "include/core/SkStrokeRec.h" | |
| #include "include/effects/SkDashPathEffect.h" | |
| #include "include/private/SkTDArray.h" | |
| #include "src/core/SkRandom.h" | |
| #include <array> | |
| /* | |
| * Cases to consider: | |
| * | |
| * 1. antialiasing on/off (esp. width <= 1) | |
| * 2. strokewidth == 0, 1, 2 | |
| * 3. hline, vline, diagonal, rect, oval | |
| * 4. dots [1,1] ([N,N] where N=strokeWidth?) or arbitrary (e.g. [2,1] or [1,2,3,2]) | |
| */ | |
| static SkPath path_hline() { | |
| return SkPath::Line({10, 10}, {600, 10}); | |
| } | |
| class DashBench : public Benchmark { | |
| protected: | |
| SkString fName; | |
| SkTDArray<SkScalar> fIntervals; | |
| int fWidth; | |
| SkPoint fPts[2]; | |
| bool fDoClip; | |
| public: | |
| DashBench(const SkScalar intervals[], int count, int width, | |
| bool doClip = false) { | |
| fIntervals.append(count, intervals); | |
| for (int i = 0; i < count; ++i) { | |
| fIntervals[i] *= width; | |
| } | |
| fWidth = width; | |
| fName.printf("dash_%d_%s", width, doClip ? "clipped" : "noclip"); | |
| fDoClip = doClip; | |
| fPts[0].set(SkIntToScalar(10), SkIntToScalar(10)); | |
| fPts[1].set(SkIntToScalar(600), SkIntToScalar(10)); | |
| } | |
| virtual SkPath makePath() { | |
| return path_hline(); | |
| } | |
| protected: | |
| const char* onGetName() override { | |
| return fName.c_str(); | |
| } | |
| void onDraw(int loops, SkCanvas* canvas) override { | |
| SkPaint paint; | |
| this->setupPaint(&paint); | |
| paint.setStyle(SkPaint::kStroke_Style); | |
| paint.setStrokeWidth(SkIntToScalar(fWidth)); | |
| paint.setAntiAlias(false); | |
| SkPath path = this->makePath(); | |
| paint.setPathEffect(SkDashPathEffect::Make(fIntervals, 0)); | |
| if (fDoClip) { | |
| SkRect r = path.getBounds(); | |
| r.inset(-SkIntToScalar(20), -SkIntToScalar(20)); | |
| // now move it so we don't intersect | |
| r.offset(0, r.height() * 3 / 2); | |
| canvas->clipRect(r); | |
| } | |
| this->handlePath(canvas, path, paint, loops); | |
| } | |
| virtual void handlePath(SkCanvas* canvas, const SkPath& path, | |
| const SkPaint& paint, int N) { | |
| for (int i = 0; i < N; ++i) { | |
| // canvas->drawPoints(SkCanvas::kLines_PointMode, 2, fPts, paint); | |
| canvas->drawPath(path, paint); | |
| } | |
| } | |
| private: | |
| using INHERITED = Benchmark; | |
| }; | |
| class RectDashBench : public DashBench { | |
| public: | |
| RectDashBench(const SkScalar intervals[], int count, int width) | |
| : INHERITED(intervals, count, width) { | |
| fName.append("_rect"); | |
| } | |
| protected: | |
| void handlePath(SkCanvas* canvas, const SkPath& path, const SkPaint& paint, int N) override { | |
| SkPoint pts[2]; | |
| if (!path.isLine(pts) || pts[0].fY != pts[1].fY) { | |
| this->INHERITED::handlePath(canvas, path, paint, N); | |
| } else { | |
| SkRect rect; | |
| rect.fLeft = pts[0].fX; | |
| rect.fTop = pts[0].fY - paint.getStrokeWidth() / 2; | |
| rect.fRight = rect.fLeft + SkIntToScalar(fWidth); | |
| rect.fBottom = rect.fTop + paint.getStrokeWidth(); | |
| SkPaint p(paint); | |
| p.setStyle(SkPaint::kFill_Style); | |
| p.setPathEffect(nullptr); | |
| int count = SkScalarRoundToInt((pts[1].fX - pts[0].fX) / (2*fWidth)); | |
| SkScalar dx = SkIntToScalar(2 * fWidth); | |
| for (int i = 0; i < N*10; ++i) { | |
| SkRect r = rect; | |
| for (int j = 0; j < count; ++j) { | |
| canvas->drawRect(r, p); | |
| r.offset(dx, 0); | |
| } | |
| } | |
| } | |
| } | |
| private: | |
| using INHERITED = DashBench; | |
| }; | |
| static SkPath make_unit_star(int n) { | |
| SkScalar rad = -SK_ScalarPI / 2; | |
| const SkScalar drad = (n >> 1) * SK_ScalarPI * 2 / n; | |
| SkPathBuilder builder; | |
| builder.moveTo(0, -SK_Scalar1); | |
| for (int i = 1; i < n; i++) { | |
| rad += drad; | |
| builder.lineTo(SkScalarCos(rad), SkScalarSin(rad)); | |
| } | |
| builder.close(); | |
| return builder.detach(); | |
| } | |
| static SkPath make_poly() { | |
| return make_unit_star(9).makeTransform(SkMatrix::Scale(100, 100)); | |
| } | |
| static SkPath make_quad() { | |
| SkScalar x0 = SkIntToScalar(10); | |
| SkScalar y0 = SkIntToScalar(10); | |
| SkPathBuilder builder; | |
| builder.moveTo(x0, y0); | |
| builder.quadTo(x0, y0 + 400 * SK_Scalar1, | |
| x0 + 600 * SK_Scalar1, y0 + 400 * SK_Scalar1); | |
| return builder.detach(); | |
| } | |
| static SkPath make_cubic() { | |
| SkScalar x0 = SkIntToScalar(10); | |
| SkScalar y0 = SkIntToScalar(10); | |
| SkPathBuilder builder; | |
| builder.moveTo(x0, y0); | |
| builder.cubicTo(x0, y0 + 400 * SK_Scalar1, | |
| x0 + 600 * SK_Scalar1, y0 + 400 * SK_Scalar1, | |
| x0 + 600 * SK_Scalar1, y0); | |
| return builder.detach(); | |
| } | |
| class MakeDashBench : public Benchmark { | |
| SkString fName; | |
| SkPath fPath; | |
| sk_sp<SkPathEffect> fPE; | |
| public: | |
| MakeDashBench(SkPath(*proc)(), const char name[]) { | |
| fName.printf("makedash_%s", name); | |
| fPath = proc(); | |
| SkScalar vals[] = { SkIntToScalar(4), SkIntToScalar(4) }; | |
| fPE = SkDashPathEffect::Make(vals, 0); | |
| } | |
| protected: | |
| const char* onGetName() override { | |
| return fName.c_str(); | |
| } | |
| void onDraw(int loops, SkCanvas*) override { | |
| SkPathBuilder dst; | |
| for (int i = 0; i < loops; ++i) { | |
| SkStrokeRec rec(SkStrokeRec::kHairline_InitStyle); | |
| fPE->filterPath(&dst, fPath, &rec); | |
| dst.reset(); | |
| } | |
| } | |
| private: | |
| using INHERITED = Benchmark; | |
| }; | |
| /* | |
| * We try to special case square dashes (intervals are equal to strokewidth). | |
| */ | |
| class DashLineBench : public Benchmark { | |
| SkString fName; | |
| SkScalar fStrokeWidth; | |
| bool fIsRound; | |
| sk_sp<SkPathEffect> fPE; | |
| public: | |
| DashLineBench(SkScalar width, bool isRound) { | |
| fName.printf("dashline_%g_%s", width, isRound ? "circle" : "square"); | |
| fStrokeWidth = width; | |
| fIsRound = isRound; | |
| SkScalar vals[] = { SK_Scalar1, SK_Scalar1 }; | |
| fPE = SkDashPathEffect::Make(vals, 0); | |
| } | |
| protected: | |
| const char* onGetName() override { | |
| return fName.c_str(); | |
| } | |
| void onDraw(int loops, SkCanvas* canvas) override { | |
| SkPaint paint; | |
| this->setupPaint(&paint); | |
| paint.setStrokeWidth(fStrokeWidth); | |
| paint.setStrokeCap(fIsRound ? SkPaint::kRound_Cap : SkPaint::kSquare_Cap); | |
| paint.setPathEffect(fPE); | |
| for (int i = 0; i < loops; ++i) { | |
| canvas->drawLine(10 * SK_Scalar1, 10 * SK_Scalar1, | |
| 640 * SK_Scalar1, 10 * SK_Scalar1, paint); | |
| } | |
| } | |
| private: | |
| using INHERITED = Benchmark; | |
| }; | |
| class DrawPointsDashingBench : public Benchmark { | |
| SkString fName; | |
| int fStrokeWidth; | |
| bool fDoAA; | |
| sk_sp<SkPathEffect> fPathEffect; | |
| public: | |
| DrawPointsDashingBench(int dashLength, int strokeWidth, bool doAA) | |
| { | |
| fName.printf("drawpointsdash_%d_%d%s", dashLength, strokeWidth, doAA ? "_aa" : "_bw"); | |
| fStrokeWidth = strokeWidth; | |
| fDoAA = doAA; | |
| SkScalar vals[] = { SkIntToScalar(dashLength), SkIntToScalar(dashLength) }; | |
| fPathEffect = SkDashPathEffect::Make(vals, SK_Scalar1); | |
| } | |
| protected: | |
| const char* onGetName() override { | |
| return fName.c_str(); | |
| } | |
| void onDraw(int loops, SkCanvas* canvas) override { | |
| SkPaint p; | |
| this->setupPaint(&p); | |
| p.setColor(SK_ColorBLACK); | |
| p.setStyle(SkPaint::kStroke_Style); | |
| p.setStrokeWidth(SkIntToScalar(fStrokeWidth)); | |
| p.setPathEffect(fPathEffect); | |
| p.setAntiAlias(fDoAA); | |
| SkPoint pts[2] = { | |
| { SkIntToScalar(10), 0 }, | |
| { SkIntToScalar(640), 0 } | |
| }; | |
| for (int i = 0; i < loops; ++i) { | |
| pts[0].fY = pts[1].fY = SkIntToScalar(i % 480); | |
| canvas->drawPoints(SkCanvas::kLines_PointMode, pts, p); | |
| } | |
| } | |
| private: | |
| using INHERITED = Benchmark; | |
| }; | |
| // Want to test how we handle dashing when 99% of the dash is clipped out | |
| class GiantDashBench : public Benchmark { | |
| SkString fName; | |
| SkScalar fStrokeWidth; | |
| SkPoint fPts[2]; | |
| sk_sp<SkPathEffect> fPathEffect; | |
| public: | |
| enum LineType { | |
| kHori_LineType, | |
| kVert_LineType, | |
| kDiag_LineType, | |
| kLineTypeCount | |
| }; | |
| static const char* LineTypeName(LineType lt) { | |
| static constexpr auto gNames = std::to_array<const char*>({"hori", "vert", "diag"}); | |
| static_assert(kLineTypeCount == std::size(gNames), "names_wrong_size"); | |
| return gNames[lt]; | |
| } | |
| GiantDashBench(LineType lt, SkScalar width) { | |
| fName.printf("giantdashline_%s_%g", LineTypeName(lt), width); | |
| fStrokeWidth = width; | |
| // deliberately pick intervals that won't be caught by asPoints(), so | |
| // we can test the filterPath code-path. | |
| const SkScalar intervals[] = { 20, 10, 10, 10 }; | |
| fPathEffect = SkDashPathEffect::Make(intervals, 0); | |
| SkScalar cx = 640 / 2; // center X | |
| SkScalar cy = 480 / 2; // center Y | |
| SkMatrix matrix; | |
| switch (lt) { | |
| case kHori_LineType: | |
| matrix.setIdentity(); | |
| break; | |
| case kVert_LineType: | |
| matrix.setRotate(90, cx, cy); | |
| break; | |
| case kDiag_LineType: | |
| matrix.setRotate(45, cx, cy); | |
| break; | |
| case kLineTypeCount: | |
| // Not a real enum value. | |
| break; | |
| } | |
| const SkScalar overshoot = 100*1000; | |
| const SkPoint pts[2] = { | |
| { -overshoot, cy }, { 640 + overshoot, cy } | |
| }; | |
| matrix.mapPoints(fPts, pts); | |
| } | |
| protected: | |
| const char* onGetName() override { | |
| return fName.c_str(); | |
| } | |
| void onDraw(int loops, SkCanvas* canvas) override { | |
| SkPaint p; | |
| this->setupPaint(&p); | |
| p.setStyle(SkPaint::kStroke_Style); | |
| p.setStrokeWidth(fStrokeWidth); | |
| p.setPathEffect(fPathEffect); | |
| for (int i = 0; i < loops; i++) { | |
| canvas->drawPoints(SkCanvas::kLines_PointMode, fPts, p); | |
| } | |
| } | |
| private: | |
| using INHERITED = Benchmark; | |
| }; | |
| // Want to test how we draw a dashed grid (like what is used in spreadsheets) of many | |
| // small dashed lines switching back and forth between horizontal and vertical | |
| class DashGridBench : public Benchmark { | |
| SkString fName; | |
| int fStrokeWidth; | |
| bool fDoAA; | |
| sk_sp<SkPathEffect> fPathEffect; | |
| public: | |
| DashGridBench(int dashLength, int strokeWidth, bool doAA) { | |
| fName.printf("dashgrid_%d_%d%s", dashLength, strokeWidth, doAA ? "_aa" : "_bw"); | |
| fStrokeWidth = strokeWidth; | |
| fDoAA = doAA; | |
| SkScalar vals[] = { SkIntToScalar(dashLength), SkIntToScalar(dashLength) }; | |
| fPathEffect = SkDashPathEffect::Make(vals, SK_Scalar1); | |
| } | |
| protected: | |
| const char* onGetName() override { | |
| return fName.c_str(); | |
| } | |
| void onDraw(int loops, SkCanvas* canvas) override { | |
| SkPaint p; | |
| this->setupPaint(&p); | |
| p.setColor(SK_ColorBLACK); | |
| p.setStyle(SkPaint::kStroke_Style); | |
| p.setStrokeWidth(SkIntToScalar(fStrokeWidth)); | |
| p.setPathEffect(fPathEffect); | |
| p.setAntiAlias(fDoAA); | |
| SkPoint pts[4] = { | |
| { SkIntToScalar(0), 20.5f }, | |
| { SkIntToScalar(20), 20.5f }, | |
| { 20.5f, SkIntToScalar(0) }, | |
| { 20.5f, SkIntToScalar(20) } | |
| }; | |
| for (int i = 0; i < loops; ++i) { | |
| for (int j = 0; j < 10; ++j) { | |
| for (int k = 0; k < 10; ++k) { | |
| // Horizontal line | |
| SkPoint horPts[2]; | |
| horPts[0].fX = pts[0].fX + k * 22.f; | |
| horPts[0].fY = pts[0].fY + j * 22.f; | |
| horPts[1].fX = pts[1].fX + k * 22.f; | |
| horPts[1].fY = pts[1].fY + j * 22.f; | |
| canvas->drawPoints(SkCanvas::kLines_PointMode, horPts, p); | |
| // Vertical line | |
| SkPoint vertPts[2]; | |
| vertPts[0].fX = pts[2].fX + k * 22.f; | |
| vertPts[0].fY = pts[2].fY + j * 22.f; | |
| vertPts[1].fX = pts[3].fX + k * 22.f; | |
| vertPts[1].fY = pts[3].fY + j * 22.f; | |
| canvas->drawPoints(SkCanvas::kLines_PointMode, vertPts, p); | |
| } | |
| } | |
| } | |
| } | |
| private: | |
| using INHERITED = Benchmark; | |
| }; | |
| /////////////////////////////////////////////////////////////////////////////// | |
| static const SkScalar gDots[] = { SK_Scalar1, SK_Scalar1 }; | |
| #define PARAM(array) array, std::size(array) | |
| DEF_BENCH( return new DashBench(PARAM(gDots), 0); ) | |
| DEF_BENCH( return new DashBench(PARAM(gDots), 1); ) | |
| DEF_BENCH( return new DashBench(PARAM(gDots), 1, true); ) | |
| DEF_BENCH( return new DashBench(PARAM(gDots), 4); ) | |
| DEF_BENCH( return new MakeDashBench(make_poly, "poly"); ) | |
| DEF_BENCH( return new MakeDashBench(make_quad, "quad"); ) | |
| DEF_BENCH( return new MakeDashBench(make_cubic, "cubic"); ) | |
| DEF_BENCH( return new DashLineBench(0, false); ) | |
| DEF_BENCH( return new DashLineBench(SK_Scalar1, false); ) | |
| DEF_BENCH( return new DashLineBench(2 * SK_Scalar1, false); ) | |
| DEF_BENCH( return new DashLineBench(0, true); ) | |
| DEF_BENCH( return new DashLineBench(SK_Scalar1, true); ) | |
| DEF_BENCH( return new DashLineBench(2 * SK_Scalar1, true); ) | |
| DEF_BENCH( return new DrawPointsDashingBench(1, 1, false); ) | |
| DEF_BENCH( return new DrawPointsDashingBench(1, 1, true); ) | |
| DEF_BENCH( return new DrawPointsDashingBench(3, 1, false); ) | |
| DEF_BENCH( return new DrawPointsDashingBench(3, 1, true); ) | |
| DEF_BENCH( return new DrawPointsDashingBench(5, 5, false); ) | |
| DEF_BENCH( return new DrawPointsDashingBench(5, 5, true); ) | |
| /* Disable the GiantDashBench for Android devices until we can better control | |
| * the memory usage. (https://code.google.com/p/skia/issues/detail?id=1430) | |
| */ | |
| #ifndef SK_BUILD_FOR_ANDROID | |
| DEF_BENCH( return new GiantDashBench(GiantDashBench::kHori_LineType, 0); ) | |
| DEF_BENCH( return new GiantDashBench(GiantDashBench::kVert_LineType, 0); ) | |
| DEF_BENCH( return new GiantDashBench(GiantDashBench::kDiag_LineType, 0); ) | |
| // pass 2 to explicitly avoid any 1-is-the-same-as-hairline special casing | |
| // hori_2 is just too slow to enable at the moment | |
| DEF_BENCH( return new GiantDashBench(GiantDashBench::kHori_LineType, 2); ) | |
| DEF_BENCH( return new GiantDashBench(GiantDashBench::kVert_LineType, 2); ) | |
| DEF_BENCH( return new GiantDashBench(GiantDashBench::kDiag_LineType, 2); ) | |
| DEF_BENCH( return new DashGridBench(1, 1, true); ) | |
| DEF_BENCH( return new DashGridBench(1, 1, false); ) | |
| DEF_BENCH( return new DashGridBench(3, 1, true); ) | |
| DEF_BENCH( return new DashGridBench(3, 1, false); ) | |
| #endif |