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