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