1/* 2 * Copyright 2011 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#include "bench/Benchmark.h" 8#include "include/core/SkBitmap.h" 9#include "include/core/SkCanvas.h" 10#include "include/core/SkPaint.h" 11#include "include/core/SkPath.h" 12#include "include/core/SkString.h" 13#include "include/core/SkStrokeRec.h" 14#include "include/effects/SkDashPathEffect.h" 15#include "include/private/SkTDArray.h" 16#include "include/utils/SkRandom.h" 17 18 19/* 20 * Cases to consider: 21 * 22 * 1. antialiasing on/off (esp. width <= 1) 23 * 2. strokewidth == 0, 1, 2 24 * 3. hline, vline, diagonal, rect, oval 25 * 4. dots [1,1] ([N,N] where N=strokeWidth?) or arbitrary (e.g. [2,1] or [1,2,3,2]) 26 */ 27static void path_hline(SkPath* path) { 28 path->moveTo(SkIntToScalar(10), SkIntToScalar(10)); 29 path->lineTo(SkIntToScalar(600), SkIntToScalar(10)); 30} 31 32class DashBench : public Benchmark { 33protected: 34 SkString fName; 35 SkTDArray<SkScalar> fIntervals; 36 int fWidth; 37 SkPoint fPts[2]; 38 bool fDoClip; 39 40public: 41 DashBench(const SkScalar intervals[], int count, int width, 42 bool doClip = false) { 43 fIntervals.append(count, intervals); 44 for (int i = 0; i < count; ++i) { 45 fIntervals[i] *= width; 46 } 47 fWidth = width; 48 fName.printf("dash_%d_%s", width, doClip ? "clipped" : "noclip"); 49 fDoClip = doClip; 50 51 fPts[0].set(SkIntToScalar(10), SkIntToScalar(10)); 52 fPts[1].set(SkIntToScalar(600), SkIntToScalar(10)); 53 } 54 55 virtual void makePath(SkPath* path) { 56 path_hline(path); 57 } 58 59protected: 60 const char* onGetName() override { 61 return fName.c_str(); 62 } 63 64 void onDraw(int loops, SkCanvas* canvas) override { 65 SkPaint paint; 66 this->setupPaint(&paint); 67 paint.setStyle(SkPaint::kStroke_Style); 68 paint.setStrokeWidth(SkIntToScalar(fWidth)); 69 paint.setAntiAlias(false); 70 71 SkPath path; 72 this->makePath(&path); 73 74 paint.setPathEffect(SkDashPathEffect::Make(fIntervals.begin(), fIntervals.count(), 0)); 75 76 if (fDoClip) { 77 SkRect r = path.getBounds(); 78 r.inset(-SkIntToScalar(20), -SkIntToScalar(20)); 79 // now move it so we don't intersect 80 r.offset(0, r.height() * 3 / 2); 81 canvas->clipRect(r); 82 } 83 84 this->handlePath(canvas, path, paint, loops); 85 } 86 87 virtual void handlePath(SkCanvas* canvas, const SkPath& path, 88 const SkPaint& paint, int N) { 89 for (int i = 0; i < N; ++i) { 90// canvas->drawPoints(SkCanvas::kLines_PointMode, 2, fPts, paint); 91 canvas->drawPath(path, paint); 92 } 93 } 94 95private: 96 using INHERITED = Benchmark; 97}; 98 99class RectDashBench : public DashBench { 100public: 101 RectDashBench(const SkScalar intervals[], int count, int width) 102 : INHERITED(intervals, count, width) { 103 fName.append("_rect"); 104 } 105 106protected: 107 void handlePath(SkCanvas* canvas, const SkPath& path, const SkPaint& paint, int N) override { 108 SkPoint pts[2]; 109 if (!path.isLine(pts) || pts[0].fY != pts[1].fY) { 110 this->INHERITED::handlePath(canvas, path, paint, N); 111 } else { 112 SkRect rect; 113 rect.fLeft = pts[0].fX; 114 rect.fTop = pts[0].fY - paint.getStrokeWidth() / 2; 115 rect.fRight = rect.fLeft + SkIntToScalar(fWidth); 116 rect.fBottom = rect.fTop + paint.getStrokeWidth(); 117 118 SkPaint p(paint); 119 p.setStyle(SkPaint::kFill_Style); 120 p.setPathEffect(nullptr); 121 122 int count = SkScalarRoundToInt((pts[1].fX - pts[0].fX) / (2*fWidth)); 123 SkScalar dx = SkIntToScalar(2 * fWidth); 124 125 for (int i = 0; i < N*10; ++i) { 126 SkRect r = rect; 127 for (int j = 0; j < count; ++j) { 128 canvas->drawRect(r, p); 129 r.offset(dx, 0); 130 } 131 } 132 } 133 } 134 135private: 136 using INHERITED = DashBench; 137}; 138 139static void make_unit_star(SkPath* path, int n) { 140 SkScalar rad = -SK_ScalarPI / 2; 141 const SkScalar drad = (n >> 1) * SK_ScalarPI * 2 / n; 142 143 path->moveTo(0, -SK_Scalar1); 144 for (int i = 1; i < n; i++) { 145 rad += drad; 146 path->lineTo(SkScalarCos(rad), SkScalarSin(rad)); 147 } 148 path->close(); 149} 150 151static void make_poly(SkPath* path) { 152 make_unit_star(path, 9); 153 const SkMatrix matrix = SkMatrix::Scale(100, 100); 154 path->transform(matrix); 155} 156 157static void make_quad(SkPath* path) { 158 SkScalar x0 = SkIntToScalar(10); 159 SkScalar y0 = SkIntToScalar(10); 160 path->moveTo(x0, y0); 161 path->quadTo(x0, y0 + 400 * SK_Scalar1, 162 x0 + 600 * SK_Scalar1, y0 + 400 * SK_Scalar1); 163} 164 165static void make_cubic(SkPath* path) { 166 SkScalar x0 = SkIntToScalar(10); 167 SkScalar y0 = SkIntToScalar(10); 168 path->moveTo(x0, y0); 169 path->cubicTo(x0, y0 + 400 * SK_Scalar1, 170 x0 + 600 * SK_Scalar1, y0 + 400 * SK_Scalar1, 171 x0 + 600 * SK_Scalar1, y0); 172} 173 174class MakeDashBench : public Benchmark { 175 SkString fName; 176 SkPath fPath; 177 sk_sp<SkPathEffect> fPE; 178 179public: 180 MakeDashBench(void (*proc)(SkPath*), const char name[]) { 181 fName.printf("makedash_%s", name); 182 proc(&fPath); 183 184 SkScalar vals[] = { SkIntToScalar(4), SkIntToScalar(4) }; 185 fPE = SkDashPathEffect::Make(vals, 2, 0); 186 } 187 188protected: 189 const char* onGetName() override { 190 return fName.c_str(); 191 } 192 193 void onDraw(int loops, SkCanvas*) override { 194 SkPath dst; 195 for (int i = 0; i < loops; ++i) { 196 SkStrokeRec rec(SkStrokeRec::kHairline_InitStyle); 197 198 fPE->filterPath(&dst, fPath, &rec, nullptr); 199 dst.rewind(); 200 } 201 } 202 203private: 204 using INHERITED = Benchmark; 205}; 206 207/* 208 * We try to special case square dashes (intervals are equal to strokewidth). 209 */ 210class DashLineBench : public Benchmark { 211 SkString fName; 212 SkScalar fStrokeWidth; 213 bool fIsRound; 214 sk_sp<SkPathEffect> fPE; 215 216public: 217 DashLineBench(SkScalar width, bool isRound) { 218 fName.printf("dashline_%g_%s", SkScalarToFloat(width), isRound ? "circle" : "square"); 219 fStrokeWidth = width; 220 fIsRound = isRound; 221 222 SkScalar vals[] = { SK_Scalar1, SK_Scalar1 }; 223 fPE = SkDashPathEffect::Make(vals, 2, 0); 224 } 225 226protected: 227 const char* onGetName() override { 228 return fName.c_str(); 229 } 230 231 void onDraw(int loops, SkCanvas* canvas) override { 232 SkPaint paint; 233 this->setupPaint(&paint); 234 paint.setStrokeWidth(fStrokeWidth); 235 paint.setStrokeCap(fIsRound ? SkPaint::kRound_Cap : SkPaint::kSquare_Cap); 236 paint.setPathEffect(fPE); 237 for (int i = 0; i < loops; ++i) { 238 canvas->drawLine(10 * SK_Scalar1, 10 * SK_Scalar1, 239 640 * SK_Scalar1, 10 * SK_Scalar1, paint); 240 } 241 } 242 243private: 244 using INHERITED = Benchmark; 245}; 246 247class DrawPointsDashingBench : public Benchmark { 248 SkString fName; 249 int fStrokeWidth; 250 bool fDoAA; 251 252 sk_sp<SkPathEffect> fPathEffect; 253 254public: 255 DrawPointsDashingBench(int dashLength, int strokeWidth, bool doAA) 256 { 257 fName.printf("drawpointsdash_%d_%d%s", dashLength, strokeWidth, doAA ? "_aa" : "_bw"); 258 fStrokeWidth = strokeWidth; 259 fDoAA = doAA; 260 261 SkScalar vals[] = { SkIntToScalar(dashLength), SkIntToScalar(dashLength) }; 262 fPathEffect = SkDashPathEffect::Make(vals, 2, SK_Scalar1); 263 } 264 265protected: 266 const char* onGetName() override { 267 return fName.c_str(); 268 } 269 270 void onDraw(int loops, SkCanvas* canvas) override { 271 SkPaint p; 272 this->setupPaint(&p); 273 p.setColor(SK_ColorBLACK); 274 p.setStyle(SkPaint::kStroke_Style); 275 p.setStrokeWidth(SkIntToScalar(fStrokeWidth)); 276 p.setPathEffect(fPathEffect); 277 p.setAntiAlias(fDoAA); 278 279 SkPoint pts[2] = { 280 { SkIntToScalar(10), 0 }, 281 { SkIntToScalar(640), 0 } 282 }; 283 284 for (int i = 0; i < loops; ++i) { 285 pts[0].fY = pts[1].fY = SkIntToScalar(i % 480); 286 canvas->drawPoints(SkCanvas::kLines_PointMode, 2, pts, p); 287 } 288 } 289 290private: 291 using INHERITED = Benchmark; 292}; 293 294// Want to test how we handle dashing when 99% of the dash is clipped out 295class GiantDashBench : public Benchmark { 296 SkString fName; 297 SkScalar fStrokeWidth; 298 SkPoint fPts[2]; 299 sk_sp<SkPathEffect> fPathEffect; 300 301public: 302 enum LineType { 303 kHori_LineType, 304 kVert_LineType, 305 kDiag_LineType, 306 kLineTypeCount 307 }; 308 309 static const char* LineTypeName(LineType lt) { 310 static const char* gNames[] = { "hori", "vert", "diag" }; 311 static_assert(kLineTypeCount == SK_ARRAY_COUNT(gNames), "names_wrong_size"); 312 return gNames[lt]; 313 } 314 315 GiantDashBench(LineType lt, SkScalar width) { 316 fName.printf("giantdashline_%s_%g", LineTypeName(lt), width); 317 fStrokeWidth = width; 318 319 // deliberately pick intervals that won't be caught by asPoints(), so 320 // we can test the filterPath code-path. 321 const SkScalar intervals[] = { 20, 10, 10, 10 }; 322 fPathEffect = SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 0); 323 324 SkScalar cx = 640 / 2; // center X 325 SkScalar cy = 480 / 2; // center Y 326 SkMatrix matrix; 327 328 switch (lt) { 329 case kHori_LineType: 330 matrix.setIdentity(); 331 break; 332 case kVert_LineType: 333 matrix.setRotate(90, cx, cy); 334 break; 335 case kDiag_LineType: 336 matrix.setRotate(45, cx, cy); 337 break; 338 case kLineTypeCount: 339 // Not a real enum value. 340 break; 341 } 342 343 const SkScalar overshoot = 100*1000; 344 const SkPoint pts[2] = { 345 { -overshoot, cy }, { 640 + overshoot, cy } 346 }; 347 matrix.mapPoints(fPts, pts, 2); 348 } 349 350protected: 351 const char* onGetName() override { 352 return fName.c_str(); 353 } 354 355 void onDraw(int loops, SkCanvas* canvas) override { 356 SkPaint p; 357 this->setupPaint(&p); 358 p.setStyle(SkPaint::kStroke_Style); 359 p.setStrokeWidth(fStrokeWidth); 360 p.setPathEffect(fPathEffect); 361 362 for (int i = 0; i < loops; i++) { 363 canvas->drawPoints(SkCanvas::kLines_PointMode, 2, fPts, p); 364 } 365 } 366 367private: 368 using INHERITED = Benchmark; 369}; 370 371// Want to test how we draw a dashed grid (like what is used in spreadsheets) of many 372// small dashed lines switching back and forth between horizontal and vertical 373class DashGridBench : public Benchmark { 374 SkString fName; 375 int fStrokeWidth; 376 bool fDoAA; 377 378 sk_sp<SkPathEffect> fPathEffect; 379 380public: 381 DashGridBench(int dashLength, int strokeWidth, bool doAA) { 382 fName.printf("dashgrid_%d_%d%s", dashLength, strokeWidth, doAA ? "_aa" : "_bw"); 383 fStrokeWidth = strokeWidth; 384 fDoAA = doAA; 385 386 SkScalar vals[] = { SkIntToScalar(dashLength), SkIntToScalar(dashLength) }; 387 fPathEffect = SkDashPathEffect::Make(vals, 2, SK_Scalar1); 388 } 389 390protected: 391 const char* onGetName() override { 392 return fName.c_str(); 393 } 394 395 void onDraw(int loops, SkCanvas* canvas) override { 396 SkPaint p; 397 this->setupPaint(&p); 398 p.setColor(SK_ColorBLACK); 399 p.setStyle(SkPaint::kStroke_Style); 400 p.setStrokeWidth(SkIntToScalar(fStrokeWidth)); 401 p.setPathEffect(fPathEffect); 402 p.setAntiAlias(fDoAA); 403 404 SkPoint pts[4] = { 405 { SkIntToScalar(0), 20.5f }, 406 { SkIntToScalar(20), 20.5f }, 407 { 20.5f, SkIntToScalar(0) }, 408 { 20.5f, SkIntToScalar(20) } 409 }; 410 411 for (int i = 0; i < loops; ++i) { 412 for (int j = 0; j < 10; ++j) { 413 for (int k = 0; k < 10; ++k) { 414 // Horizontal line 415 SkPoint horPts[2]; 416 horPts[0].fX = pts[0].fX + k * 22.f; 417 horPts[0].fY = pts[0].fY + j * 22.f; 418 horPts[1].fX = pts[1].fX + k * 22.f; 419 horPts[1].fY = pts[1].fY + j * 22.f; 420 canvas->drawPoints(SkCanvas::kLines_PointMode, 2, horPts, p); 421 422 // Vertical line 423 SkPoint vertPts[2]; 424 vertPts[0].fX = pts[2].fX + k * 22.f; 425 vertPts[0].fY = pts[2].fY + j * 22.f; 426 vertPts[1].fX = pts[3].fX + k * 22.f; 427 vertPts[1].fY = pts[3].fY + j * 22.f; 428 canvas->drawPoints(SkCanvas::kLines_PointMode, 2, vertPts, p); 429 } 430 } 431 } 432 } 433 434private: 435 using INHERITED = Benchmark; 436}; 437 438/////////////////////////////////////////////////////////////////////////////// 439 440static const SkScalar gDots[] = { SK_Scalar1, SK_Scalar1 }; 441 442#define PARAM(array) array, SK_ARRAY_COUNT(array) 443 444DEF_BENCH( return new DashBench(PARAM(gDots), 0); ) 445DEF_BENCH( return new DashBench(PARAM(gDots), 1); ) 446DEF_BENCH( return new DashBench(PARAM(gDots), 1, true); ) 447DEF_BENCH( return new DashBench(PARAM(gDots), 4); ) 448DEF_BENCH( return new MakeDashBench(make_poly, "poly"); ) 449DEF_BENCH( return new MakeDashBench(make_quad, "quad"); ) 450DEF_BENCH( return new MakeDashBench(make_cubic, "cubic"); ) 451DEF_BENCH( return new DashLineBench(0, false); ) 452DEF_BENCH( return new DashLineBench(SK_Scalar1, false); ) 453DEF_BENCH( return new DashLineBench(2 * SK_Scalar1, false); ) 454DEF_BENCH( return new DashLineBench(0, true); ) 455DEF_BENCH( return new DashLineBench(SK_Scalar1, true); ) 456DEF_BENCH( return new DashLineBench(2 * SK_Scalar1, true); ) 457 458DEF_BENCH( return new DrawPointsDashingBench(1, 1, false); ) 459DEF_BENCH( return new DrawPointsDashingBench(1, 1, true); ) 460DEF_BENCH( return new DrawPointsDashingBench(3, 1, false); ) 461DEF_BENCH( return new DrawPointsDashingBench(3, 1, true); ) 462DEF_BENCH( return new DrawPointsDashingBench(5, 5, false); ) 463DEF_BENCH( return new DrawPointsDashingBench(5, 5, true); ) 464 465/* Disable the GiantDashBench for Android devices until we can better control 466 * the memory usage. (https://code.google.com/p/skia/issues/detail?id=1430) 467 */ 468#ifndef SK_BUILD_FOR_ANDROID 469DEF_BENCH( return new GiantDashBench(GiantDashBench::kHori_LineType, 0); ) 470DEF_BENCH( return new GiantDashBench(GiantDashBench::kVert_LineType, 0); ) 471DEF_BENCH( return new GiantDashBench(GiantDashBench::kDiag_LineType, 0); ) 472 473// pass 2 to explicitly avoid any 1-is-the-same-as-hairline special casing 474 475// hori_2 is just too slow to enable at the moment 476DEF_BENCH( return new GiantDashBench(GiantDashBench::kHori_LineType, 2); ) 477DEF_BENCH( return new GiantDashBench(GiantDashBench::kVert_LineType, 2); ) 478DEF_BENCH( return new GiantDashBench(GiantDashBench::kDiag_LineType, 2); ) 479 480DEF_BENCH( return new DashGridBench(1, 1, true); ) 481DEF_BENCH( return new DashGridBench(1, 1, false); ) 482DEF_BENCH( return new DashGridBench(3, 1, true); ) 483DEF_BENCH( return new DashGridBench(3, 1, false); ) 484#endif 485