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