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 "SkMipMap.h" 14 #include "SkPicture.h" 15 #include "SkPictureRecorder.h" 16 #include "SkResourceCache.h" 17 #include "SkSurface.h" 18 19 //////////////////////////////////////////////////////////////////////////////////////// 20 21 enum LockedState { 22 kNotLocked, 23 kLocked, 24 }; 25 26 enum CachedState { 27 kNotInCache, 28 kInCache, 29 }; 30 31 static void check_data(skiatest::Reporter* reporter, const SkCachedData* data, 32 int refcnt, CachedState cacheState, LockedState lockedState) { 33 REPORTER_ASSERT(reporter, data->testing_only_getRefCnt() == refcnt); 34 REPORTER_ASSERT(reporter, data->testing_only_isInCache() == (kInCache == cacheState)); 35 bool isLocked = (data->data() != nullptr); 36 REPORTER_ASSERT(reporter, isLocked == (lockedState == kLocked)); 37 } 38 39 static void test_mipmapcache(skiatest::Reporter* reporter, SkResourceCache* cache) { 40 cache->purgeAll(); 41 42 SkBitmap src; 43 src.allocN32Pixels(5, 5); 44 src.setImmutable(); 45 46 const SkDestinationSurfaceColorMode colorMode = SkDestinationSurfaceColorMode::kLegacy; 47 48 const SkMipMap* mipmap = SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(src), colorMode, 49 cache); 50 REPORTER_ASSERT(reporter, nullptr == mipmap); 51 52 mipmap = SkMipMapCache::AddAndRef(src, colorMode, cache); 53 REPORTER_ASSERT(reporter, mipmap); 54 55 { 56 const SkMipMap* mm = SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(src), colorMode, 57 cache); 58 REPORTER_ASSERT(reporter, mm); 59 REPORTER_ASSERT(reporter, mm == mipmap); 60 mm->unref(); 61 } 62 63 check_data(reporter, mipmap, 2, kInCache, kLocked); 64 65 mipmap->unref(); 66 // tricky, since technically after this I'm no longer an owner, but since the cache is 67 // local, I know it won't get purged behind my back 68 check_data(reporter, mipmap, 1, kInCache, kNotLocked); 69 70 // find us again 71 mipmap = SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(src), colorMode, cache); 72 check_data(reporter, mipmap, 2, kInCache, kLocked); 73 74 cache->purgeAll(); 75 check_data(reporter, mipmap, 1, kNotInCache, kLocked); 76 77 mipmap->unref(); 78 } 79 80 static void test_mipmap_notify(skiatest::Reporter* reporter, SkResourceCache* cache) { 81 const SkDestinationSurfaceColorMode colorMode = SkDestinationSurfaceColorMode::kLegacy; 82 const int N = 3; 83 84 SkBitmap src[N]; 85 for (int i = 0; i < N; ++i) { 86 src[i].allocN32Pixels(5, 5); 87 src[i].setImmutable(); 88 SkMipMapCache::AddAndRef(src[i], colorMode, cache)->unref(); 89 } 90 91 for (int i = 0; i < N; ++i) { 92 const auto desc = SkBitmapCacheDesc::Make(src[i]); 93 const SkMipMap* mipmap = SkMipMapCache::FindAndRef(desc, colorMode, cache); 94 if (cache) { 95 // if cache is null, we're working on the global cache, and other threads might purge 96 // it, making this check fragile. 97 REPORTER_ASSERT(reporter, mipmap); 98 } 99 SkSafeUnref(mipmap); 100 101 src[i].reset(); // delete the underlying pixelref, which *should* remove us from the cache 102 103 mipmap = SkMipMapCache::FindAndRef(desc, colorMode, cache); 104 REPORTER_ASSERT(reporter, !mipmap); 105 } 106 } 107 108 #include "SkDiscardableMemoryPool.h" 109 110 static SkDiscardableMemoryPool* gPool = 0; 111 static SkDiscardableMemory* pool_factory(size_t bytes) { 112 SkASSERT(gPool); 113 return gPool->create(bytes); 114 } 115 116 static void testBitmapCache_discarded_bitmap(skiatest::Reporter* reporter, SkResourceCache* cache, 117 SkResourceCache::DiscardableFactory factory) { 118 test_mipmapcache(reporter, cache); 119 test_mipmap_notify(reporter, cache); 120 } 121 122 DEF_TEST(BitmapCache_discarded_bitmap, reporter) { 123 const size_t byteLimit = 100 * 1024; 124 { 125 SkResourceCache cache(byteLimit); 126 testBitmapCache_discarded_bitmap(reporter, &cache, nullptr); 127 } 128 { 129 sk_sp<SkDiscardableMemoryPool> pool(SkDiscardableMemoryPool::Create(byteLimit, nullptr)); 130 gPool = pool.get(); 131 SkResourceCache::DiscardableFactory factory = pool_factory; 132 SkResourceCache cache(factory); 133 testBitmapCache_discarded_bitmap(reporter, &cache, factory); 134 } 135 } 136 137 static void test_discarded_image(skiatest::Reporter* reporter, const SkMatrix& transform, 138 sk_sp<SkImage> (*buildImage)()) { 139 auto surface(SkSurface::MakeRasterN32Premul(10, 10)); 140 SkCanvas* canvas = surface->getCanvas(); 141 142 // SkBitmapCache is global, so other threads could be evicting our bitmaps. Loop a few times 143 // to mitigate this risk. 144 const unsigned kRepeatCount = 42; 145 for (unsigned i = 0; i < kRepeatCount; ++i) { 146 SkAutoCanvasRestore acr(canvas, true); 147 148 sk_sp<SkImage> image(buildImage()); 149 150 // always use high quality to ensure caching when scaled 151 SkPaint paint; 152 paint.setFilterQuality(kHigh_SkFilterQuality); 153 154 // draw the image (with a transform, to tickle different code paths) to ensure 155 // any associated resources get cached 156 canvas->concat(transform); 157 canvas->drawImage(image, 0, 0, &paint); 158 159 const auto desc = SkBitmapCacheDesc::Make(image.get()); 160 161 // delete the image 162 image.reset(nullptr); 163 164 // all resources should have been purged 165 SkBitmap result; 166 REPORTER_ASSERT(reporter, !SkBitmapCache::Find(desc, &result)); 167 } 168 } 169 170 171 // Verify that associated bitmap cache entries are purged on SkImage destruction. 172 DEF_TEST(BitmapCache_discarded_image, reporter) { 173 // Cache entries associated with SkImages fall into two categories: 174 // 175 // 1) generated image bitmaps (managed by the image cacherator) 176 // 2) scaled/resampled bitmaps (cached when HQ filters are used) 177 // 178 // To exercise the first cache type, we use generated/picture-backed SkImages. 179 // To exercise the latter, we draw scaled bitmap images using HQ filters. 180 181 const SkMatrix xforms[] = { 182 SkMatrix::MakeScale(1, 1), 183 SkMatrix::MakeScale(1.7f, 0.5f), 184 }; 185 186 for (size_t i = 0; i < SK_ARRAY_COUNT(xforms); ++i) { 187 test_discarded_image(reporter, xforms[i], []() { 188 auto surface(SkSurface::MakeRasterN32Premul(10, 10)); 189 surface->getCanvas()->clear(SK_ColorCYAN); 190 return surface->makeImageSnapshot(); 191 }); 192 193 test_discarded_image(reporter, xforms[i], []() { 194 SkPictureRecorder recorder; 195 SkCanvas* canvas = recorder.beginRecording(10, 10); 196 canvas->clear(SK_ColorCYAN); 197 return SkImage::MakeFromPicture(recorder.finishRecordingAsPicture(), 198 SkISize::Make(10, 10), nullptr, nullptr, 199 SkImage::BitDepth::kU8, 200 SkColorSpace::MakeSRGB()); 201 }); 202 } 203 } 204