Home | History | Annotate | Download | only in tests
      1 /*
      2  * Copyright 2014 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 "Test.h"
      9 #include "SkBitmapCache.h"
     10 #include "SkCanvas.h"
     11 #include "SkDiscardableMemoryPool.h"
     12 #include "SkGraphics.h"
     13 #include "SkPicture.h"
     14 #include "SkPictureRecorder.h"
     15 #include "SkResourceCache.h"
     16 #include "SkSurface.h"
     17 #include "SkTypes.h"
     18 
     19 ////////////////////////////////////////////////////////////////////////////////////////
     20 
     21 static void make_bitmap(SkBitmap* bitmap, const SkImageInfo& info, SkBitmap::Allocator* allocator) {
     22     if (info.colorType() == kIndex_8_SkColorType) {
     23         bitmap->setInfo(info);
     24         SkPMColor ctStorage[256];
     25         memset(ctStorage, 0xFF, sizeof(ctStorage)); // init with opaque-white for the moment
     26         SkAutoTUnref<SkColorTable> ctable(new SkColorTable(ctStorage, 256));
     27         bitmap->allocPixels(allocator, ctable);
     28     } else if (allocator) {
     29         bitmap->setInfo(info);
     30         allocator->allocPixelRef(bitmap, 0);
     31     } else {
     32         bitmap->allocPixels(info);
     33     }
     34 }
     35 
     36 // https://bug.skia.org/2894
     37 DEF_TEST(BitmapCache_add_rect, reporter) {
     38     SkResourceCache::DiscardableFactory factory = SkResourceCache::GetDiscardableFactory();
     39     SkBitmap::Allocator* allocator = SkBitmapCache::GetAllocator();
     40 
     41     SkAutoTDelete<SkResourceCache> cache;
     42     if (factory) {
     43         cache.reset(new SkResourceCache(factory));
     44     } else {
     45         const size_t byteLimit = 100 * 1024;
     46         cache.reset(new SkResourceCache(byteLimit));
     47     }
     48     SkBitmap cachedBitmap;
     49     make_bitmap(&cachedBitmap, SkImageInfo::MakeN32Premul(5, 5), allocator);
     50     cachedBitmap.setImmutable();
     51 
     52     SkBitmap bm;
     53     SkIRect rect = SkIRect::MakeWH(5, 5);
     54     uint32_t cachedID = cachedBitmap.getGenerationID();
     55     SkPixelRef* cachedPR = cachedBitmap.pixelRef();
     56 
     57     // Wrong subset size
     58     REPORTER_ASSERT(reporter, !SkBitmapCache::Add(cachedPR, SkIRect::MakeWH(4, 6), cachedBitmap, cache));
     59     REPORTER_ASSERT(reporter, !SkBitmapCache::Find(cachedID, rect, &bm, cache));
     60     // Wrong offset value
     61     REPORTER_ASSERT(reporter, !SkBitmapCache::Add(cachedPR, SkIRect::MakeXYWH(-1, 0, 5, 5), cachedBitmap, cache));
     62     REPORTER_ASSERT(reporter, !SkBitmapCache::Find(cachedID, rect, &bm, cache));
     63 
     64     // Should not be in the cache
     65     REPORTER_ASSERT(reporter, !SkBitmapCache::Find(cachedID, rect, &bm, cache));
     66 
     67     REPORTER_ASSERT(reporter, SkBitmapCache::Add(cachedPR, rect, cachedBitmap, cache));
     68     // Should be in the cache, we just added it
     69     REPORTER_ASSERT(reporter, SkBitmapCache::Find(cachedID, rect, &bm, cache));
     70 }
     71 
     72 #include "SkMipMap.h"
     73 
     74 enum LockedState {
     75     kNotLocked,
     76     kLocked,
     77 };
     78 
     79 enum CachedState {
     80     kNotInCache,
     81     kInCache,
     82 };
     83 
     84 static void check_data(skiatest::Reporter* reporter, const SkCachedData* data,
     85                        int refcnt, CachedState cacheState, LockedState lockedState) {
     86     REPORTER_ASSERT(reporter, data->testing_only_getRefCnt() == refcnt);
     87     REPORTER_ASSERT(reporter, data->testing_only_isInCache() == (kInCache == cacheState));
     88     bool isLocked = (data->data() != nullptr);
     89     REPORTER_ASSERT(reporter, isLocked == (lockedState == kLocked));
     90 }
     91 
     92 static void test_mipmapcache(skiatest::Reporter* reporter, SkResourceCache* cache) {
     93     cache->purgeAll();
     94 
     95     SkBitmap src;
     96     src.allocN32Pixels(5, 5);
     97     src.setImmutable();
     98 
     99     const SkMipMap* mipmap = SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(src), cache);
    100     REPORTER_ASSERT(reporter, nullptr == mipmap);
    101 
    102     mipmap = SkMipMapCache::AddAndRef(src, cache);
    103     REPORTER_ASSERT(reporter, mipmap);
    104 
    105     {
    106         const SkMipMap* mm = SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(src), cache);
    107         REPORTER_ASSERT(reporter, mm);
    108         REPORTER_ASSERT(reporter, mm == mipmap);
    109         mm->unref();
    110     }
    111 
    112     check_data(reporter, mipmap, 2, kInCache, kLocked);
    113 
    114     mipmap->unref();
    115     // tricky, since technically after this I'm no longer an owner, but since the cache is
    116     // local, I know it won't get purged behind my back
    117     check_data(reporter, mipmap, 1, kInCache, kNotLocked);
    118 
    119     // find us again
    120     mipmap = SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(src), cache);
    121     check_data(reporter, mipmap, 2, kInCache, kLocked);
    122 
    123     cache->purgeAll();
    124     check_data(reporter, mipmap, 1, kNotInCache, kLocked);
    125 
    126     mipmap->unref();
    127 }
    128 
    129 static void test_mipmap_notify(skiatest::Reporter* reporter, SkResourceCache* cache) {
    130     const int N = 3;
    131     SkBitmap src[N];
    132     for (int i = 0; i < N; ++i) {
    133         src[i].allocN32Pixels(5, 5);
    134         src[i].setImmutable();
    135         SkMipMapCache::AddAndRef(src[i], cache)->unref();
    136     }
    137 
    138     for (int i = 0; i < N; ++i) {
    139         const SkMipMap* mipmap = SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(src[i]), cache);
    140         if (cache) {
    141             // if cache is null, we're working on the global cache, and other threads might purge
    142             // it, making this check fragile.
    143             REPORTER_ASSERT(reporter, mipmap);
    144         }
    145         SkSafeUnref(mipmap);
    146 
    147         src[i].reset(); // delete the underlying pixelref, which *should* remove us from the cache
    148 
    149         mipmap = SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(src[i]), cache);
    150         REPORTER_ASSERT(reporter, !mipmap);
    151     }
    152 }
    153 
    154 static void test_bitmap_notify(skiatest::Reporter* reporter, SkResourceCache* cache) {
    155     const SkIRect subset = SkIRect::MakeWH(5, 5);
    156     const int N = 3;
    157     SkBitmap src[N], dst[N];
    158     for (int i = 0; i < N; ++i) {
    159         src[i].allocN32Pixels(5, 5);
    160         src[i].setImmutable();
    161         dst[i].allocN32Pixels(5, 5);
    162         dst[i].setImmutable();
    163         SkBitmapCache::Add(src[i].pixelRef(), subset, dst[i], cache);
    164     }
    165 
    166     for (int i = 0; i < N; ++i) {
    167         const uint32_t genID = src[i].getGenerationID();
    168         SkBitmap result;
    169         bool found = SkBitmapCache::Find(genID, subset, &result, cache);
    170         if (cache) {
    171             // if cache is null, we're working on the global cache, and other threads might purge
    172             // it, making this check fragile.
    173             REPORTER_ASSERT(reporter, found);
    174         }
    175 
    176         src[i].reset(); // delete the underlying pixelref, which *should* remove us from the cache
    177 
    178         found = SkBitmapCache::Find(genID, subset, &result, cache);
    179         REPORTER_ASSERT(reporter, !found);
    180     }
    181 }
    182 
    183 #include "SkDiscardableMemoryPool.h"
    184 
    185 static SkDiscardableMemoryPool* gPool = 0;
    186 static SkDiscardableMemory* pool_factory(size_t bytes) {
    187     SkASSERT(gPool);
    188     return gPool->create(bytes);
    189 }
    190 
    191 static void testBitmapCache_discarded_bitmap(skiatest::Reporter* reporter, SkResourceCache* cache,
    192                                              SkResourceCache::DiscardableFactory factory) {
    193     SkBitmap::Allocator* allocator = cache->allocator();
    194     const SkColorType testTypes[] = {
    195         kAlpha_8_SkColorType,
    196         kRGB_565_SkColorType,
    197         kRGBA_8888_SkColorType,
    198         kBGRA_8888_SkColorType,
    199         kIndex_8_SkColorType,
    200         kGray_8_SkColorType
    201     };
    202     for (const SkColorType testType : testTypes) {
    203         SkBitmap cachedBitmap;
    204         make_bitmap(&cachedBitmap, SkImageInfo::Make(5, 5, testType, kPremul_SkAlphaType),
    205                     allocator);
    206         cachedBitmap.setImmutable();
    207         cachedBitmap.unlockPixels();
    208 
    209         SkBitmap bm;
    210         SkIRect rect = SkIRect::MakeWH(5, 5);
    211 
    212         // Add a bitmap to the cache.
    213         REPORTER_ASSERT(reporter, SkBitmapCache::Add(cachedBitmap.pixelRef(), rect, cachedBitmap,
    214                                                      cache));
    215         REPORTER_ASSERT(reporter, SkBitmapCache::Find(cachedBitmap.getGenerationID(), rect, &bm,
    216                                                       cache));
    217 
    218         // Finding more than once works fine.
    219         REPORTER_ASSERT(reporter, SkBitmapCache::Find(cachedBitmap.getGenerationID(), rect, &bm,
    220                                                       cache));
    221         bm.unlockPixels();
    222 
    223         // Drop the pixels in the bitmap.
    224         if (factory) {
    225             REPORTER_ASSERT(reporter, gPool->getRAMUsed() > 0);
    226             gPool->dumpPool();
    227 
    228             // The bitmap is not in the cache since it has been dropped.
    229             REPORTER_ASSERT(reporter, !SkBitmapCache::Find(cachedBitmap.getGenerationID(), rect,
    230                                                            &bm, cache));
    231         }
    232 
    233         make_bitmap(&cachedBitmap, SkImageInfo::Make(5, 5, testType, kPremul_SkAlphaType),
    234                     allocator);
    235         cachedBitmap.setImmutable();
    236         cachedBitmap.unlockPixels();
    237 
    238         // We can add the bitmap back to the cache and find it again.
    239         REPORTER_ASSERT(reporter, SkBitmapCache::Add(cachedBitmap.pixelRef(), rect, cachedBitmap,
    240                                                      cache));
    241         REPORTER_ASSERT(reporter, SkBitmapCache::Find(cachedBitmap.getGenerationID(), rect, &bm,
    242                                                       cache));
    243     }
    244     test_mipmapcache(reporter, cache);
    245     test_bitmap_notify(reporter, cache);
    246     test_mipmap_notify(reporter, cache);
    247 }
    248 
    249 DEF_TEST(BitmapCache_discarded_bitmap, reporter) {
    250     const size_t byteLimit = 100 * 1024;
    251     {
    252         SkResourceCache cache(byteLimit);
    253         testBitmapCache_discarded_bitmap(reporter, &cache, nullptr);
    254     }
    255     {
    256         SkAutoTUnref<SkDiscardableMemoryPool> pool(
    257             SkDiscardableMemoryPool::Create(byteLimit, nullptr));
    258         gPool = pool.get();
    259         SkResourceCache::DiscardableFactory factory = pool_factory;
    260         SkResourceCache cache(factory);
    261         testBitmapCache_discarded_bitmap(reporter, &cache, factory);
    262     }
    263 }
    264 
    265 static void test_discarded_image(skiatest::Reporter* reporter, const SkMatrix& transform,
    266                                  SkImage* (*buildImage)()) {
    267     SkAutoTUnref<SkSurface> surface(SkSurface::NewRasterN32Premul(10, 10));
    268     SkCanvas* canvas = surface->getCanvas();
    269 
    270     // SkBitmapCache is global, so other threads could be evicting our bitmaps.  Loop a few times
    271     // to mitigate this risk.
    272     const unsigned kRepeatCount = 42;
    273     for (unsigned i = 0; i < kRepeatCount; ++i) {
    274         SkAutoCanvasRestore acr(canvas, true);
    275 
    276         SkAutoTUnref<SkImage> image(buildImage());
    277 
    278         // always use high quality to ensure caching when scaled
    279         SkPaint paint;
    280         paint.setFilterQuality(kHigh_SkFilterQuality);
    281 
    282         // draw the image (with a transform, to tickle different code paths) to ensure
    283         // any associated resources get cached
    284         canvas->concat(transform);
    285         canvas->drawImage(image, 0, 0, &paint);
    286 
    287         auto imageId = image->uniqueID();
    288 
    289         // delete the image
    290         image.reset(nullptr);
    291 
    292         // all resources should have been purged
    293         SkBitmap result;
    294         REPORTER_ASSERT(reporter, !SkBitmapCache::Find(imageId, &result));
    295     }
    296 }
    297 
    298 
    299 // Verify that associated bitmap cache entries are purged on SkImage destruction.
    300 DEF_TEST(BitmapCache_discarded_image, reporter) {
    301     // Cache entries associated with SkImages fall into two categories:
    302     //
    303     // 1) generated image bitmaps (managed by the image cacherator)
    304     // 2) scaled/resampled bitmaps (cached when HQ filters are used)
    305     //
    306     // To exercise the first cache type, we use generated/picture-backed SkImages.
    307     // To exercise the latter, we draw scaled bitmap images using HQ filters.
    308 
    309     const SkMatrix xforms[] = {
    310         SkMatrix::MakeScale(1, 1),
    311         SkMatrix::MakeScale(1.7f, 0.5f),
    312     };
    313 
    314     for (size_t i = 0; i < SK_ARRAY_COUNT(xforms); ++i) {
    315         test_discarded_image(reporter, xforms[i], []() {
    316             SkAutoTUnref<SkSurface> surface(SkSurface::NewRasterN32Premul(10, 10));
    317             surface->getCanvas()->clear(SK_ColorCYAN);
    318             return surface->newImageSnapshot();
    319         });
    320 
    321         test_discarded_image(reporter, xforms[i], []() {
    322             SkPictureRecorder recorder;
    323             SkCanvas* canvas = recorder.beginRecording(10, 10);
    324             canvas->clear(SK_ColorCYAN);
    325             SkAutoTUnref<SkPicture> picture(recorder.endRecording());
    326             return SkImage::NewFromPicture(picture, SkISize::Make(10, 10), nullptr, nullptr);
    327         });
    328     }
    329 }
    330