Home | History | Annotate | Download | only in bench
      1 /*
      2  * Copyright 2016 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 "sk_tool_utils.h"
     10 #include "SkCanvas.h"
     11 #include "SkImage.h"
     12 #include "SkSurface.h"
     13 
     14 #if SK_SUPPORT_GPU
     15 
     16 #include "GrContext.h"
     17 
     18 /** These benchmarks were designed to measure changes to GrResourceCache's replacement policy */
     19 
     20 //////////////////////////////////////////////////////////////////////////////
     21 
     22 // The width/height of the images to draw. The small size underestimates the value of a good
     23 // replacement strategy since the texture uploads are quite small. However, the effects are still
     24 // significant and this lets the benchmarks complete a lot faster, especially on mobile.
     25 static constexpr int kS = 25;
     26 
     27 static void make_images(sk_sp<SkImage> imgs[], int cnt) {
     28     for (int i = 0; i < cnt; ++i) {
     29         SkBitmap bmp = sk_tool_utils::create_checkerboard_bitmap(kS, kS, SK_ColorBLACK,
     30                                                                  SK_ColorCYAN, 10);
     31         imgs[i] = SkImage::MakeFromBitmap(bmp);
     32     }
     33 }
     34 
     35 static void draw_image(SkCanvas* canvas, SkImage* img) {
     36     // Make the paint transparent to avoid any issues of deferred tiler blending
     37     // optmizations
     38     SkPaint paint;
     39     paint.setAlpha(0x10);
     40     canvas->drawImage(img, 0, 0, &paint);
     41 }
     42 
     43 void set_cache_budget(SkCanvas* canvas, int approxImagesInBudget) {
     44     // This is inexact but we attempt to figure out a baseline number of resources GrContext needs
     45     // to render an SkImage and add one additional resource for each image we'd like to fit.
     46     GrContext* context =  canvas->getGrContext();
     47     SkASSERT(context);
     48     context->flush();
     49     context->purgeAllUnlockedResources();
     50     sk_sp<SkImage> image;
     51     make_images(&image, 1);
     52     draw_image(canvas, image.get());
     53     context->flush();
     54     int baselineCount;
     55     context->getResourceCacheUsage(&baselineCount, nullptr);
     56     baselineCount -= 1; // for the image's textures.
     57     context->setResourceCacheLimits(baselineCount + approxImagesInBudget, 1 << 30);
     58     context->purgeAllUnlockedResources();
     59 }
     60 
     61 //////////////////////////////////////////////////////////////////////////////
     62 
     63 /**
     64  * Tests repeatedly drawing the same set of images in each frame. Different instances of the bench
     65  * run with different cache sizes and either repeat the image order each frame or use a random
     66  * order. Every variation of this bench draws the same image set, only the budget and order of
     67  * images differs. Since the total fill is the same they can be cross-compared.
     68  */
     69 class ImageCacheBudgetBench : public Benchmark {
     70 public:
     71     /** budgetSize is the number of images that can fit in the cache. 100 images will be drawn. */
     72     ImageCacheBudgetBench(int budgetSize, bool shuffle)
     73             : fBudgetSize(budgetSize)
     74             , fShuffle(shuffle)
     75             , fIndices(nullptr) {
     76         float imagesOverBudget = float(kImagesToDraw) / budgetSize;
     77         // Make the benchmark name contain the percentage of the budget that is used in each
     78         // simulated frame.
     79         fName.printf("image_cache_budget_%.0f%s", imagesOverBudget * 100,
     80                      (shuffle ? "_shuffle" : ""));
     81     }
     82 
     83     bool isSuitableFor(Backend backend) override { return kGPU_Backend == backend; }
     84 
     85 protected:
     86     const char* onGetName() override {
     87         return fName.c_str();
     88     }
     89 
     90     void onPerCanvasPreDraw(SkCanvas* canvas) override {
     91         GrContext* context = canvas->getGrContext();
     92         SkASSERT(context);
     93         context->getResourceCacheLimits(&fOldCount, &fOldBytes);
     94         set_cache_budget(canvas, fBudgetSize);
     95         make_images(fImages, kImagesToDraw);
     96         if (fShuffle) {
     97             SkRandom random;
     98             fIndices.reset(new int[kSimulatedFrames * kImagesToDraw]);
     99             for (int frame = 0; frame < kSimulatedFrames; ++frame) {
    100                 int* base = fIndices.get() + frame * kImagesToDraw;
    101                 for (int i = 0; i < kImagesToDraw; ++i) {
    102                     base[i] = i;
    103                 }
    104                 for (int i = 0; i < kImagesToDraw - 1; ++i) {
    105                     int other = random.nextULessThan(kImagesToDraw - i) + i;
    106                     SkTSwap(base[i], base[other]);
    107                 }
    108             }
    109         }
    110     }
    111 
    112     void onPerCanvasPostDraw(SkCanvas* canvas) override {
    113         GrContext* context =  canvas->getGrContext();
    114         SkASSERT(context);
    115         context->setResourceCacheLimits(fOldCount, fOldBytes);
    116         for (int i = 0; i < kImagesToDraw; ++i) {
    117             fImages[i].reset();
    118         }
    119         fIndices.reset(nullptr);
    120     }
    121 
    122     void onDraw(int loops, SkCanvas* canvas) override {
    123         for (int i = 0; i < loops; ++i) {
    124             for (int frame = 0; frame < kSimulatedFrames; ++frame) {
    125                 for (int j = 0; j < kImagesToDraw; ++j) {
    126                     int idx;
    127                     if (fShuffle) {
    128                         idx = fIndices[frame * kImagesToDraw + j];
    129                     } else {
    130                         idx = j;
    131                     }
    132                     draw_image(canvas, fImages[idx].get());
    133                 }
    134                 // Simulate a frame boundary by flushing. This should notify GrResourceCache.
    135                 canvas->flush();
    136            }
    137         }
    138     }
    139 
    140 private:
    141     static constexpr int kImagesToDraw = 100;
    142     static constexpr int kSimulatedFrames = 5;
    143 
    144     int                         fBudgetSize;
    145     bool                        fShuffle;
    146     SkString                    fName;
    147     sk_sp<SkImage>              fImages[kImagesToDraw];
    148     std::unique_ptr<int[]>      fIndices;
    149     size_t                      fOldBytes;
    150     int                         fOldCount;
    151 
    152     typedef Benchmark INHERITED;
    153 };
    154 
    155 DEF_BENCH( return new ImageCacheBudgetBench(105, false); )
    156 
    157 DEF_BENCH( return new ImageCacheBudgetBench(90, false); )
    158 
    159 DEF_BENCH( return new ImageCacheBudgetBench(80, false); )
    160 
    161 DEF_BENCH( return new ImageCacheBudgetBench(50, false); )
    162 
    163 DEF_BENCH( return new ImageCacheBudgetBench(105, true); )
    164 
    165 DEF_BENCH( return new ImageCacheBudgetBench(90, true); )
    166 
    167 DEF_BENCH( return new ImageCacheBudgetBench(80, true); )
    168 
    169 DEF_BENCH( return new ImageCacheBudgetBench(50, true); )
    170 
    171 //////////////////////////////////////////////////////////////////////////////
    172 
    173 /**
    174  * Similar to above but changes between being over and under budget by varying the number of images
    175  * rendered. This is not directly comparable to the non-dynamic benchmarks.
    176  */
    177 class ImageCacheBudgetDynamicBench : public Benchmark {
    178 public:
    179     enum class Mode {
    180         // Increase from min to max images drawn gradually over simulated frames and then back.
    181         kPingPong,
    182         // Alternate between under and over budget every other simulated frame.
    183         kFlipFlop
    184     };
    185 
    186     ImageCacheBudgetDynamicBench(Mode mode) : fMode(mode) {}
    187 
    188     bool isSuitableFor(Backend backend) override { return kGPU_Backend == backend; }
    189 
    190 protected:
    191     const char* onGetName() override {
    192         switch (fMode) {
    193             case Mode::kPingPong:
    194                 return "image_cache_budget_dynamic_ping_pong";
    195             case Mode::kFlipFlop:
    196                 return "image_cache_budget_dynamic_flip_flop";
    197         }
    198         return "";
    199     }
    200 
    201     void onPerCanvasPreDraw(SkCanvas* canvas) override {
    202         GrContext* context =  canvas->getGrContext();
    203         SkASSERT(context);
    204         context->getResourceCacheLimits(&fOldCount, &fOldBytes);
    205         make_images(fImages, kMaxImagesToDraw);
    206         set_cache_budget(canvas, kImagesInBudget);
    207     }
    208 
    209     void onPerCanvasPostDraw(SkCanvas* canvas) override {
    210         GrContext* context =  canvas->getGrContext();
    211         SkASSERT(context);
    212         context->setResourceCacheLimits(fOldCount, fOldBytes);
    213         for (int i = 0; i < kMaxImagesToDraw; ++i) {
    214             fImages[i].reset();
    215         }
    216     }
    217 
    218     void onDraw(int loops, SkCanvas* canvas) override {
    219         int delta = 0;
    220         switch (fMode) {
    221             case Mode::kPingPong:
    222                 delta = 1;
    223                 break;
    224             case Mode::kFlipFlop:
    225                 delta = kMaxImagesToDraw - kMinImagesToDraw;
    226                 break;
    227         }
    228         for (int i = 0; i < loops; ++i) {
    229             int imgsToDraw = kMinImagesToDraw;
    230             for (int frame = 0; frame < kSimulatedFrames; ++frame) {
    231                 for (int j = 0; j < imgsToDraw; ++j) {
    232                     draw_image(canvas, fImages[j].get());
    233                 }
    234                 imgsToDraw += delta;
    235                 if (imgsToDraw > kMaxImagesToDraw || imgsToDraw < kMinImagesToDraw) {
    236                     delta = -delta;
    237                     imgsToDraw += 2 * delta;
    238                 }
    239                 // Simulate a frame boundary by flushing. This should notify GrResourceCache.
    240                 canvas->flush();
    241             }
    242         }
    243     }
    244 
    245 private:
    246     static constexpr int kImagesInBudget  = 25;
    247     static constexpr int kMinImagesToDraw = 15;
    248     static constexpr int kMaxImagesToDraw = 35;
    249     static constexpr int kSimulatedFrames = 80;
    250 
    251     Mode                        fMode;
    252     sk_sp<SkImage>              fImages[kMaxImagesToDraw];
    253     size_t                      fOldBytes;
    254     int                         fOldCount;
    255 
    256     typedef Benchmark INHERITED;
    257 };
    258 
    259 DEF_BENCH( return new ImageCacheBudgetDynamicBench(ImageCacheBudgetDynamicBench::Mode::kPingPong); )
    260 DEF_BENCH( return new ImageCacheBudgetDynamicBench(ImageCacheBudgetDynamicBench::Mode::kFlipFlop); )
    261 
    262 #endif
    263