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