/* libs/graphics/sgl/SkGlyphCache.cpp ** ** Copyright 2006, The Android Open Source Project ** ** 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 "SkGlyphCache.h" #include "SkFontHost.h" #include "SkPaint.h" #include "SkTemplates.h" #define SPEW_PURGE_STATUS //#define USE_CACHE_HASH //#define RECORD_HASH_EFFICIENCY /////////////////////////////////////////////////////////////////////////////// #ifdef RECORD_HASH_EFFICIENCY static uint32_t gHashSuccess; static uint32_t gHashCollision; static void RecordHashSuccess() { gHashSuccess += 1; } static void RecordHashCollisionIf(bool pred) { if (pred) { gHashCollision += 1; uint32_t total = gHashSuccess + gHashCollision; SkDebugf("Font Cache Hash success rate: %d%%\n", 100 * gHashSuccess / total); } } #else #define RecordHashSuccess() (void)0 #define RecordHashCollisionIf(pred) (void)0 #endif #define RecordHashCollision() RecordHashCollisionIf(true) /////////////////////////////////////////////////////////////////////////////// #define kMinGlphAlloc (sizeof(SkGlyph) * 64) #define kMinImageAlloc (24 * 64) // should be pointsize-dependent #define METRICS_RESERVE_COUNT 128 // so we don't grow this array a lot SkGlyphCache::SkGlyphCache(const SkDescriptor* desc) : fGlyphAlloc(kMinGlphAlloc), fImageAlloc(kMinImageAlloc) { fPrev = fNext = NULL; fDesc = desc->copy(); fScalerContext = SkScalerContext::Create(desc); fScalerContext->getFontMetrics(NULL, &fFontMetricsY); // init to 0 so that all of the pointers will be null memset(fGlyphHash, 0, sizeof(fGlyphHash)); // init with 0xFF so that the charCode field will be -1, which is invalid memset(fCharToGlyphHash, 0xFF, sizeof(fCharToGlyphHash)); fMemoryUsed = sizeof(*this) + kMinGlphAlloc + kMinImageAlloc; fGlyphArray.setReserve(METRICS_RESERVE_COUNT); fMetricsCount = 0; fAdvanceCount = 0; fAuxProcList = NULL; } SkGlyphCache::~SkGlyphCache() { SkGlyph** gptr = fGlyphArray.begin(); SkGlyph** stop = fGlyphArray.end(); while (gptr < stop) { SkPath* path = (*gptr)->fPath; if (path) { SkDELETE(path); } gptr += 1; } SkDescriptor::Free(fDesc); SkDELETE(fScalerContext); this->invokeAndRemoveAuxProcs(); } /////////////////////////////////////////////////////////////////////////////// #ifdef SK_DEBUG class AutoCheckForNull { public: AutoCheckForNull(const SkTDArray& array) : fArray(array) { for (int i = 0; i < array.count(); i++) SkASSERT(array[i]); } ~AutoCheckForNull() { const SkTDArray& array = fArray; for (int i = 0; i < array.count(); i++) { SkASSERT(array[i]); } } private: const SkTDArray& fArray; }; #define VALIDATE() AutoCheckForNull acfn(fGlyphArray) #else #define VALIDATE() #endif uint16_t SkGlyphCache::unicharToGlyph(SkUnichar charCode) { VALIDATE(); uint32_t id = SkGlyph::MakeID(charCode); const CharGlyphRec& rec = fCharToGlyphHash[ID2HashIndex(id)]; if (rec.fID == id) { return rec.fGlyph->getGlyphID(); } else { return fScalerContext->charToGlyphID(charCode); } } SkUnichar SkGlyphCache::glyphToUnichar(uint16_t glyphID) { return fScalerContext->glyphIDToChar(glyphID); } /////////////////////////////////////////////////////////////////////////////// const SkGlyph& SkGlyphCache::getUnicharAdvance(SkUnichar charCode) { VALIDATE(); uint32_t id = SkGlyph::MakeID(charCode); CharGlyphRec* rec = &fCharToGlyphHash[ID2HashIndex(id)]; if (rec->fID != id) { // this ID is based on the UniChar rec->fID = id; // this ID is based on the glyph index id = SkGlyph::MakeID(fScalerContext->charToGlyphID(charCode)); rec->fGlyph = this->lookupMetrics(id, kJustAdvance_MetricsType); } return *rec->fGlyph; } const SkGlyph& SkGlyphCache::getGlyphIDAdvance(uint16_t glyphID) { VALIDATE(); uint32_t id = SkGlyph::MakeID(glyphID); unsigned index = ID2HashIndex(id); SkGlyph* glyph = fGlyphHash[index]; if (NULL == glyph || glyph->fID != id) { glyph = this->lookupMetrics(glyphID, kJustAdvance_MetricsType); fGlyphHash[index] = glyph; } return *glyph; } /////////////////////////////////////////////////////////////////////////////// const SkGlyph& SkGlyphCache::getUnicharMetrics(SkUnichar charCode) { VALIDATE(); uint32_t id = SkGlyph::MakeID(charCode); CharGlyphRec* rec = &fCharToGlyphHash[ID2HashIndex(id)]; if (rec->fID != id) { RecordHashCollisionIf(rec->fGlyph != NULL); // this ID is based on the UniChar rec->fID = id; // this ID is based on the glyph index id = SkGlyph::MakeID(fScalerContext->charToGlyphID(charCode)); rec->fGlyph = this->lookupMetrics(id, kFull_MetricsType); } else { RecordHashSuccess(); if (rec->fGlyph->isJustAdvance()) { fScalerContext->getMetrics(rec->fGlyph); } } SkASSERT(rec->fGlyph->isFullMetrics()); return *rec->fGlyph; } const SkGlyph& SkGlyphCache::getUnicharMetrics(SkUnichar charCode, SkFixed x, SkFixed y) { VALIDATE(); uint32_t id = SkGlyph::MakeID(charCode, x, y); CharGlyphRec* rec = &fCharToGlyphHash[ID2HashIndex(id)]; if (rec->fID != id) { RecordHashCollisionIf(rec->fGlyph != NULL); // this ID is based on the UniChar rec->fID = id; // this ID is based on the glyph index id = SkGlyph::MakeID(fScalerContext->charToGlyphID(charCode), x, y); rec->fGlyph = this->lookupMetrics(id, kFull_MetricsType); } else { RecordHashSuccess(); if (rec->fGlyph->isJustAdvance()) { fScalerContext->getMetrics(rec->fGlyph); } } SkASSERT(rec->fGlyph->isFullMetrics()); return *rec->fGlyph; } const SkGlyph& SkGlyphCache::getGlyphIDMetrics(uint16_t glyphID) { VALIDATE(); uint32_t id = SkGlyph::MakeID(glyphID); unsigned index = ID2HashIndex(id); SkGlyph* glyph = fGlyphHash[index]; if (NULL == glyph || glyph->fID != id) { RecordHashCollisionIf(glyph != NULL); glyph = this->lookupMetrics(glyphID, kFull_MetricsType); fGlyphHash[index] = glyph; } else { RecordHashSuccess(); if (glyph->isJustAdvance()) { fScalerContext->getMetrics(glyph); } } SkASSERT(glyph->isFullMetrics()); return *glyph; } const SkGlyph& SkGlyphCache::getGlyphIDMetrics(uint16_t glyphID, SkFixed x, SkFixed y) { VALIDATE(); uint32_t id = SkGlyph::MakeID(glyphID, x, y); unsigned index = ID2HashIndex(id); SkGlyph* glyph = fGlyphHash[index]; if (NULL == glyph || glyph->fID != id) { RecordHashCollisionIf(glyph != NULL); glyph = this->lookupMetrics(id, kFull_MetricsType); fGlyphHash[index] = glyph; } else { RecordHashSuccess(); if (glyph->isJustAdvance()) { fScalerContext->getMetrics(glyph); } } SkASSERT(glyph->isFullMetrics()); return *glyph; } SkGlyph* SkGlyphCache::lookupMetrics(uint32_t id, MetricsType mtype) { SkGlyph* glyph; int hi = 0; int count = fGlyphArray.count(); if (count) { SkGlyph** gptr = fGlyphArray.begin(); int lo = 0; hi = count - 1; while (lo < hi) { int mid = (hi + lo) >> 1; if (gptr[mid]->fID < id) { lo = mid + 1; } else { hi = mid; } } glyph = gptr[hi]; if (glyph->fID == id) { if (kFull_MetricsType == mtype && glyph->isJustAdvance()) { fScalerContext->getMetrics(glyph); } return glyph; } // check if we need to bump hi before falling though to the allocator if (glyph->fID < id) { hi += 1; } } // not found, but hi tells us where to inser the new glyph fMemoryUsed += sizeof(SkGlyph); glyph = (SkGlyph*)fGlyphAlloc.alloc(sizeof(SkGlyph), SkChunkAlloc::kThrow_AllocFailType); glyph->fID = id; glyph->fImage = NULL; glyph->fPath = NULL; *fGlyphArray.insert(hi) = glyph; if (kJustAdvance_MetricsType == mtype) { fScalerContext->getAdvance(glyph); fAdvanceCount += 1; } else { SkASSERT(kFull_MetricsType == mtype); fScalerContext->getMetrics(glyph); fMetricsCount += 1; } return glyph; } const void* SkGlyphCache::findImage(const SkGlyph& glyph) { if (glyph.fWidth) { if (glyph.fImage == NULL) { size_t size = glyph.computeImageSize(); const_cast(glyph).fImage = fImageAlloc.alloc(size, SkChunkAlloc::kReturnNil_AllocFailType); fScalerContext->getImage(glyph); fMemoryUsed += size; } } return glyph.fImage; } const SkPath* SkGlyphCache::findPath(const SkGlyph& glyph) { if (glyph.fWidth) { if (glyph.fPath == NULL) { const_cast(glyph).fPath = SkNEW(SkPath); fScalerContext->getPath(glyph, glyph.fPath); fMemoryUsed += sizeof(SkPath) + glyph.fPath->getPoints(NULL, 0x7FFFFFFF) * sizeof(SkPoint); } } return glyph.fPath; } /////////////////////////////////////////////////////////////////////////////// bool SkGlyphCache::getAuxProcData(void (*proc)(void*), void** dataPtr) const { const AuxProcRec* rec = fAuxProcList; while (rec) { if (rec->fProc == proc) { if (dataPtr) { *dataPtr = rec->fData; } return true; } rec = rec->fNext; } return false; } void SkGlyphCache::setAuxProc(void (*proc)(void*), void* data) { if (proc == NULL) { return; } AuxProcRec* rec = fAuxProcList; while (rec) { if (rec->fProc == proc) { rec->fData = data; return; } rec = rec->fNext; } // not found, create a new rec rec = SkNEW(AuxProcRec); rec->fProc = proc; rec->fData = data; rec->fNext = fAuxProcList; fAuxProcList = rec; } void SkGlyphCache::removeAuxProc(void (*proc)(void*)) { AuxProcRec* rec = fAuxProcList; AuxProcRec* prev = NULL; while (rec) { AuxProcRec* next = rec->fNext; if (rec->fProc == proc) { if (prev) { prev->fNext = next; } else { fAuxProcList = next; } SkDELETE(rec); return; } prev = rec; rec = next; } } void SkGlyphCache::invokeAndRemoveAuxProcs() { AuxProcRec* rec = fAuxProcList; while (rec) { rec->fProc(rec->fData); AuxProcRec* next = rec->fNext; SkDELETE(rec); rec = next; } } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// #include "SkGlobals.h" #include "SkThread.h" #define SkGlyphCache_GlobalsTag SkSetFourByteTag('g', 'l', 'f', 'c') #ifdef USE_CACHE_HASH #define HASH_BITCOUNT 6 #define HASH_COUNT (1 << HASH_BITCOUNT) #define HASH_MASK (HASH_COUNT - 1) static unsigned desc_to_hashindex(const SkDescriptor* desc) { SkASSERT(HASH_MASK < 256); // since our munging reduces to 8 bits uint32_t n = *(const uint32_t*)desc; //desc->getChecksum(); SkASSERT(n == desc->getChecksum()); // don't trust that the low bits of checksum vary enough, so... n ^= (n >> 24) ^ (n >> 16) ^ (n >> 8) ^ (n >> 30); return n & HASH_MASK; } #endif class SkGlyphCache_Globals : public SkGlobals::Rec { public: SkMutex fMutex; SkGlyphCache* fHead; size_t fTotalMemoryUsed; #ifdef USE_CACHE_HASH SkGlyphCache* fHash[HASH_COUNT]; #endif #ifdef SK_DEBUG void validate() const; #else void validate() const {} #endif }; #ifdef SK_USE_RUNTIME_GLOBALS static SkGlobals::Rec* create_globals() { SkGlyphCache_Globals* rec = SkNEW(SkGlyphCache_Globals); rec->fHead = NULL; rec->fTotalMemoryUsed = 0; #ifdef USE_CACHE_HASH memset(rec->fHash, 0, sizeof(rec->fHash)); #endif return rec; } #define FIND_GC_GLOBALS() *(SkGlyphCache_Globals*)SkGlobals::Find(SkGlyphCache_GlobalsTag, create_globals) #define GET_GC_GLOBALS() *(SkGlyphCache_Globals*)SkGlobals::Get(SkGlyphCache_GlobalsTag) #else static SkGlyphCache_Globals gGCGlobals; #define FIND_GC_GLOBALS() gGCGlobals #define GET_GC_GLOBALS() gGCGlobals #endif void SkGlyphCache::VisitAllCaches(bool (*proc)(SkGlyphCache*, void*), void* context) { SkGlyphCache_Globals& globals = FIND_GC_GLOBALS(); SkAutoMutexAcquire ac(globals.fMutex); SkGlyphCache* cache; globals.validate(); for (cache = globals.fHead; cache != NULL; cache = cache->fNext) { if (proc(cache, context)) { break; } } globals.validate(); } /* This guy calls the visitor from within the mutext lock, so the visitor cannot: - take too much time - try to acquire the mutext again - call a fontscaler (which might call into the cache) */ SkGlyphCache* SkGlyphCache::VisitCache(const SkDescriptor* desc, bool (*proc)(const SkGlyphCache*, void*), void* context) { SkASSERT(desc); SkGlyphCache_Globals& globals = FIND_GC_GLOBALS(); SkAutoMutexAcquire ac(globals.fMutex); SkGlyphCache* cache; bool insideMutex = true; globals.validate(); #ifdef USE_CACHE_HASH SkGlyphCache** hash = globals.fHash; unsigned index = desc_to_hashindex(desc); cache = hash[index]; if (cache && *cache->fDesc == *desc) { cache->detach(&globals.fHead); goto FOUND_IT; } #endif for (cache = globals.fHead; cache != NULL; cache = cache->fNext) { if (cache->fDesc->equals(*desc)) { cache->detach(&globals.fHead); goto FOUND_IT; } } /* Release the mutex now, before we create a new entry (which might have side-effects like trying to access the cache/mutex (yikes!) */ ac.release(); // release the mutex now insideMutex = false; // can't use globals anymore cache = SkNEW_ARGS(SkGlyphCache, (desc)); FOUND_IT: if (proc(cache, context)) { // stay detached if (insideMutex) { SkASSERT(globals.fTotalMemoryUsed >= cache->fMemoryUsed); globals.fTotalMemoryUsed -= cache->fMemoryUsed; #ifdef USE_CACHE_HASH hash[index] = NULL; #endif } } else { // reattach if (insideMutex) { cache->attachToHead(&globals.fHead); #ifdef USE_CACHE_HASH hash[index] = cache; #endif } else { AttachCache(cache); } cache = NULL; } return cache; } void SkGlyphCache::AttachCache(SkGlyphCache* cache) { SkASSERT(cache); SkASSERT(cache->fNext == NULL); SkGlyphCache_Globals& globals = GET_GC_GLOBALS(); SkAutoMutexAcquire ac(globals.fMutex); globals.validate(); // if we have a fixed budget for our cache, do a purge here { size_t allocated = globals.fTotalMemoryUsed + cache->fMemoryUsed; size_t amountToFree = SkFontHost::ShouldPurgeFontCache(allocated); if (amountToFree) (void)InternalFreeCache(&globals, amountToFree); } cache->attachToHead(&globals.fHead); globals.fTotalMemoryUsed += cache->fMemoryUsed; #ifdef USE_CACHE_HASH unsigned index = desc_to_hashindex(cache->fDesc); SkASSERT(globals.fHash[index] != cache); globals.fHash[index] = cache; #endif globals.validate(); } size_t SkGlyphCache::GetCacheUsed() { SkGlyphCache_Globals& globals = FIND_GC_GLOBALS(); SkAutoMutexAcquire ac(globals.fMutex); return SkGlyphCache::ComputeMemoryUsed(globals.fHead); } bool SkGlyphCache::SetCacheUsed(size_t bytesUsed) { size_t curr = SkGlyphCache::GetCacheUsed(); if (curr > bytesUsed) { SkGlyphCache_Globals& globals = FIND_GC_GLOBALS(); SkAutoMutexAcquire ac(globals.fMutex); return InternalFreeCache(&globals, curr - bytesUsed) > 0; } return false; } /////////////////////////////////////////////////////////////////////////////// SkGlyphCache* SkGlyphCache::FindTail(SkGlyphCache* cache) { if (cache) { while (cache->fNext) { cache = cache->fNext; } } return cache; } size_t SkGlyphCache::ComputeMemoryUsed(const SkGlyphCache* head) { size_t size = 0; while (head != NULL) { size += head->fMemoryUsed; head = head->fNext; } return size; } #ifdef SK_DEBUG void SkGlyphCache_Globals::validate() const { size_t computed = SkGlyphCache::ComputeMemoryUsed(fHead); if (fTotalMemoryUsed != computed) { printf("total %d, computed %d\n", (int)fTotalMemoryUsed, (int)computed); } SkASSERT(fTotalMemoryUsed == computed); } #endif size_t SkGlyphCache::InternalFreeCache(SkGlyphCache_Globals* globals, size_t bytesNeeded) { globals->validate(); size_t bytesFreed = 0; int count = 0; // don't do any "small" purges size_t minToPurge = globals->fTotalMemoryUsed >> 2; if (bytesNeeded < minToPurge) bytesNeeded = minToPurge; SkGlyphCache* cache = FindTail(globals->fHead); while (cache != NULL && bytesFreed < bytesNeeded) { SkGlyphCache* prev = cache->fPrev; bytesFreed += cache->fMemoryUsed; #ifdef USE_CACHE_HASH unsigned index = desc_to_hashindex(cache->fDesc); if (cache == globals->fHash[index]) { globals->fHash[index] = NULL; } #endif cache->detach(&globals->fHead); SkDELETE(cache); cache = prev; count += 1; } SkASSERT(bytesFreed <= globals->fTotalMemoryUsed); globals->fTotalMemoryUsed -= bytesFreed; globals->validate(); #ifdef SPEW_PURGE_STATUS if (count) { SkDebugf("purging %dK from font cache [%d entries]\n", (int)(bytesFreed >> 10), count); } #endif return bytesFreed; }