1/*
2 * Copyright 2013 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#if SK_SUPPORT_GPU
9
10#include "GrContextFactory.h"
11#include "GrResourceCache.h"
12#include "SkGpuDevice.h"
13#include "Test.h"
14
15static const int gWidth = 640;
16static const int gHeight = 480;
17
18////////////////////////////////////////////////////////////////////////////////
19static void test_cache(skiatest::Reporter* reporter,
20                       GrContext* context,
21                       SkCanvas* canvas) {
22    const SkIRect size = SkIRect::MakeWH(gWidth, gHeight);
23
24    SkBitmap src;
25    src.allocN32Pixels(size.width(), size.height());
26    src.eraseColor(SK_ColorBLACK);
27    size_t srcSize = src.getSize();
28
29    size_t initialCacheSize;
30    context->getResourceCacheUsage(NULL, &initialCacheSize);
31
32    int oldMaxNum;
33    size_t oldMaxBytes;
34    context->getResourceCacheLimits(&oldMaxNum, &oldMaxBytes);
35
36    // Set the cache limits so we can fit 10 "src" images and the
37    // max number of textures doesn't matter
38    size_t maxCacheSize = initialCacheSize + 10*srcSize;
39    context->setResourceCacheLimits(1000, maxCacheSize);
40
41    SkBitmap readback;
42    readback.allocN32Pixels(size.width(), size.height());
43
44    for (int i = 0; i < 100; ++i) {
45        canvas->drawBitmap(src, 0, 0);
46        canvas->readPixels(size, &readback);
47
48        // "modify" the src texture
49        src.notifyPixelsChanged();
50
51        size_t curCacheSize;
52        context->getResourceCacheUsage(NULL, &curCacheSize);
53
54        // we should never go over the size limit
55        REPORTER_ASSERT(reporter, curCacheSize <= maxCacheSize);
56    }
57
58    context->setResourceCacheLimits(oldMaxNum, oldMaxBytes);
59}
60
61class TestResource : public GrCacheable {
62    static const size_t kDefaultSize = 100;
63
64public:
65    SK_DECLARE_INST_COUNT(TestResource);
66    TestResource(size_t size = kDefaultSize)
67        : fCache(NULL)
68        , fToDelete(NULL)
69        , fSize(size) {
70        ++fAlive;
71    }
72
73    ~TestResource() {
74        --fAlive;
75        if (NULL != fToDelete) {
76            // Breaks our little 2-element cycle below.
77            fToDelete->setDeleteWhenDestroyed(NULL, NULL);
78            fCache->deleteResource(fToDelete->getCacheEntry());
79        }
80    }
81
82    void setSize(size_t size) {
83        fSize = size;
84        this->didChangeGpuMemorySize();
85    }
86
87    size_t gpuMemorySize() const SK_OVERRIDE { return fSize; }
88
89    bool isValidOnGpu() const SK_OVERRIDE { return true; }
90
91    static int alive() { return fAlive; }
92
93    void setDeleteWhenDestroyed(GrResourceCache* cache, TestResource* resource) {
94        fCache = cache;
95        fToDelete = resource;
96    }
97
98private:
99    GrResourceCache* fCache;
100    TestResource* fToDelete;
101    size_t fSize;
102    static int fAlive;
103
104    typedef GrCacheable INHERITED;
105};
106int TestResource::fAlive = 0;
107
108static void test_purge_invalidated(skiatest::Reporter* reporter, GrContext* context) {
109    GrCacheID::Domain domain = GrCacheID::GenerateDomain();
110    GrCacheID::Key keyData;
111    keyData.fData64[0] = 5;
112    keyData.fData64[1] = 18;
113    GrResourceKey::ResourceType t = GrResourceKey::GenerateResourceType();
114    GrResourceKey key(GrCacheID(domain, keyData), t, 0);
115
116    GrResourceCache cache(5, 30000);
117
118    // Add two resources with the same key that delete each other from the cache when destroyed.
119    TestResource* a = new TestResource();
120    TestResource* b = new TestResource();
121    cache.addResource(key, a);
122    cache.addResource(key, b);
123    // Circle back.
124    a->setDeleteWhenDestroyed(&cache, b);
125    b->setDeleteWhenDestroyed(&cache, a);
126    a->unref();
127    b->unref();
128
129    // Add a third independent resource also with the same key.
130    GrCacheable* r = new TestResource();
131    cache.addResource(key, r);
132    r->unref();
133
134    // Invalidate all three, all three should be purged and destroyed.
135    REPORTER_ASSERT(reporter, 3 == TestResource::alive());
136    const GrResourceInvalidatedMessage msg = { key };
137    SkMessageBus<GrResourceInvalidatedMessage>::Post(msg);
138    cache.purgeAsNeeded();
139    REPORTER_ASSERT(reporter, 0 == TestResource::alive());
140}
141
142static void test_cache_delete_on_destruction(skiatest::Reporter* reporter,
143                                             GrContext* context) {
144    GrCacheID::Domain domain = GrCacheID::GenerateDomain();
145    GrCacheID::Key keyData;
146    keyData.fData64[0] = 5;
147    keyData.fData64[1] = 0;
148    GrResourceKey::ResourceType t = GrResourceKey::GenerateResourceType();
149
150    GrResourceKey key(GrCacheID(domain, keyData), t, 0);
151
152    {
153        {
154            GrResourceCache cache(3, 30000);
155            TestResource* a = new TestResource();
156            TestResource* b = new TestResource();
157            cache.addResource(key, a);
158            cache.addResource(key, b);
159
160            a->setDeleteWhenDestroyed(&cache, b);
161            b->setDeleteWhenDestroyed(&cache, a);
162
163            a->unref();
164            b->unref();
165            REPORTER_ASSERT(reporter, 2 == TestResource::alive());
166        }
167        REPORTER_ASSERT(reporter, 0 == TestResource::alive());
168    }
169    {
170        GrResourceCache cache(3, 30000);
171        TestResource* a = new TestResource();
172        TestResource* b = new TestResource();
173        cache.addResource(key, a);
174        cache.addResource(key, b);
175
176        a->setDeleteWhenDestroyed(&cache, b);
177        b->setDeleteWhenDestroyed(&cache, a);
178
179        a->unref();
180        b->unref();
181
182        cache.deleteResource(a->getCacheEntry());
183
184        REPORTER_ASSERT(reporter, 0 == TestResource::alive());
185    }
186}
187
188static void test_resource_size_changed(skiatest::Reporter* reporter,
189                                       GrContext* context) {
190    GrCacheID::Domain domain = GrCacheID::GenerateDomain();
191    GrResourceKey::ResourceType t = GrResourceKey::GenerateResourceType();
192
193    GrCacheID::Key key1Data;
194    key1Data.fData64[0] = 0;
195    key1Data.fData64[1] = 0;
196    GrResourceKey key1(GrCacheID(domain, key1Data), t, 0);
197
198    GrCacheID::Key key2Data;
199    key2Data.fData64[0] = 1;
200    key2Data.fData64[1] = 0;
201    GrResourceKey key2(GrCacheID(domain, key2Data), t, 0);
202
203    // Test changing resources sizes (both increase & decrease).
204    {
205        GrResourceCache cache(2, 300);
206
207        TestResource* a = new TestResource(0);
208        a->setSize(100); // Test didChangeGpuMemorySize() when not in the cache.
209        cache.addResource(key1, a);
210        a->unref();
211
212        TestResource* b = new TestResource(0);
213        b->setSize(100);
214        cache.addResource(key2, b);
215        b->unref();
216
217        REPORTER_ASSERT(reporter, 200 == cache.getCachedResourceBytes());
218        REPORTER_ASSERT(reporter, 2 == cache.getCachedResourceCount());
219
220        static_cast<TestResource*>(cache.find(key2))->setSize(200);
221        static_cast<TestResource*>(cache.find(key1))->setSize(50);
222
223        REPORTER_ASSERT(reporter, 250 == cache.getCachedResourceBytes());
224        REPORTER_ASSERT(reporter, 2 == cache.getCachedResourceCount());
225    }
226
227    // Test increasing a resources size beyond the cache budget.
228    {
229        GrResourceCache cache(2, 300);
230
231        TestResource* a = new TestResource(100);
232        cache.addResource(key1, a);
233        a->unref();
234
235        TestResource* b = new TestResource(100);
236        cache.addResource(key2, b);
237        b->unref();
238
239        REPORTER_ASSERT(reporter, 200 == cache.getCachedResourceBytes());
240        REPORTER_ASSERT(reporter, 2 == cache.getCachedResourceCount());
241
242        static_cast<TestResource*>(cache.find(key2))->setSize(201);
243        REPORTER_ASSERT(reporter, NULL == cache.find(key1));
244
245        REPORTER_ASSERT(reporter, 201 == cache.getCachedResourceBytes());
246        REPORTER_ASSERT(reporter, 1 == cache.getCachedResourceCount());
247    }
248
249    // Test changing the size of an exclusively-held resource.
250    {
251        GrResourceCache cache(2, 300);
252
253        TestResource* a = new TestResource(100);
254        cache.addResource(key1, a);
255        cache.makeExclusive(a->getCacheEntry());
256
257        TestResource* b = new TestResource(100);
258        cache.addResource(key2, b);
259        b->unref();
260
261        REPORTER_ASSERT(reporter, 200 == cache.getCachedResourceBytes());
262        REPORTER_ASSERT(reporter, 2 == cache.getCachedResourceCount());
263        REPORTER_ASSERT(reporter, NULL == cache.find(key1));
264
265        a->setSize(200);
266
267        REPORTER_ASSERT(reporter, 300 == cache.getCachedResourceBytes());
268        REPORTER_ASSERT(reporter, 2 == cache.getCachedResourceCount());
269        // Internal resource cache validation will test the detached size (debug mode only).
270
271        cache.makeNonExclusive(a->getCacheEntry());
272        a->unref();
273
274        REPORTER_ASSERT(reporter, 300 == cache.getCachedResourceBytes());
275        REPORTER_ASSERT(reporter, 2 == cache.getCachedResourceCount());
276        REPORTER_ASSERT(reporter, NULL != cache.find(key1));
277        // Internal resource cache validation will test the detached size (debug mode only).
278    }
279}
280
281////////////////////////////////////////////////////////////////////////////////
282DEF_GPUTEST(ResourceCache, reporter, factory) {
283    for (int type = 0; type < GrContextFactory::kLastGLContextType; ++type) {
284        GrContextFactory::GLContextType glType = static_cast<GrContextFactory::GLContextType>(type);
285        if (!GrContextFactory::IsRenderingGLContext(glType)) {
286            continue;
287        }
288        GrContext* context = factory->get(glType);
289        if (NULL == context) {
290            continue;
291        }
292
293        GrTextureDesc desc;
294        desc.fConfig = kSkia8888_GrPixelConfig;
295        desc.fFlags = kRenderTarget_GrTextureFlagBit;
296        desc.fWidth = gWidth;
297        desc.fHeight = gHeight;
298
299        SkAutoTUnref<GrTexture> texture(context->createUncachedTexture(desc, NULL, 0));
300        SkAutoTUnref<SkGpuDevice> device(SkNEW_ARGS(SkGpuDevice, (context, texture.get())));
301        SkCanvas canvas(device.get());
302
303        test_cache(reporter, context, &canvas);
304        test_purge_invalidated(reporter, context);
305        test_cache_delete_on_destruction(reporter, context);
306        test_resource_size_changed(reporter, context);
307    }
308}
309
310#endif
311