aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/core/SkBitmapCache.cpp
blob: 3b85657b1bacefe2cd5badaacaabc2da8c1b3f20 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
/*
 * Copyright 2014 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "SkBitmapCache.h"
#include "SkImage.h"
#include "SkResourceCache.h"
#include "SkMipMap.h"
#include "SkPixelRef.h"
#include "SkRect.h"

/**
 *  Use this for bitmapcache and mipmapcache entries.
 */
uint64_t SkMakeResourceCacheSharedIDForBitmap(uint32_t bitmapGenID) {
    uint64_t sharedID = SkSetFourByteTag('b', 'm', 'a', 'p');
    return (sharedID << 32) | bitmapGenID;
}

void SkNotifyBitmapGenIDIsStale(uint32_t bitmapGenID) {
    SkResourceCache::PostPurgeSharedID(SkMakeResourceCacheSharedIDForBitmap(bitmapGenID));
}

///////////////////////////////////////////////////////////////////////////////////////////////////

/**
 This function finds the bounds of the bitmap *within its pixelRef*.
 If the bitmap lacks a pixelRef, it will return an empty rect, since
 that doesn't make sense.  This may be a useful enough function that
 it should be somewhere else (in SkBitmap?).
 */
static SkIRect get_bounds_from_bitmap(const SkBitmap& bm) {
    if (!(bm.pixelRef())) {
        return SkIRect::MakeEmpty();
    }
    SkIPoint origin = bm.pixelRefOrigin();
    return SkIRect::MakeXYWH(origin.fX, origin.fY, bm.width(), bm.height());
}

/**
 *  This function finds the bounds of the image. Today this is just the entire bounds,
 *  but in the future we may support subsets within an image, in which case this should
 *  return that subset (see get_bounds_from_bitmap).
 */
static SkIRect get_bounds_from_image(const SkImage* image) {
    SkASSERT(image->width() > 0 && image->height() > 0);
    return SkIRect::MakeWH(image->width(), image->height());
}

SkBitmapCacheDesc SkBitmapCacheDesc::Make(uint32_t imageID, int origWidth, int origHeight) {
    SkASSERT(imageID);
    SkASSERT(origWidth > 0 && origHeight > 0);
    return { imageID, 0, 0, {0, 0, origWidth, origHeight} };
}

SkBitmapCacheDesc SkBitmapCacheDesc::Make(const SkBitmap& bm, int scaledWidth, int scaledHeight) {
    SkASSERT(bm.width() > 0 && bm.height() > 0);
    SkASSERT(scaledWidth > 0 && scaledHeight > 0);
    SkASSERT(scaledWidth != bm.width() || scaledHeight != bm.height());

    return { bm.getGenerationID(), scaledWidth, scaledHeight, get_bounds_from_bitmap(bm) };
}

SkBitmapCacheDesc SkBitmapCacheDesc::Make(const SkBitmap& bm) {
    SkASSERT(bm.width() > 0 && bm.height() > 0);
    SkASSERT(bm.pixelRefOrigin() == SkIPoint::Make(0, 0));

    return { bm.getGenerationID(), 0, 0, get_bounds_from_bitmap(bm) };
}

SkBitmapCacheDesc SkBitmapCacheDesc::Make(const SkImage* image, int scaledWidth, int scaledHeight) {
    SkASSERT(image->width() > 0 && image->height() > 0);
    SkASSERT(scaledWidth > 0 && scaledHeight > 0);

    // If the dimensions are the same, should we set them to 0,0?
    //SkASSERT(scaledWidth != image->width() || scaledHeight != image->height());

    return { image->uniqueID(), scaledWidth, scaledHeight, get_bounds_from_image(image) };
}

SkBitmapCacheDesc SkBitmapCacheDesc::Make(const SkImage* image) {
    SkASSERT(image->width() > 0 && image->height() > 0);

    return { image->uniqueID(), 0, 0, get_bounds_from_image(image) };
}

