/* Copyright 2010 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #include "GrTextureCache.h" #include "GrTexture.h" GrTextureEntry::GrTextureEntry(const GrTextureKey& key, GrTexture* texture) : fKey(key), fTexture(texture) { fLockCount = 0; fPrev = fNext = NULL; // we assume ownership of the texture, and will unref it when we die GrAssert(texture); } GrTextureEntry::~GrTextureEntry() { fTexture->unref(); } #if GR_DEBUG void GrTextureEntry::validate() const { GrAssert(fLockCount >= 0); GrAssert(fTexture); fTexture->validate(); } #endif /////////////////////////////////////////////////////////////////////////////// GrTextureCache::GrTextureCache(int maxCount, size_t maxBytes) : fMaxCount(maxCount), fMaxBytes(maxBytes) { fEntryCount = 0; fEntryBytes = 0; fClientDetachedCount = 0; fClientDetachedBytes = 0; fHead = fTail = NULL; } GrTextureCache::~GrTextureCache() { GrAutoTextureCacheValidate atcv(this); this->removeAll(); } void GrTextureCache::getLimits(int* maxTextures, size_t* maxTextureBytes) const{ if (maxTextures) { *maxTextures = fMaxCount; } if (maxTextureBytes) { *maxTextureBytes = fMaxBytes; } } void GrTextureCache::setLimits(int maxTextures, size_t maxTextureBytes) { bool smaller = (maxTextures < fMaxCount) || (maxTextureBytes < fMaxBytes); fMaxCount = maxTextures; fMaxBytes = maxTextureBytes; if (smaller) { this->purgeAsNeeded(); } } void GrTextureCache::internalDetach(GrTextureEntry* entry, bool clientDetach) { GrTextureEntry* prev = entry->fPrev; GrTextureEntry* next = entry->fNext; if (prev) { prev->fNext = next; } else { fHead = next; } if (next) { next->fPrev = prev; } else { fTail = prev; } // update our stats if (clientDetach) { fClientDetachedCount += 1; fClientDetachedBytes += entry->texture()->sizeInBytes(); } else { fEntryCount -= 1; fEntryBytes -= entry->texture()->sizeInBytes(); } } void GrTextureCache::attachToHead(GrTextureEntry* entry, bool clientReattach) { entry->fPrev = NULL; entry->fNext = fHead; if (fHead) { fHead->fPrev = entry; } fHead = entry; if (NULL == fTail) { fTail = entry; } // update our stats if (clientReattach) { fClientDetachedCount -= 1; fClientDetachedBytes -= entry->texture()->sizeInBytes(); } else { fEntryCount += 1; fEntryBytes += entry->texture()->sizeInBytes(); } } class GrTextureCache::Key { typedef GrTextureEntry T; const GrTextureKey& fKey; public: Key(const GrTextureKey& key) : fKey(key) {} uint32_t getHash() const { return fKey.hashIndex(); } static bool LT(const T& entry, const Key& key) { return entry.key() < key.fKey; } static bool EQ(const T& entry, const Key& key) { return entry.key() == key.fKey; } #if GR_DEBUG static uint32_t GetHash(const T& entry) { return entry.key().hashIndex(); } static bool LT(const T& a, const T& b) { return a.key() < b.key(); } static bool EQ(const T& a, const T& b) { return a.key() == b.key(); } #endif }; GrTextureEntry* GrTextureCache::findAndLock(const GrTextureKey& key) { GrAutoTextureCacheValidate atcv(this); GrTextureEntry* entry = fCache.find(key); if (entry) { this->internalDetach(entry, false); this->attachToHead(entry, false); // mark the entry as "busy" so it doesn't get purged entry->lock(); } return entry; } GrTextureEntry* GrTextureCache::createAndLock(const GrTextureKey& key, GrTexture* texture) { GrAutoTextureCacheValidate atcv(this); GrTextureEntry* entry = new GrTextureEntry(key, texture); this->attachToHead(entry, false); fCache.insert(key, entry); #if GR_DUMP_TEXTURE_UPLOAD GrPrintf("--- add texture to cache %p, count=%d bytes= %d %d\n", entry, fEntryCount, texture->sizeInBytes(), fEntryBytes); #endif // mark the entry as "busy" so it doesn't get purged entry->lock(); this->purgeAsNeeded(); return entry; } void GrTextureCache::detach(GrTextureEntry* entry) { internalDetach(entry, true); fCache.remove(entry->fKey, entry); } void GrTextureCache::reattachAndUnlock(GrTextureEntry* entry) { attachToHead(entry, true); fCache.insert(entry->key(), entry); unlock(entry); } void GrTextureCache::unlock(GrTextureEntry* entry) { GrAutoTextureCacheValidate atcv(this); GrAssert(entry); GrAssert(entry->isLocked()); GrAssert(fCache.find(entry->key())); entry->unlock(); this->purgeAsNeeded(); } void GrTextureCache::purgeAsNeeded() { GrAutoTextureCacheValidate atcv(this); GrTextureEntry* entry = fTail; while (entry) { if (fEntryCount <= fMaxCount && fEntryBytes <= fMaxBytes) { break; } GrTextureEntry* prev = entry->fPrev; if (!entry->isLocked()) { // remove from our cache fCache.remove(entry->fKey, entry); // remove from our llist this->internalDetach(entry, false); #if GR_DUMP_TEXTURE_UPLOAD GrPrintf("--- ~texture from cache %p [%d %d]\n", entry->texture(), entry->texture()->width(), entry->texture()->height()); #endif delete entry; } entry = prev; } } void GrTextureCache::removeAll() { GrAssert(!fClientDetachedCount); GrAssert(!fClientDetachedBytes); GrTextureEntry* entry = fHead; while (entry) { GrAssert(!entry->isLocked()); GrTextureEntry* next = entry->fNext; delete entry; entry = next; } fCache.removeAll(); fHead = fTail = NULL; fEntryCount = 0; fEntryBytes = 0; } /////////////////////////////////////////////////////////////////////////////// #if GR_DEBUG static int countMatches(const GrTextureEntry* head, const GrTextureEntry* target) { const GrTextureEntry* entry = head; int count = 0; while (entry) { if (target == entry) { count += 1; } entry = entry->next(); } return count; } #if GR_DEBUG static bool both_zero_or_nonzero(int count, size_t bytes) { return (count == 0 && bytes == 0) || (count > 0 && bytes > 0); } #endif void GrTextureCache::validate() const { GrAssert(!fHead == !fTail); GrAssert(both_zero_or_nonzero(fEntryCount, fEntryBytes)); GrAssert(both_zero_or_nonzero(fClientDetachedCount, fClientDetachedBytes)); GrAssert(fClientDetachedBytes <= fEntryBytes); GrAssert(fClientDetachedCount <= fEntryCount); GrAssert((fEntryCount - fClientDetachedCount) == fCache.count()); fCache.validate(); GrTextureEntry* entry = fHead; int count = 0; size_t bytes = 0; while (entry) { entry->validate(); GrAssert(fCache.find(entry->key())); count += 1; bytes += entry->texture()->sizeInBytes(); entry = entry->fNext; } GrAssert(count == fEntryCount - fClientDetachedCount); GrAssert(bytes == fEntryBytes - fClientDetachedBytes); count = 0; for (entry = fTail; entry; entry = entry->fPrev) { count += 1; } GrAssert(count == fEntryCount - fClientDetachedCount); for (int i = 0; i < count; i++) { int matches = countMatches(fHead, fCache.getArray()[i]); GrAssert(1 == matches); } } #endif