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 "SkMakeUnique.h" 14 #include "SkMipMap.h" 15 #include "SkPicture.h" 16 #include "SkPictureRecorder.h" 17 #include "SkResourceCache.h" 18 #include "SkSurface.h" 19 20 //////////////////////////////////////////////////////////////////////////////////////// 21 22 enum LockedState { 23 kNotLocked, 24 kLocked, 25 }; 26 27 enum CachedState { 28 kNotInCache, 29 kInCache, 30 }; 31 32 static void check_data(skiatest::Reporter* reporter, const SkCachedData* data, 33 int refcnt, CachedState cacheState, LockedState lockedState) { 34 REPORTER_ASSERT(reporter, data->testing_only_getRefCnt() == refcnt); 35 REPORTER_ASSERT(reporter, data->testing_only_isInCache() == (kInCache == cacheState)); 36 bool isLocked = (data->data() != nullptr); 37 REPORTER_ASSERT(reporter, isLocked == (lockedState == kLocked)); 38 } 39 40 static void test_mipmapcache(skiatest::Reporter* reporter, SkResourceCache* cache) { 41 cache->purgeAll(); 42 43 SkBitmap src; 44 src.allocN32Pixels(5, 5); 45 src.setImmutable(); 46 47 const SkDestinationSurfaceColorMode colorMode = SkDestinationSurfaceColorMode::kLegacy; 48 49 const SkMipMap* mipmap = SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(src), colorMode, 50 cache); 51 REPORTER_ASSERT(reporter, nullptr == mipmap); 52 53 mipmap = SkMipMapCache::AddAndRef(src, colorMode, cache); 54 REPORTER_ASSERT(reporter, mipmap); 55 56 { 57 const SkMipMap* mm = SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(src), colorMode, 58 cache); 59 REPORTER_ASSERT(reporter, mm); 60 REPORTER_ASSERT(reporter, mm == mipmap); 61 mm->unref(); 62 } 63 64 check_data(reporter, mipmap, 2, kInCache, kLocked); 65 66 mipmap->unref(); 67 // tricky, since technically after this I'm no longer an owner, but since the cache is 68 // local, I know it won't get purged behind my back 69 check_data(reporter, mipmap, 1, kInCache, kNotLocked); 70 71 // find us again 72 mipmap = SkMipMapCache::FindAndRef(SkBitmapCacheDesc::Make(src), colorMode, cache); 73 check_data(reporter, mipmap, 2, kInCache, kLocked); 74 75 cache->purgeAll(); 76 check_data(reporter, mipmap, 1, kNotInCache, kLocked); 77 78 mipmap->unref(); 79 } 80 81 static void test_mipmap_notify(skiatest::Reporter* reporter, SkResourceCache* cache) { 82 const SkDestinationSurfaceColorMode colorMode = SkDestinationSurfaceColorMode::kLegacy; 83 const int N = 3; 84 85 SkBitmap src[N]; 86 for (int i = 0; i < N; ++i) { 87 src[i].allocN32Pixels(5, 5); 88 src[i].setImmutable(); 89 SkMipMapCache::AddAndRef(src[i], colorMode, cache)->unref(); 90 } 91 92 for (int i = 0; i < N; ++i) { 93 const auto desc = SkBitmapCacheDesc::Make(src[i]); 94 const SkMipMap* mipmap = SkMipMapCache::FindAndRef(desc, colorMode, cache); 95 if (cache) { 96 // if cache is null, we're working on the global cache, and other threads might purge 97 // it, making this check fragile. 98 REPORTER_ASSERT(reporter, mipmap); 99 } 100 SkSafeUnref(mipmap); 101 102 src[i].reset(); // delete the underlying pixelref, which *should* remove us from the cache 103 104 mipmap = SkMipMapCache::FindAndRef(desc, colorMode, cache); 105 REPORTER_ASSERT(reporter, !mipmap); 106 } 107 } 108 109 #include "SkDiscardableMemoryPool.h" 110 111 static SkDiscardableMemoryPool* gPool = 0; 112 static SkDiscardableMemory* pool_factory(size_t bytes) { 113 SkASSERT(gPool); 114 return gPool->create(bytes); 115 } 116 117 static void testBitmapCache_discarded_bitmap(skiatest::Reporter* reporter, SkResourceCache* cache, 118 SkResourceCache::DiscardableFactory factory) { 119 test_mipmapcache(reporter, cache); 120 test_mipmap_notify(reporter, cache); 121 } 122 123 DEF_TEST(BitmapCache_discarded_bitmap, reporter) { 124 const size_t byteLimit = 100 * 1024; 125 { 126 SkResourceCache cache(byteLimit); 127 testBitmapCache_discarded_bitmap(reporter, &cache, nullptr); 128 } 129 { 130 sk_sp<SkDiscardableMemoryPool> pool(SkDiscardableMemoryPool::Make(byteLimit)); 131 gPool = pool.get(); 132 SkResourceCache::DiscardableFactory factory = pool_factory; 133 SkResourceCache cache(factory); 134 testBitmapCache_discarded_bitmap(reporter, &cache, factory); 135 } 136 } 137 138 static void test_discarded_image(skiatest::Reporter* reporter, const SkMatrix& transform, 139 sk_sp<SkImage> (*buildImage)()) { 140 auto surface(SkSurface::MakeRasterN32Premul(10, 10)); 141 SkCanvas* canvas = surface->getCanvas(); 142 143 // SkBitmapCache is global, so other threads could be evicting our bitmaps. Loop a few times 144 // to mitigate this risk. 145 const unsigned kRepeatCount = 42; 146 for (unsigned i = 0; i < kRepeatCount; ++i) { 147 SkAutoCanvasRestore acr(canvas, true); 148 149 sk_sp<SkImage> image(buildImage()); 150 151 // always use high quality to ensure caching when scaled 152 SkPaint paint; 153 paint.setFilterQuality(kHigh_SkFilterQuality); 154 155 // draw the image (with a transform, to tickle different code paths) to ensure 156 // any associated resources get cached 157 canvas->concat(transform); 158 canvas->drawImage(image, 0, 0, &paint); 159 160 const auto desc = SkBitmapCacheDesc::Make(image.get()); 161 162 // delete the image 163 image.reset(nullptr); 164 165 // all resources should have been purged 166 SkBitmap result; 167 REPORTER_ASSERT(reporter, !SkBitmapCache::Find(desc, &result)); 168 } 169 } 170 171 172 // Verify that associated bitmap cache entries are purged on SkImage destruction. 173 DEF_TEST(BitmapCache_discarded_image, reporter) { 174 // Cache entries associated with SkImages fall into two categories: 175 // 176 // 1) generated image bitmaps (managed by the image cacherator) 177 // 2) scaled/resampled bitmaps (cached when HQ filters are used) 178 // 179 // To exercise the first cache type, we use generated/picture-backed SkImages. 180 // To exercise the latter, we draw scaled bitmap images using HQ filters. 181 182 const SkMatrix xforms[] = { 183 SkMatrix::MakeScale(1, 1), 184 SkMatrix::MakeScale(1.7f, 0.5f), 185 }; 186 187 for (size_t i = 0; i < SK_ARRAY_COUNT(xforms); ++i) { 188 test_discarded_image(reporter, xforms[i], []() { 189 auto surface(SkSurface::MakeRasterN32Premul(10, 10)); 190 surface->getCanvas()->clear(SK_ColorCYAN); 191 return surface->makeImageSnapshot(); 192 }); 193 194 test_discarded_image(reporter, xforms[i], []() { 195 SkPictureRecorder recorder; 196 SkCanvas* canvas = recorder.beginRecording(10, 10); 197 canvas->clear(SK_ColorCYAN); 198 return SkImage::MakeFromPicture(recorder.finishRecordingAsPicture(), 199 SkISize::Make(10, 10), nullptr, nullptr, 200 SkImage::BitDepth::kU8, 201 SkColorSpace::MakeSRGB()); 202 }); 203 } 204 } 205 206 /////////////////////////////////////////////////////////////////////////////////////////////////// 207 208 static void* gTestNamespace; 209 210 struct TestKey : SkResourceCache::Key { 211 int32_t fData; 212 213 TestKey(int sharedID, int32_t data) : fData(data) { 214 this->init(&gTestNamespace, sharedID, sizeof(fData)); 215 } 216 }; 217 218 struct TestRec : SkResourceCache::Rec { 219 enum { 220 kDidInstall = 1 << 0, 221 }; 222 223 TestKey fKey; 224 int* fFlags; 225 bool fCanBePurged; 226 227 TestRec(int sharedID, int32_t data, int* flagPtr) : fKey(sharedID, data), fFlags(flagPtr) { 228 fCanBePurged = false; 229 } 230 231 const Key& getKey() const override { return fKey; } 232 size_t bytesUsed() const override { return 1024; /* just need a value */ } 233 bool canBePurged() override { return fCanBePurged; } 234 void postAddInstall(void*) override { 235 *fFlags |= kDidInstall; 236 } 237 const char* getCategory() const override { return "test-category"; } 238 }; 239 240 static void test_duplicate_add(SkResourceCache* cache, skiatest::Reporter* reporter, 241 bool purgable) { 242 int sharedID = 1; 243 int data = 0; 244 245 int flags0 = 0, flags1 = 0; 246 247 auto rec0 = skstd::make_unique<TestRec>(sharedID, data, &flags0); 248 auto rec1 = skstd::make_unique<TestRec>(sharedID, data, &flags1); 249 SkASSERT(rec0->getKey() == rec1->getKey()); 250 251 TestRec* r0 = rec0.get(); // save the bare-pointer since we will release rec0 252 r0->fCanBePurged = purgable; 253 254 REPORTER_ASSERT(reporter, !(flags0 & TestRec::kDidInstall)); 255 REPORTER_ASSERT(reporter, !(flags1 & TestRec::kDidInstall)); 256 257 cache->add(rec0.release(), nullptr); 258 REPORTER_ASSERT(reporter, flags0 & TestRec::kDidInstall); 259 REPORTER_ASSERT(reporter, !(flags1 & TestRec::kDidInstall)); 260 flags0 = 0; // reset the flag 261 262 cache->add(rec1.release(), nullptr); 263 if (purgable) { 264 // we purged rec0, and did install rec1 265 REPORTER_ASSERT(reporter, !(flags0 & TestRec::kDidInstall)); 266 REPORTER_ASSERT(reporter, flags1 & TestRec::kDidInstall); 267 } else { 268 // we re-used rec0 and did not install rec1 269 REPORTER_ASSERT(reporter, flags0 & TestRec::kDidInstall); 270 REPORTER_ASSERT(reporter, !(flags1 & TestRec::kDidInstall)); 271 r0->fCanBePurged = true; // so we can cleanup the cache 272 } 273 } 274 275 /* 276 * Test behavior when the same key is added more than once. 277 */ 278 DEF_TEST(ResourceCache_purge, reporter) { 279 for (bool purgable : { false, true }) { 280 { 281 SkResourceCache cache(1024 * 1024); 282 test_duplicate_add(&cache, reporter, purgable); 283 } 284 { 285 SkResourceCache cache(SkDiscardableMemory::Create); 286 test_duplicate_add(&cache, reporter, purgable); 287 } 288 } 289 } 290