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