1 /*
2  * Copyright 2012 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 
8 #include "bench/Benchmark.h"
9 #include "include/core/SkBitmap.h"
10 #include "include/core/SkCanvas.h"
11 #include "include/core/SkImage.h"
12 #include "include/core/SkM44.h"
13 #include "include/core/SkPaint.h"
14 #include "include/core/SkShader.h"
15 #include "include/core/SkString.h"
16 #include "include/core/SkVertices.h"
17 #include "include/utils/SkRandom.h"
18 
19 // This bench simulates the calls Skia sees from various HTML5 canvas
20 // game bench marks
21 class GameBench : public Benchmark {
22 public:
23     enum Type {
24         kScale_Type,
25         kTranslate_Type,
26         kRotate_Type
27     };
28 
29     enum Clear {
30         kFull_Clear,
31         kPartial_Clear
32     };
33 
GameBench(Type type,Clear clear,bool aligned=false,bool useAtlas=false,bool useDrawVertices=false)34     GameBench(Type type, Clear clear,
35               bool aligned = false, bool useAtlas = false,
36               bool useDrawVertices = false)
37         : fType(type)
38         , fClear(clear)
39         , fAligned(aligned)
40         , fUseAtlas(useAtlas)
41         , fUseDrawVertices(useDrawVertices)
42         , fName("game")
43         , fNumSaved(0)
44         , fInitialized(false) {
45 
46         switch (fType) {
47         case kScale_Type:
48             fName.append("_scale");
49             break;
50         case kTranslate_Type:
51             fName.append("_trans");
52             break;
53         case kRotate_Type:
54             fName.append("_rot");
55             break;
56         }
57 
58         if (aligned) {
59             fName.append("_aligned");
60         }
61 
62         if (kPartial_Clear == clear) {
63             fName.append("_partial");
64         } else {
65             fName.append("_full");
66         }
67 
68         if (useAtlas) {
69             fName.append("_atlas");
70         }
71 
72         if (useDrawVertices) {
73             fName.append("_drawVerts");
74         }
75 
76         // It's HTML 5 canvas, so always AA
77         fName.append("_aa");
78     }
79 
80 protected:
onGetName()81     const char* onGetName() override {
82         return fName.c_str();
83     }
84 
onDelayedSetup()85     void onDelayedSetup() override {
86         if (!fInitialized) {
87             this->makeCheckerboard();
88             this->makeAtlas();
89             fInitialized = true;
90         }
91     }
92 
onDraw(int loops,SkCanvas * canvas)93     void onDraw(int loops, SkCanvas* canvas) override {
94         SkRandom scaleRand;
95         SkRandom transRand;
96         SkRandom rotRand;
97 
98         int width, height;
99         if (fUseAtlas) {
100             width = kAtlasCellWidth;
101             height = kAtlasCellHeight;
102         } else {
103             width = kCheckerboardWidth;
104             height = kCheckerboardHeight;
105         }
106 
107         SkPaint clearPaint;
108         clearPaint.setColor(0xFF000000);
109         clearPaint.setAntiAlias(true);
110 
111         SkISize size = canvas->getBaseLayerSize();
112 
113         SkScalar maxTransX, maxTransY;
114 
115         if (kScale_Type == fType) {
116             maxTransX = size.fWidth  - (1.5f * width);
117             maxTransY = size.fHeight - (1.5f * height);
118         } else if (kTranslate_Type == fType) {
119             maxTransX = SkIntToScalar(size.fWidth  - width);
120             maxTransY = SkIntToScalar(size.fHeight - height);
121         } else {
122             SkASSERT(kRotate_Type == fType);
123             // Yes, some rotations will be off the top and left sides
124             maxTransX = size.fWidth  - SK_ScalarSqrt2 * height;
125             maxTransY = size.fHeight - SK_ScalarSqrt2 * height;
126         }
127 
128         SkMatrix mat;
129         SkRect dst = { 0, 0, SkIntToScalar(width), SkIntToScalar(height) };
130         SkRect clearRect = { -1.0f, -1.0f, width+1.0f, height+1.0f };
131         SkPoint verts[4] = { // for drawVertices path
132             { 0, 0 },
133             { 0, SkIntToScalar(height) },
134             { SkIntToScalar(width), SkIntToScalar(height) },
135             { SkIntToScalar(width), 0 }
136         };
137         uint16_t indices[6] = { 0, 1, 2, 0, 2, 3 };
138 
139         SkPaint p;
140         p.setColor(0xFF000000);
141 
142         SkPaint p2;         // for drawVertices path
143         p2.setColor(0xFF000000);
144         p2.setShader(fAtlas->makeShader(SkSamplingOptions(SkFilterMode::kLinear)));
145 
146         for (int i = 0; i < loops; ++i, ++fNumSaved) {
147             if (0 == i % kNumBeforeClear) {
148                 if (kPartial_Clear == fClear) {
149                     for (int j = 0; j < fNumSaved; ++j) {
150                         canvas->setMatrix(SkMatrix::I());
151                         mat.setTranslate(fSaved[j][0], fSaved[j][1]);
152 
153                         if (kScale_Type == fType) {
154                             mat.preScale(fSaved[j][2], fSaved[j][2]);
155                         } else if (kRotate_Type == fType) {
156                             mat.preRotate(fSaved[j][2]);
157                         }
158 
159                         canvas->concat(mat);
160                         canvas->drawRect(clearRect, clearPaint);
161                     }
162                 } else {
163                     canvas->clear(0xFF000000);
164                 }
165 
166                 fNumSaved = 0;
167             }
168 
169             SkASSERT(fNumSaved < kNumBeforeClear);
170 
171             canvas->setMatrix(SkMatrix::I());
172 
173             fSaved[fNumSaved][0] = transRand.nextRangeScalar(0.0f, maxTransX);
174             fSaved[fNumSaved][1] = transRand.nextRangeScalar(0.0f, maxTransY);
175             if (fAligned) {
176                 // make the translations integer aligned
177                 fSaved[fNumSaved][0] = SkScalarFloorToScalar(fSaved[fNumSaved][0]);
178                 fSaved[fNumSaved][1] = SkScalarFloorToScalar(fSaved[fNumSaved][1]);
179             }
180 
181             mat.setTranslate(fSaved[fNumSaved][0], fSaved[fNumSaved][1]);
182 
183             if (kScale_Type == fType) {
184                 fSaved[fNumSaved][2] = scaleRand.nextRangeScalar(0.5f, 1.5f);
185                 mat.preScale(fSaved[fNumSaved][2], fSaved[fNumSaved][2]);
186             } else if (kRotate_Type == fType) {
187                 fSaved[fNumSaved][2] = rotRand.nextRangeScalar(0.0f, 360.0f);
188                 mat.preRotate(fSaved[fNumSaved][2]);
189             }
190 
191             canvas->concat(mat);
192             if (fUseAtlas) {
193                 const int curCell = i % (kNumAtlasedX * kNumAtlasedY);
194                 SkRect src = SkRect::Make(
195                               fAtlasRects[curCell % (kNumAtlasedX)][curCell / (kNumAtlasedX)]);
196 
197                 if (fUseDrawVertices) {
198                     SkPoint uvs[4] = {
199                         { SkIntToScalar(src.fLeft),  SkIntToScalar(src.fBottom) },
200                         { SkIntToScalar(src.fLeft),  SkIntToScalar(src.fTop) },
201                         { SkIntToScalar(src.fRight), SkIntToScalar(src.fTop) },
202                         { SkIntToScalar(src.fRight), SkIntToScalar(src.fBottom) },
203                     };
204                     canvas->drawVertices(SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode,
205                                                               4, verts, uvs, nullptr, 6, indices),
206                                          SkBlendMode::kModulate, p2);
207                 } else {
208                     canvas->drawImageRect(fAtlas, src, dst, SkSamplingOptions(), &p,
209                                            SkCanvas::kFast_SrcRectConstraint);
210                 }
211             } else {
212                 canvas->drawImageRect(fCheckerboard, dst, SkSamplingOptions(), &p);
213             }
214         }
215     }
216 
217 private:
218     static const int kCheckerboardWidth = 64;
219     static const int kCheckerboardHeight = 128;
220 
221     static const int kAtlasCellWidth = 48;
222     static const int kAtlasCellHeight = 36;
223     static const int kNumAtlasedX = 5;
224     static const int kNumAtlasedY = 5;
225     static const int kAtlasSpacer = 2;
226     static const int kTotAtlasWidth  = kNumAtlasedX * kAtlasCellWidth +
227                                        (kNumAtlasedX+1) * kAtlasSpacer;
228     static const int kTotAtlasHeight = kNumAtlasedY * kAtlasCellHeight +
229                                        (kNumAtlasedY+1) * kAtlasSpacer;
230     static const int kNumBeforeClear = 100;
231 
232     Type     fType;
233     Clear    fClear;
234     bool     fAligned;
235     bool     fUseAtlas;
236     bool     fUseDrawVertices;
237     SkString fName;
238     int      fNumSaved; // num draws stored in 'fSaved'
239     bool     fInitialized;
240 
241     // 0 & 1 are always x & y translate. 2 is either scale or rotate.
242     SkScalar fSaved[kNumBeforeClear][3];
243 
244     sk_sp<SkImage> fCheckerboard, fAtlas;
245     SkIRect  fAtlasRects[kNumAtlasedX][kNumAtlasedY];
246 
247     // Note: the resulting checker board has transparency
makeCheckerboard()248     void makeCheckerboard() {
249         static int kCheckSize = 16;
250 
251         SkBitmap bm;
252         bm.allocN32Pixels(kCheckerboardWidth, kCheckerboardHeight);
253         for (int y = 0; y < kCheckerboardHeight; ++y) {
254             int even = (y / kCheckSize) % 2;
255 
256             SkPMColor* scanline = bm.getAddr32(0, y);
257 
258             for (int x = 0; x < kCheckerboardWidth; ++x) {
259                 if (even == (x / kCheckSize) % 2) {
260                     *scanline++ = 0xFFFF0000;
261                 } else {
262                     *scanline++ = 0x00000000;
263                 }
264             }
265         }
266         fCheckerboard = bm.asImage();
267     }
268 
269     // Note: the resulting atlas has transparency
makeAtlas()270     void makeAtlas() {
271         SkRandom rand;
272 
273         SkColor colors[kNumAtlasedX][kNumAtlasedY];
274 
275         for (int y = 0; y < kNumAtlasedY; ++y) {
276             for (int x = 0; x < kNumAtlasedX; ++x) {
277                 colors[x][y] = rand.nextU() | 0xff000000;
278                 fAtlasRects[x][y] = SkIRect::MakeXYWH(kAtlasSpacer + x * (kAtlasCellWidth + kAtlasSpacer),
279                                                       kAtlasSpacer + y * (kAtlasCellHeight + kAtlasSpacer),
280                                                       kAtlasCellWidth,
281                                                       kAtlasCellHeight);
282             }
283         }
284 
285         SkBitmap bm;
286         bm.allocN32Pixels(kTotAtlasWidth, kTotAtlasHeight);
287 
288         for (int y = 0; y < kTotAtlasHeight; ++y) {
289             int colorY = y / (kAtlasCellHeight + kAtlasSpacer);
290             bool inColorY = (y % (kAtlasCellHeight + kAtlasSpacer)) >= kAtlasSpacer;
291 
292             SkPMColor* scanline = bm.getAddr32(0, y);
293 
294             for (int x = 0; x < kTotAtlasWidth; ++x, ++scanline) {
295                 int colorX = x / (kAtlasCellWidth + kAtlasSpacer);
296                 bool inColorX = (x % (kAtlasCellWidth + kAtlasSpacer)) >= kAtlasSpacer;
297 
298                 if (inColorX && inColorY) {
299                     SkASSERT(colorX < kNumAtlasedX && colorY < kNumAtlasedY);
300                     *scanline = colors[colorX][colorY];
301                 } else {
302                     *scanline = 0x00000000;
303                 }
304             }
305         }
306         fAtlas = bm.asImage();
307     }
308 
309     using INHERITED = Benchmark;
310 };
311 
312 // Partial clear
313 DEF_BENCH(return new GameBench(GameBench::kScale_Type, GameBench::kPartial_Clear);)
314 DEF_BENCH(return new GameBench(GameBench::kTranslate_Type, GameBench::kPartial_Clear);)
315 DEF_BENCH(return new GameBench(GameBench::kTranslate_Type, GameBench::kPartial_Clear, true);)
316 DEF_BENCH(return new GameBench(GameBench::kRotate_Type, GameBench::kPartial_Clear);)
317 
318 // Full clear
319 DEF_BENCH(return new GameBench(GameBench::kScale_Type, GameBench::kFull_Clear);)
320 DEF_BENCH(return new GameBench(GameBench::kTranslate_Type, GameBench::kFull_Clear);)
321 DEF_BENCH(return new GameBench(GameBench::kTranslate_Type, GameBench::kFull_Clear, true);)
322 DEF_BENCH(return new GameBench(GameBench::kRotate_Type, GameBench::kFull_Clear);)
323 
324 // Atlased
325 DEF_BENCH(return new GameBench(GameBench::kTranslate_Type, GameBench::kFull_Clear, false, true);)
326 DEF_BENCH(return new GameBench(
327                          GameBench::kTranslate_Type, GameBench::kFull_Clear, false, true, true);)
328 
329 
330 class CanvasMatrixBench : public Benchmark {
331     SkString fName;
332 public:
333     enum Type {
334         kTranslate_Type,
335         kScale_Type,
336         k2x3_Type,
337         k3x3_Type,
338         k4x4_Type,
339     };
340     Type fType;
341 
CanvasMatrixBench(Type t)342     CanvasMatrixBench(Type t) : fType(t) {
343         fName.set("canvas_matrix");
344         switch (fType) {
345             case kTranslate_Type: fName.append("_trans"); break;
346             case kScale_Type:     fName.append("_scale"); break;
347             case k2x3_Type:       fName.append("_2x3"); break;
348             case k3x3_Type:       fName.append("_3x3"); break;
349             case k4x4_Type:       fName.append("_4x4"); break;
350         }
351     }
352 
353 protected:
onGetName()354     const char* onGetName() override {
355         return fName.c_str();
356     }
357 
onDraw(int loops,SkCanvas * canvas)358     void onDraw(int loops, SkCanvas* canvas) override {
359         SkMatrix m;
360         m.setRotate(1);
361         if (fType == k3x3_Type) {
362             m[7] = 0.0001f;
363         }
364         SkM44 m4(m);
365 
366         for (int i = 0; i < loops; ++i) {
367             canvas->save();
368             for (int j = 0; j < 10000; ++j) {
369                 switch (fType) {
370                     case kTranslate_Type: canvas->translate(0.0001f, 0.0001f); break;
371                     case kScale_Type:     canvas->scale(1.0001f, 0.9999f); break;
372                     case k2x3_Type:       canvas->concat(m); break;
373                     case k3x3_Type:       canvas->concat(m); break;
374                     case k4x4_Type:       canvas->concat(m4); break;
375                 }
376             }
377             canvas->restore();
378         }
379     }
380 
381 private:
382     using INHERITED = Benchmark;
383 };
384 
385 DEF_BENCH(return new CanvasMatrixBench(CanvasMatrixBench::kTranslate_Type));
386 DEF_BENCH(return new CanvasMatrixBench(CanvasMatrixBench::kScale_Type));
387 DEF_BENCH(return new CanvasMatrixBench(CanvasMatrixBench::k2x3_Type));
388 DEF_BENCH(return new CanvasMatrixBench(CanvasMatrixBench::k3x3_Type));
389 DEF_BENCH(return new CanvasMatrixBench(CanvasMatrixBench::k4x4_Type));
390