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
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
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
81static 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
111static SkDiscardableMemoryPool* gPool = 0;
112static SkDiscardableMemory* pool_factory(size_t bytes) {
113    SkASSERT(gPool);
114    return gPool->create(bytes);
115}
116
117static 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
123DEF_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
138static 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.
173DEF_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
208static void* gTestNamespace;
209
210struct 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
218struct 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
240static 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 */
278DEF_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