namespace {
static unsigned gBitmapKeyNamespaceLabel;

struct BitmapKey : public SkResourceCache::Key {
public:
    BitmapKey(const SkBitmapCacheDesc& desc) : fDesc(desc) {
        this->init(&gBitmapKeyNamespaceLabel, SkMakeResourceCacheSharedIDForBitmap(fDesc.fImageID),
                   sizeof(fDesc));
    }

    void dump() const {
        SkDebugf("-- add [%d %d] %d [%d %d %d %d]\n",
                 fDesc.fScaledWidth, fDesc.fScaledHeight, fDesc.fImageID,
             fDesc.fSubset.x(), fDesc.fSubset.y(), fDesc.fSubset.width(), fDesc.fSubset.height());
    }

    const SkBitmapCacheDesc fDesc;
};
}

//////////////////////
#include "SkDiscardableMemory.h"
#include "SkNextID.h"

void SkBitmapCache_setImmutableWithID(SkPixelRef* pr, uint32_t id) {
    pr->setImmutableWithID(id);
}

//#define REC_TRACE   SkDebugf
static void REC_TRACE(const char format[], ...) {}

// for diagnostics
static int32_t gRecCounter;

class SkBitmapCache::Rec : public SkResourceCache::Rec {
public:
    Rec(const SkBitmapCacheDesc& desc, const SkImageInfo& info, size_t rowBytes,
        std::unique_ptr<SkDiscardableMemory> dm, void* block)
        : fKey(desc)
        , fDM(std::move(dm))
        , fMalloc(block)
        , fInfo(info)
        , fRowBytes(rowBytes)
        , fExternalCounter(kBeforeFirstInstall_ExternalCounter)
    {
        SkASSERT(!(fDM && fMalloc));    // can't have both

        // We need an ID to return with the bitmap/pixelref.
        // If they are not scaling, we can return the same ID as the key/desc
        // If they are scaling, we need a new ID
        if (desc.fScaledWidth == 0 && desc.fScaledHeight == 0) {
            fPrUniqueID = desc.fImageID;
        } else {
            fPrUniqueID = SkNextID::ImageID();
        }
        REC_TRACE(" Rec(%d): [%d %d] %d\n",
                  sk_atomic_inc(&gRecCounter), fInfo.width(), fInfo.height(), fPrUniqueID);
    }

    ~Rec() override {
        SkASSERT(0 == fExternalCounter || kBeforeFirstInstall_ExternalCounter == fExternalCounter);
        if (fDM && kBeforeFirstInstall_ExternalCounter == fExternalCounter) {
            // we never installed, so we need to unlock before we destroy the DM
            SkASSERT(fDM->data());
            fDM->unlock();
        }
        REC_TRACE("~Rec(%d): [%d %d] %d\n",
                  sk_atomic_dec(&gRecCounter) - 1, fInfo.width(), fInfo.height(), fPrUniqueID);
        sk_free(fMalloc);   // may be null
    }

    const Key& getKey() const override { return fKey; }
    size_t bytesUsed() const override {
        return sizeof(fKey) + fInfo.computeByteSize(fRowBytes);
    }
    bool canBePurged() override {
        SkAutoMutexAcquire ama(fMutex);
        return fExternalCounter == 0;
    }
    void postAddInstall(void* payload) override {
        SkAssertResult(this->install(static_cast<SkBitmap*>(payload)));
    }

    const char* getCategory() const override { return "bitmap"; }
    SkDiscardableMemory* diagnostic_only_getDiscardable() const override {
        return fDM.get();
    }

    static void ReleaseProc(void* addr, void* ctx) {
        Rec* rec = static_cast<Rec*>(ctx);
        SkAutoMutexAcquire ama(rec->fMutex);

        REC_TRACE(" Rec: [%d] releaseproc\n", rec->fPrUniqueID);

        SkASSERT(rec->fExternalCounter > 0);
        rec->fExternalCounter -= 1;
        if (rec->fDM) {
            SkASSERT(rec->fMalloc == nullptr);
            if (rec->fExternalCounter == 0) {
                REC_TRACE(" Rec [%d] unlock\n", rec->fPrUniqueID);
                rec->fDM->unlock();
            }
        } else {
            SkASSERT(rec->fMalloc != nullptr);
        }
    }

