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