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