    bool install(SkBitmap* bitmap) {
        SkAutoMutexAcquire ama(fMutex);

        // are we still valid
        if (!fDM && !fMalloc) {
            REC_TRACE(" Rec: [%d] invalid\n", fPrUniqueID);
            return false;
        }

        /*
            constructor      fExternalCount < 0     fDM->data()
            after install    fExternalCount > 0     fDM->data()
            after Release    fExternalCount == 0    !fDM->data()
        */
        if (fDM) {
            if (kBeforeFirstInstall_ExternalCounter == fExternalCounter) {
                SkASSERT(fDM->data());
            } else if (fExternalCounter > 0) {
                SkASSERT(fDM->data());
            } else {
                SkASSERT(fExternalCounter == 0);
                if (!fDM->lock()) {
                    REC_TRACE(" Rec [%d] re-lock failed\n", fPrUniqueID);
                    fDM.reset(nullptr);
                    return false;
                }
                REC_TRACE(" Rec [%d] re-lock succeeded\n", fPrUniqueID);
            }
            SkASSERT(fDM->data());
        }

        bitmap->installPixels(fInfo, fDM ? fDM->data() : fMalloc, fRowBytes, ReleaseProc, this);
        SkBitmapCache_setImmutableWithID(bitmap->pixelRef(), fPrUniqueID);

        REC_TRACE(" Rec: [%d] install new pr\n", fPrUniqueID);

        if (kBeforeFirstInstall_ExternalCounter == fExternalCounter) {
            fExternalCounter = 1;
        } else {
            fExternalCounter += 1;
        }
        SkASSERT(fExternalCounter > 0);
        return true;
    }

    static bool Finder(const SkResourceCache::Rec& baseRec, void* contextBitmap) {
        Rec* rec = (Rec*)&baseRec;
        SkBitmap* result = (SkBitmap*)contextBitmap;
        REC_TRACE(" Rec: [%d] found\n", rec->fPrUniqueID);
        return rec->install(result);
    }

private:
    BitmapKey   fKey;

    SkMutex     fMutex;

    // either fDM or fMalloc can be non-null, but not both
    std::unique_ptr<SkDiscardableMemory> fDM;
    void*       fMalloc;

    SkImageInfo fInfo;
    size_t      fRowBytes;
    uint32_t    fPrUniqueID;

    // This field counts the number of external pixelrefs we have created. They notify us when
    // they are destroyed so we can decrement this.
    //
    //  > 0     we have outstanding pixelrefs
    // == 0     we have no outstanding pixelrefs, and can be safely purged
    //  < 0     we have been created, but not yet "installed" the first time.
    //
    int         fExternalCounter;

    enum {
        kBeforeFirstInstall_ExternalCounter = -1
    };
};

void SkBitmapCache::PrivateDeleteRec(Rec* rec) { delete rec; }

SkBitmapCache::RecPtr SkBitmapCache::Alloc(const SkBitmapCacheDesc& desc, const SkImageInfo& info,
                                           SkPixmap* pmap) {
    // Ensure that the caller is self-consistent:
    //  - if they are scaling, the info matches the scaled size
    //  - if they are not, the info matches the subset (i.e. the subset is the entire image)
    if (desc.fScaledWidth == 0 && desc.fScaledHeight == 0) {
        SkASSERT(info.width() == desc.fSubset.width());
        SkASSERT(info.height() == desc.fSubset.height());
    } else {
        SkASSERT(info.width() == desc.fScaledWidth);
        SkASSERT(info.height() == desc.fScaledHeight);
    }

    const size_t rb = info.minRowBytes();
    size_t size = info.computeByteSize(rb);
    if (SkImageInfo::ByteSizeOverflowed(size)) {
        return nullptr;
    }

    std::unique_ptr<SkDiscardableMemory> dm;
    void* block = nullptr;

    auto factory = SkResourceCache::GetDiscardableFactory();
    if (factory) {
        dm.reset(factory(size));
    } else {
        block = sk_malloc_canfail(size);
    }
    if (!dm && !block) {
        return nullptr;
    }
    *pmap = SkPixmap(info, dm ? dm->data() : block, rb);
    return RecPtr(new Rec(desc, info, rb, std::move(dm), block));
}

