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