/* * 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 "bench/Benchmark.h" #include "include/core/SkBitmap.h" #include "include/core/SkCanvas.h" #include "include/core/SkM44.h" #include "include/core/SkPaint.h" #include "include/core/SkShader.h" #include "include/core/SkString.h" #include "include/core/SkVertices.h" #include "include/utils/SkRandom.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->getBaseLayerSize(); 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); SkPaint p2; // for drawVertices path p2.setColor(0xFF000000); p2.setShader(fAtlas.makeShader(SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone))); 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(SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode, 4, verts, uvs, nullptr, 6, indices), SkBlendMode::kModulate, 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); 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); 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; } } } } using INHERITED = Benchmark; }; // 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);) class CanvasMatrixBench : public Benchmark { SkString fName; public: enum Type { kTranslate_Type, kScale_Type, k2x3_Type, k3x3_Type, k4x4_Type, }; Type fType; CanvasMatrixBench(Type t) : fType(t) { fName.set("canvas_matrix"); switch (fType) { case kTranslate_Type: fName.append("_trans"); break; case kScale_Type: fName.append("_scale"); break; case k2x3_Type: fName.append("_2x3"); break; case k3x3_Type: fName.append("_3x3"); break; case k4x4_Type: fName.append("_4x4"); break; } } protected: const char* onGetName() override { return fName.c_str(); } void onDraw(int loops, SkCanvas* canvas) override { SkMatrix m; m.setRotate(1); if (fType == k3x3_Type) { m[7] = 0.0001f; } SkM44 m4(m); for (int i = 0; i < loops; ++i) { canvas->save(); for (int j = 0; j < 10000; ++j) { switch (fType) { case kTranslate_Type: canvas->translate(0.0001f, 0.0001f); break; case kScale_Type: canvas->scale(1.0001f, 0.9999f); break; case k2x3_Type: canvas->concat(m); break; case k3x3_Type: canvas->concat(m); break; case k4x4_Type: canvas->concat(m4); break; } } canvas->restore(); } } private: using INHERITED = Benchmark; }; DEF_BENCH(return new CanvasMatrixBench(CanvasMatrixBench::kTranslate_Type)); DEF_BENCH(return new CanvasMatrixBench(CanvasMatrixBench::kScale_Type)); DEF_BENCH(return new CanvasMatrixBench(CanvasMatrixBench::k2x3_Type)); DEF_BENCH(return new CanvasMatrixBench(CanvasMatrixBench::k3x3_Type)); DEF_BENCH(return new CanvasMatrixBench(CanvasMatrixBench::k4x4_Type));