blob: 999bf1a94f1f0bb392c8662527db81114569078b [file] [log] [blame]
/*
* Copyright 2011 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkTextureCache.h"
//#define TRACE_HASH_HITS
//#define TRACE_TEXTURE_CACHE_PURGE
SkTextureCache::Entry::Entry(const SkBitmap& bitmap)
: fName(0), fKey(bitmap), fPrev(NULL), fNext(NULL) {
fMemSize = SkGL::ComputeTextureMemorySize(bitmap);
fLockCount = 0;
}
SkTextureCache::Entry::~Entry() {
if (fName != 0) {
glDeleteTextures(1, &fName);
}
}
///////////////////////////////////////////////////////////////////////////////
SkTextureCache::SkTextureCache(size_t countMax, size_t sizeMax)
: fHead(NULL), fTail(NULL),
fTexCountMax(countMax), fTexSizeMax(sizeMax),
fTexCount(0), fTexSize(0) {
sk_bzero(fHash, sizeof(fHash));
this->validate();
}
SkTextureCache::~SkTextureCache() {
#ifdef SK_DEBUG
Entry* entry = fHead;
while (entry) {
SkASSERT(entry->lockCount() == 0);
entry = entry->fNext;
}
#endif
this->validate();
}
void SkTextureCache::deleteAllCaches(bool texturesAreValid) {
this->validate();
Entry* entry = fHead;
while (entry) {
Entry* next = entry->fNext;
if (!texturesAreValid) {
entry->abandonTexture();
}
SkDELETE(entry);
entry = next;
}
fSorted.reset();
sk_bzero(fHash, sizeof(fHash));
fTexCount = 0;
fTexSize = 0;
fTail = fHead = NULL;
this->validate();
}
///////////////////////////////////////////////////////////////////////////////
int SkTextureCache::findInSorted(const Key& key) const {
int count = fSorted.count();
if (count == 0) {
return ~0;
}
Entry** sorted = fSorted.begin();
int lo = 0;
int hi = count - 1;
while (lo < hi) {
int mid = (hi + lo) >> 1;
if (sorted[mid]->getKey() < key) {
lo = mid + 1;
} else {
hi = mid;
}
}
// hi is now our best guess
const Entry* entry = sorted[hi];
if (entry->getKey() == key) {
return hi;
}
// return where to insert it
if (entry->getKey() < key) {
hi += 1;
}
return ~hi; // we twiddle to indicate not-found
}
#ifdef TRACE_HASH_HITS
static int gHashHits;
static int gSortedHits;
#endif
SkTextureCache::Entry* SkTextureCache::find(const Key& key, int* insert) const {
int count = fSorted.count();
if (count == 0) {
*insert = 0;
return NULL;
}
// check the hash first
int hashIndex = key.getHashIndex();
Entry* entry = fHash[hashIndex];
if (NULL != entry && entry->getKey() == key) {
#ifdef TRACE_HASH_HITS
gHashHits += 1;
#endif
return entry;
}
int index = this->findInSorted(key);
if (index >= 0) {
#ifdef TRACE_HASH_HITS
gSortedHits += 1;
#endif
entry = fSorted[index];
fHash[hashIndex] = entry;
return entry;
}
// ~index is where to insert the entry
*insert = ~index;
return NULL;
}
SkTextureCache::Entry* SkTextureCache::lock(const SkBitmap& bitmap) {
this->validate();
// call this before we call find(), so we don't reorder after find() and
// invalidate our index
this->purgeIfNecessary(SkGL::ComputeTextureMemorySize(bitmap));
Key key(bitmap);
int index;
Entry* entry = this->find(key, &index);
if (NULL == entry) {
entry = SkNEW_ARGS(Entry, (bitmap));
entry->fName = SkGL::BindNewTexture(bitmap, &entry->fTexSize);
if (0 == entry->fName) {
SkDELETE(entry);
return NULL;
}
fHash[key.getHashIndex()] = entry;
*fSorted.insert(index) = entry;
fTexCount += 1;
fTexSize += entry->memSize();
} else {
// detach from our llist
Entry* prev = entry->fPrev;
Entry* next = entry->fNext;
if (prev) {
prev->fNext = next;
} else {
SkASSERT(fHead == entry);
fHead = next;
}
if (next) {
next->fPrev = prev;
} else {
SkASSERT(fTail == entry);
fTail = prev;
}
// now bind the texture
glBindTexture(GL_TEXTURE_2D, entry->fName);
}
// add to head of llist for LRU
entry->fPrev = NULL;
entry->fNext = fHead;
if (NULL != fHead) {
SkASSERT(NULL == fHead->fPrev);
fHead->fPrev = entry;
}
fHead = entry;
if (NULL == fTail) {
fTail = entry;
}
this->validate();
entry->lock();
#ifdef TRACE_HASH_HITS
SkDebugf("---- texture cache hash=%d sorted=%d\n", gHashHits, gSortedHits);
#endif
return entry;
}
void SkTextureCache::unlock(Entry* entry) {
this->validate();
#ifdef SK_DEBUG
SkASSERT(entry);
int index = this->findInSorted(entry->getKey());
SkASSERT(fSorted[index] == entry);
#endif
SkASSERT(entry->fLockCount > 0);
entry->unlock();
}
void SkTextureCache::purgeIfNecessary(size_t extraSize) {
this->validate();
size_t countMax = fTexCountMax;
size_t sizeMax = fTexSizeMax;
// take extraSize into account, but watch for underflow of size_t
if (extraSize > sizeMax) {
sizeMax = 0;
} else {
sizeMax -= extraSize;
}
Entry* entry = fTail;
while (entry) {
if (fTexCount <= countMax && fTexSize <= sizeMax) {
break;
}
Entry* prev = entry->fPrev;
// don't purge an entry that is locked
if (entry->isLocked()) {
entry = prev;
continue;
}
fTexCount -= 1;
fTexSize -= entry->memSize();
// remove from our sorted and hash arrays
int index = this->findInSorted(entry->getKey());
SkASSERT(index >= 0);
fSorted.remove(index);
index = entry->getKey().getHashIndex();
if (entry == fHash[index]) {
fHash[index] = NULL;
}
// now detach it from our llist
Entry* next = entry->fNext;
if (prev) {
prev->fNext = next;
} else {
fHead = next;
}
if (next) {
next->fPrev = prev;
} else {
fTail = prev;
}
// now delete it
#ifdef TRACE_TEXTURE_CACHE_PURGE
SkDebugf("---- purge texture cache %d size=%d\n",
entry->name(), entry->memSize());
#endif
SkDELETE(entry);
// keep going
entry = prev;
}
this->validate();
}
void SkTextureCache::setMaxCount(size_t count) {
if (fTexCountMax != count) {
fTexCountMax = count;
this->purgeIfNecessary(0);
}
}
void SkTextureCache::setMaxSize(size_t size) {
if (fTexSizeMax != size) {
fTexSizeMax = size;
this->purgeIfNecessary(0);
}
}
///////////////////////////////////////////////////////////////////////////////
#ifdef SK_DEBUG
void SkTextureCache::validate() const {
if (0 == fTexCount) {
SkASSERT(0 == fTexSize);
SkASSERT(NULL == fHead);
SkASSERT(NULL == fTail);
return;
}
SkASSERT(fTexSize); // do we allow a zero-sized texture?
SkASSERT(fHead);
SkASSERT(fTail);
SkASSERT(NULL == fHead->fPrev);
SkASSERT(NULL == fTail->fNext);
if (1 == fTexCount) {
SkASSERT(fHead == fTail);
}
const Entry* entry = fHead;
size_t count = 0;
size_t size = 0;
size_t i;
while (entry != NULL) {
SkASSERT(count < fTexCount);
SkASSERT(size < fTexSize);
size += entry->memSize();
count += 1;
if (NULL == entry->fNext) {
SkASSERT(fTail == entry);
}
entry = entry->fNext;
}
SkASSERT(count == fTexCount);
SkASSERT(size == fTexSize);
count = 0;
size = 0;
entry = fTail;
while (entry != NULL) {
SkASSERT(count < fTexCount);
SkASSERT(size < fTexSize);
size += entry->memSize();
count += 1;
if (NULL == entry->fPrev) {
SkASSERT(fHead == entry);
}
entry = entry->fPrev;
}
SkASSERT(count == fTexCount);
SkASSERT(size == fTexSize);
SkASSERT(count == (size_t)fSorted.count());
for (i = 1; i < count; i++) {
SkASSERT(fSorted[i-1]->getKey() < fSorted[i]->getKey());
}
for (i = 0; i < kHashCount; i++) {
if (fHash[i]) {
size_t index = fHash[i]->getKey().getHashIndex();
SkASSERT(index == i);
index = fSorted.find(fHash[i]);
SkASSERT((size_t)index < count);
}
}
}
#endif