C++程序  |  364行  |  8.39 KB

#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) {

    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();
    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