| /* | 
 |  * Copyright 2012 Google Inc. | 
 |  * | 
 |  * Use of this source code is governed by a BSD-style license that can be | 
 |  * found in the LICENSE file. | 
 |  */ | 
 |  | 
 | #include "Benchmark.h" | 
 | #include "SkCanvas.h" | 
 | #include "SkPaint.h" | 
 | #include "SkRandom.h" | 
 | #include "SkShader.h" | 
 | #include "SkString.h" | 
 |  | 
 | // This bench simulates the calls Skia sees from various HTML5 canvas | 
 | // game bench marks | 
 | class GameBench : public Benchmark { | 
 | public: | 
 |     enum Type { | 
 |         kScale_Type, | 
 |         kTranslate_Type, | 
 |         kRotate_Type | 
 |     }; | 
 |  | 
 |     enum Clear { | 
 |         kFull_Clear, | 
 |         kPartial_Clear | 
 |     }; | 
 |  | 
 |     GameBench(Type type, Clear clear, | 
 |               bool aligned = false, bool useAtlas = false, | 
 |               bool useDrawVertices = false) | 
 |         : fType(type) | 
 |         , fClear(clear) | 
 |         , fAligned(aligned) | 
 |         , fUseAtlas(useAtlas) | 
 |         , fUseDrawVertices(useDrawVertices) | 
 |         , fName("game") | 
 |         , fNumSaved(0) | 
 |         , fInitialized(false) { | 
 |  | 
 |         switch (fType) { | 
 |         case kScale_Type: | 
 |             fName.append("_scale"); | 
 |             break; | 
 |         case kTranslate_Type: | 
 |             fName.append("_trans"); | 
 |             break; | 
 |         case kRotate_Type: | 
 |             fName.append("_rot"); | 
 |             break; | 
 |         }; | 
 |  | 
 |         if (aligned) { | 
 |             fName.append("_aligned"); | 
 |         } | 
 |  | 
 |         if (kPartial_Clear == clear) { | 
 |             fName.append("_partial"); | 
 |         } else { | 
 |             fName.append("_full"); | 
 |         } | 
 |  | 
 |         if (useAtlas) { | 
 |             fName.append("_atlas"); | 
 |         } | 
 |  | 
 |         if (useDrawVertices) { | 
 |             fName.append("_drawVerts"); | 
 |         } | 
 |  | 
 |         // It's HTML 5 canvas, so always AA | 
 |         fName.append("_aa"); | 
 |     } | 
 |  | 
 | protected: | 
 |     const char* onGetName() override { | 
 |         return fName.c_str(); | 
 |     } | 
 |  | 
 |     void onDelayedSetup() override { | 
 |         if (!fInitialized) { | 
 |             this->makeCheckerboard(); | 
 |             this->makeAtlas(); | 
 |             fInitialized = true; | 
 |         } | 
 |     } | 
 |  | 
 |     void onDraw(int loops, SkCanvas* canvas) override { | 
 |         SkRandom scaleRand; | 
 |         SkRandom transRand; | 
 |         SkRandom rotRand; | 
 |  | 
 |         int width, height; | 
 |         if (fUseAtlas) { | 
 |             width = kAtlasCellWidth; | 
 |             height = kAtlasCellHeight; | 
 |         } else { | 
 |             width = kCheckerboardWidth; | 
 |             height = kCheckerboardHeight; | 
 |         } | 
 |  | 
 |         SkPaint clearPaint; | 
 |         clearPaint.setColor(0xFF000000); | 
 |         clearPaint.setAntiAlias(true); | 
 |  | 
 |         SkISize size = canvas->getDeviceSize(); | 
 |  | 
 |         SkScalar maxTransX, maxTransY; | 
 |  | 
 |         if (kScale_Type == fType) { | 
 |             maxTransX = size.fWidth  - (1.5f * width); | 
 |             maxTransY = size.fHeight - (1.5f * height); | 
 |         } else if (kTranslate_Type == fType) { | 
 |             maxTransX = SkIntToScalar(size.fWidth  - width); | 
 |             maxTransY = SkIntToScalar(size.fHeight - height); | 
 |         } else { | 
 |             SkASSERT(kRotate_Type == fType); | 
 |             // Yes, some rotations will be off the top and left sides | 
 |             maxTransX = size.fWidth  - SK_ScalarSqrt2 * height; | 
 |             maxTransY = size.fHeight - SK_ScalarSqrt2 * height; | 
 |         } | 
 |  | 
 |         SkMatrix mat; | 
 |         SkRect dst = { 0, 0, SkIntToScalar(width), SkIntToScalar(height) }; | 
 |         SkRect clearRect = { -1.0f, -1.0f, width+1.0f, height+1.0f }; | 
 |         SkPoint verts[4] = { // for drawVertices path | 
 |             { 0, 0 }, | 
 |             { 0, SkIntToScalar(height) }, | 
 |             { SkIntToScalar(width), SkIntToScalar(height) }, | 
 |             { SkIntToScalar(width), 0 } | 
 |         }; | 
 |         uint16_t indices[6] = { 0, 1, 2, 0, 2, 3 }; | 
 |  | 
 |         SkPaint p; | 
 |         p.setColor(0xFF000000); | 
 |         p.setFilterQuality(kLow_SkFilterQuality); | 
 |  | 
 |         SkPaint p2;         // for drawVertices path | 
 |         p2.setColor(0xFF000000); | 
 |         p2.setFilterQuality(kLow_SkFilterQuality); | 
 |         p2.setShader(SkShader::MakeBitmapShader(fAtlas, | 
 |                                                 SkShader::kClamp_TileMode, | 
 |                                                 SkShader::kClamp_TileMode)); | 
 |  | 
 |         for (int i = 0; i < loops; ++i, ++fNumSaved) { | 
 |             if (0 == i % kNumBeforeClear) { | 
 |                 if (kPartial_Clear == fClear) { | 
 |                     for (int j = 0; j < fNumSaved; ++j) { | 
 |                         canvas->setMatrix(SkMatrix::I()); | 
 |                         mat.setTranslate(fSaved[j][0], fSaved[j][1]); | 
 |  | 
 |                         if (kScale_Type == fType) { | 
 |                             mat.preScale(fSaved[j][2], fSaved[j][2]); | 
 |                         } else if (kRotate_Type == fType) { | 
 |                             mat.preRotate(fSaved[j][2]); | 
 |                         } | 
 |  | 
 |                         canvas->concat(mat); | 
 |                         canvas->drawRect(clearRect, clearPaint); | 
 |                     } | 
 |                 } else { | 
 |                     canvas->clear(0xFF000000); | 
 |                 } | 
 |  | 
 |                 fNumSaved = 0; | 
 |             } | 
 |  | 
 |             SkASSERT(fNumSaved < kNumBeforeClear); | 
 |  | 
 |             canvas->setMatrix(SkMatrix::I()); | 
 |  | 
 |             fSaved[fNumSaved][0] = transRand.nextRangeScalar(0.0f, maxTransX); | 
 |             fSaved[fNumSaved][1] = transRand.nextRangeScalar(0.0f, maxTransY); | 
 |             if (fAligned) { | 
 |                 // make the translations integer aligned | 
 |                 fSaved[fNumSaved][0] = SkScalarFloorToScalar(fSaved[fNumSaved][0]); | 
 |                 fSaved[fNumSaved][1] = SkScalarFloorToScalar(fSaved[fNumSaved][1]); | 
 |             } | 
 |  | 
 |             mat.setTranslate(fSaved[fNumSaved][0], fSaved[fNumSaved][1]); | 
 |  | 
 |             if (kScale_Type == fType) { | 
 |                 fSaved[fNumSaved][2] = scaleRand.nextRangeScalar(0.5f, 1.5f); | 
 |                 mat.preScale(fSaved[fNumSaved][2], fSaved[fNumSaved][2]); | 
 |             } else if (kRotate_Type == fType) { | 
 |                 fSaved[fNumSaved][2] = rotRand.nextRangeScalar(0.0f, 360.0f); | 
 |                 mat.preRotate(fSaved[fNumSaved][2]); | 
 |             } | 
 |  | 
 |             canvas->concat(mat); | 
 |             if (fUseAtlas) { | 
 |                 const int curCell = i % (kNumAtlasedX * kNumAtlasedY); | 
 |                 SkIRect src = fAtlasRects[curCell % (kNumAtlasedX)][curCell / (kNumAtlasedX)]; | 
 |  | 
 |                 if (fUseDrawVertices) { | 
 |                     SkPoint uvs[4] = { | 
 |                         { SkIntToScalar(src.fLeft),  SkIntToScalar(src.fBottom) }, | 
 |                         { SkIntToScalar(src.fLeft),  SkIntToScalar(src.fTop) }, | 
 |                         { SkIntToScalar(src.fRight), SkIntToScalar(src.fTop) }, | 
 |                         { SkIntToScalar(src.fRight), SkIntToScalar(src.fBottom) }, | 
 |                     }; | 
 |                     canvas->drawVertices(SkCanvas::kTriangles_VertexMode, | 
 |                                          4, verts, uvs, nullptr, indices, 6, p2); | 
 |                 } else { | 
 |                     canvas->drawBitmapRect(fAtlas, src, dst, &p, | 
 |                                            SkCanvas::kFast_SrcRectConstraint); | 
 |                 } | 
 |             } else { | 
 |                 canvas->drawBitmapRect(fCheckerboard, dst, &p); | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 | private: | 
 |     static const int kCheckerboardWidth = 64; | 
 |     static const int kCheckerboardHeight = 128; | 
 |  | 
 |     static const int kAtlasCellWidth = 48; | 
 |     static const int kAtlasCellHeight = 36; | 
 |     static const int kNumAtlasedX = 5; | 
 |     static const int kNumAtlasedY = 5; | 
 |     static const int kAtlasSpacer = 2; | 
 |     static const int kTotAtlasWidth  = kNumAtlasedX * kAtlasCellWidth + | 
 |                                        (kNumAtlasedX+1) * kAtlasSpacer; | 
 |     static const int kTotAtlasHeight = kNumAtlasedY * kAtlasCellHeight + | 
 |                                        (kNumAtlasedY+1) * kAtlasSpacer; | 
 |     static const int kNumBeforeClear = 100; | 
 |  | 
 |     Type     fType; | 
 |     Clear    fClear; | 
 |     bool     fAligned; | 
 |     bool     fUseAtlas; | 
 |     bool     fUseDrawVertices; | 
 |     SkString fName; | 
 |     int      fNumSaved; // num draws stored in 'fSaved' | 
 |     bool     fInitialized; | 
 |  | 
 |     // 0 & 1 are always x & y translate. 2 is either scale or rotate. | 
 |     SkScalar fSaved[kNumBeforeClear][3]; | 
 |  | 
 |     SkBitmap fCheckerboard; | 
 |     SkBitmap fAtlas; | 
 |     SkIRect  fAtlasRects[kNumAtlasedX][kNumAtlasedY]; | 
 |  | 
 |     // Note: the resulting checker board has transparency | 
 |     void makeCheckerboard() { | 
 |         static int kCheckSize = 16; | 
 |  | 
 |         fCheckerboard.allocN32Pixels(kCheckerboardWidth, kCheckerboardHeight); | 
 |         SkAutoLockPixels lock(fCheckerboard); | 
 |         for (int y = 0; y < kCheckerboardHeight; ++y) { | 
 |             int even = (y / kCheckSize) % 2; | 
 |  | 
 |             SkPMColor* scanline = fCheckerboard.getAddr32(0, y); | 
 |  | 
 |             for (int x = 0; x < kCheckerboardWidth; ++x) { | 
 |                 if (even == (x / kCheckSize) % 2) { | 
 |                     *scanline++ = 0xFFFF0000; | 
 |                 } else { | 
 |                     *scanline++ = 0x00000000; | 
 |                 } | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     // Note: the resulting atlas has transparency | 
 |     void makeAtlas() { | 
 |         SkRandom rand; | 
 |  | 
 |         SkColor colors[kNumAtlasedX][kNumAtlasedY]; | 
 |  | 
 |         for (int y = 0; y < kNumAtlasedY; ++y) { | 
 |             for (int x = 0; x < kNumAtlasedX; ++x) { | 
 |                 colors[x][y] = rand.nextU() | 0xff000000; | 
 |                 fAtlasRects[x][y] = SkIRect::MakeXYWH(kAtlasSpacer + x * (kAtlasCellWidth + kAtlasSpacer), | 
 |                                                       kAtlasSpacer + y * (kAtlasCellHeight + kAtlasSpacer), | 
 |                                                       kAtlasCellWidth, | 
 |                                                       kAtlasCellHeight); | 
 |             } | 
 |         } | 
 |  | 
 |         fAtlas.allocN32Pixels(kTotAtlasWidth, kTotAtlasHeight); | 
 |         SkAutoLockPixels lock(fAtlas); | 
 |  | 
 |         for (int y = 0; y < kTotAtlasHeight; ++y) { | 
 |             int colorY = y / (kAtlasCellHeight + kAtlasSpacer); | 
 |             bool inColorY = (y % (kAtlasCellHeight + kAtlasSpacer)) >= kAtlasSpacer; | 
 |  | 
 |             SkPMColor* scanline = fAtlas.getAddr32(0, y); | 
 |  | 
 |             for (int x = 0; x < kTotAtlasWidth; ++x, ++scanline) { | 
 |                 int colorX = x / (kAtlasCellWidth + kAtlasSpacer); | 
 |                 bool inColorX = (x % (kAtlasCellWidth + kAtlasSpacer)) >= kAtlasSpacer; | 
 |  | 
 |                 if (inColorX && inColorY) { | 
 |                     SkASSERT(colorX < kNumAtlasedX && colorY < kNumAtlasedY); | 
 |                     *scanline = colors[colorX][colorY]; | 
 |                 } else { | 
 |                     *scanline = 0x00000000; | 
 |                 } | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     typedef Benchmark INHERITED; | 
 | }; | 
 |  | 
 | // Partial clear | 
 | DEF_BENCH(return new GameBench(GameBench::kScale_Type, GameBench::kPartial_Clear);) | 
 | DEF_BENCH(return new GameBench(GameBench::kTranslate_Type, GameBench::kPartial_Clear);) | 
 | DEF_BENCH(return new GameBench(GameBench::kTranslate_Type, GameBench::kPartial_Clear, true);) | 
 | DEF_BENCH(return new GameBench(GameBench::kRotate_Type, GameBench::kPartial_Clear);) | 
 |  | 
 | // Full clear | 
 | DEF_BENCH(return new GameBench(GameBench::kScale_Type, GameBench::kFull_Clear);) | 
 | DEF_BENCH(return new GameBench(GameBench::kTranslate_Type, GameBench::kFull_Clear);) | 
 | DEF_BENCH(return new GameBench(GameBench::kTranslate_Type, GameBench::kFull_Clear, true);) | 
 | DEF_BENCH(return new GameBench(GameBench::kRotate_Type, GameBench::kFull_Clear);) | 
 |  | 
 | // Atlased | 
 | DEF_BENCH(return new GameBench(GameBench::kTranslate_Type, GameBench::kFull_Clear, false, true);) | 
 | DEF_BENCH(return new GameBench( | 
 |                          GameBench::kTranslate_Type, GameBench::kFull_Clear, false, true, true);) |