void SkBitmapCache::Add(RecPtr rec, SkBitmap* bitmap) {
    SkResourceCache::Add(rec.release(), bitmap);
}

bool SkBitmapCache::Find(const SkBitmapCacheDesc& desc, SkBitmap* result) {
    desc.validate();
    return SkResourceCache::Find(BitmapKey(desc), SkBitmapCache::Rec::Finder, result);
}

//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////

#define CHECK_LOCAL(localCache, localName, globalName, ...) \
    ((localCache) ? localCache->localName(__VA_ARGS__) : SkResourceCache::globalName(__VA_ARGS__))

namespace {
static unsigned gMipMapKeyNamespaceLabel;

struct MipMapKey : public SkResourceCache::Key {
public:
    MipMapKey(uint32_t imageID, const SkIRect& subset)
        : fImageID(imageID)
        , fSubset(subset)
    {
        SkASSERT(fImageID);
        SkASSERT(!subset.isEmpty());
        this->init(&gMipMapKeyNamespaceLabel, SkMakeResourceCacheSharedIDForBitmap(fImageID),
                   sizeof(fImageID) + sizeof(fSubset));
    }

    uint32_t    fImageID;
    SkIRect     fSubset;
};

struct MipMapRec : public SkResourceCache::Rec {
    MipMapRec(uint32_t imageID, const SkIRect& subset, const SkMipMap* result)
        : fKey(imageID, subset)
        , fMipMap(result)
    {
        fMipMap->attachToCacheAndRef();
    }

    ~MipMapRec() override {
        fMipMap->detachFromCacheAndUnref();
    }

    const Key& getKey() const override { return fKey; }
    size_t bytesUsed() const override { return sizeof(fKey) + fMipMap->size(); }
    const char* getCategory() const override { return "mipmap"; }
    SkDiscardableMemory* diagnostic_only_getDiscardable() const override {
        return fMipMap->diagnostic_only_getDiscardable();
    }

    static bool Finder(const SkResourceCache::Rec& baseRec, void* contextMip) {
        const MipMapRec& rec = static_cast<const MipMapRec&>(baseRec);
        const SkMipMap* mm = SkRef(rec.fMipMap);
        // the call to ref() above triggers a "lock" in the case of discardable memory,
        // which means we can now check for null (in case the lock failed).
        if (nullptr == mm->data()) {
            mm->unref();    // balance our call to ref()
            return false;
        }
        // the call must call unref() when they are done.
        *(const SkMipMap**)contextMip = mm;
        return true;
    }

private:
    MipMapKey       fKey;
    const SkMipMap* fMipMap;
};
}

const SkMipMap* SkMipMapCache::FindAndRef(const SkBitmapCacheDesc& desc,
                                          SkResourceCache* localCache) {
    SkASSERT(desc.fScaledWidth == 0);
    SkASSERT(desc.fScaledHeight == 0);
    MipMapKey key(desc.fImageID, desc.fSubset);
    const SkMipMap* result;

    if (!CHECK_LOCAL(localCache, find, Find, key, MipMapRec::Finder, &result)) {
        result = nullptr;
    }
    return result;
}

static SkResourceCache::DiscardableFactory get_fact(SkResourceCache* localCache) {
    return localCache ? localCache->GetDiscardableFactory()
                      : SkResourceCache::GetDiscardableFactory();
}

const SkMipMap* SkMipMapCache::AddAndRef(const SkBitmap& src, SkResourceCache* localCache) {
    SkMipMap* mipmap = SkMipMap::Build(src, get_fact(localCache));
    if (mipmap) {
        MipMapRec* rec = new MipMapRec(src.getGenerationID(), get_bounds_from_bitmap(src), mipmap);
        CHECK_LOCAL(localCache, add, Add, rec);
        src.pixelRef()->notifyAddedToCache();
    }
    return mipmap;
}