Change the GlyphCache to use a hash table instead of doing its own ad-hoc
hashing. This change appears to be performance neutral.
BUG=skia:

Review URL: https://codereview.chromium.org/1216983003
diff --git a/src/core/SkGlyph.h b/src/core/SkGlyph.h
index abca215..4699859 100644
--- a/src/core/SkGlyph.h
+++ b/src/core/SkGlyph.h
@@ -8,6 +8,7 @@
 #ifndef SkGlyph_DEFINED
 #define SkGlyph_DEFINED
 
+#include "SkChecksum.h"
 #include "SkTypes.h"
 #include "SkFixed.h"
 #include "SkMask.h"
@@ -114,6 +115,16 @@
 
     void toMask(SkMask* mask) const;
 
+    class HashTraits {
+    public:
+        static uint32_t GetKey(const SkGlyph& glyph) {
+            return glyph.fID;
+        }
+        static uint32_t Hash(uint32_t glyphId) {
+            return SkChecksum::CheapMix(glyphId);
+        }
+    };
+
  private:
     // TODO(herb) remove friend statement after SkGlyphCache cleanup.
     friend class SkGlyphCache;
diff --git a/src/core/SkGlyphCache.cpp b/src/core/SkGlyphCache.cpp
index ae268d5..9b0199f 100755
--- a/src/core/SkGlyphCache.cpp
+++ b/src/core/SkGlyphCache.cpp
@@ -9,10 +9,7 @@
 #include "SkGlyphCache_Globals.h"
 #include "SkGraphics.h"
 #include "SkLazyPtr.h"
-#include "SkMutex.h"
-#include "SkPaint.h"
 #include "SkPath.h"
-#include "SkTLS.h"
 #include "SkTemplates.h"
 #include "SkTypeface.h"
 
@@ -35,118 +32,52 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-#ifdef SK_GLYPHCACHE_TRACK_HASH_STATS
-    #define RecordHashSuccess()             fHashHitCount += 1
-    #define RecordHashCollisionIf(pred)     do { if (pred) fHashMissCount += 1; } while (0)
-#else
-    #define RecordHashSuccess()             (void)0
-    #define RecordHashCollisionIf(pred)     (void)0
-#endif
-#define RecordHashCollision() RecordHashCollisionIf(true)
-
-///////////////////////////////////////////////////////////////////////////////
-
 // so we don't grow our arrays a lot
 #define kMinGlyphCount      16
 #define kMinGlyphImageSize  (16*2)
 #define kMinAllocAmount     ((sizeof(SkGlyph) + kMinGlyphImageSize) * kMinGlyphCount)
 
 SkGlyphCache::SkGlyphCache(SkTypeface* typeface, const SkDescriptor* desc, SkScalerContext* ctx)
-        : fScalerContext(ctx), fGlyphAlloc(kMinAllocAmount) {
+    : fDesc(desc->copy())
+    , fScalerContext(ctx)
+    , fGlyphAlloc(kMinAllocAmount) {
     SkASSERT(typeface);
     SkASSERT(desc);
     SkASSERT(ctx);
 
     fPrev = fNext = NULL;
 
-    fDesc = desc->copy();
     fScalerContext->getFontMetrics(&fFontMetrics);
 
-    // Create the sentinel SkGlyph.
-    SkGlyph* sentinel = fGlyphArray.insert(0);
-    sentinel->initGlyphFromCombinedID(SkGlyph::kImpossibleID);
-
-    // Initialize all index to zero which points to the sentinel SkGlyph.
-    memset(fGlyphHash, 0x00, sizeof(fGlyphHash));
-
     fMemoryUsed = sizeof(*this);
 
-    fGlyphArray.setReserve(kMinGlyphCount);
-
     fAuxProcList = NULL;
-
-#ifdef SK_GLYPHCACHE_TRACK_HASH_STATS
-    fHashHitCount = fHashMissCount = 0;
-#endif
 }
 
 SkGlyphCache::~SkGlyphCache() {
-#if 0
-    {
-        size_t ptrMem = fGlyphArray.count() * sizeof(SkGlyph*);
-        size_t glyphAlloc = fGlyphAlloc.totalCapacity();
-        size_t glyphHashUsed = 0;
-        size_t uniHashUsed = 0;
-        for (int i = 0; i < kHashCount; ++i) {
-            glyphHashUsed += fGlyphHash[i] ? sizeof(fGlyphHash[0]) : 0;
-            uniHashUsed += fCharToGlyphHash[i].fID != 0xFFFFFFFF ? sizeof(fCharToGlyphHash[0]) : 0;
+    fGlyphMap.foreach(
+        [](SkGlyph* g) {
+            SkDELETE(g->fPath);
         }
-        size_t glyphUsed = fGlyphArray.count() * sizeof(SkGlyph);
-        size_t imageUsed = 0;
-        for (int i = 0; i < fGlyphArray.count(); ++i) {
-            const SkGlyph& g = *fGlyphArray[i];
-            if (g.fImage) {
-                imageUsed += g.fHeight * g.rowBytes();
-            }
-        }
-
-        SkDebugf("glyphPtrArray,%zu, Alloc,%zu, imageUsed,%zu, glyphUsed,%zu, glyphHashAlloc,%zu, glyphHashUsed,%zu, unicharHashAlloc,%zu, unicharHashUsed,%zu\n",
-                 ptrMem, glyphAlloc, imageUsed, glyphUsed, sizeof(fGlyphHash), glyphHashUsed, sizeof(CharGlyphRec) * kHashCount, uniHashUsed);
-
-    }
-#endif
-    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();
 }
 
-SkGlyphCache::CharGlyphRec* SkGlyphCache::getCharGlyphRec(uint32_t id) {
-    if (NULL == fCharToGlyphHash.get()) {
+SkGlyphCache::CharGlyphRec* SkGlyphCache::getCharGlyphRec(PackedUnicharID packedUnicharID) {
+    if (NULL == fPackedUnicharIDToPackedGlyphID.get()) {
         // Allocate the array.
-        fCharToGlyphHash.reset(kHashCount);
-        // Initialize entries of fCharToGlyphHash to index the sentinel glyph and
-        // an fID value that will not match any id.
+        fPackedUnicharIDToPackedGlyphID.reset(kHashCount);
+        // Initialize array to map character and position with the impossible glyph ID. This
+        // represents no mapping.
         for (int i = 0; i <kHashCount; ++i) {
-            fCharToGlyphHash[i].fID = SkGlyph::kImpossibleID;
-            fCharToGlyphHash[i].fGlyphIndex = 0;
+            fPackedUnicharIDToPackedGlyphID[i].fPackedUnicharID = SkGlyph::kImpossibleID;
+            fPackedUnicharIDToPackedGlyphID[i].fPackedGlyphID = 0;
         }
     }
 
-    return &fCharToGlyphHash[ID2HashIndex(id)];
-}
-
-void SkGlyphCache::adjustCaches(int insertion_index) {
-    for (int i = 0; i < kHashCount; ++i) {
-        if (fGlyphHash[i] >= SkToU16(insertion_index)) {
-            fGlyphHash[i] += 1;
-        }
-    }
-    if (fCharToGlyphHash.get() != NULL) {
-        for (int i = 0; i < kHashCount; ++i) {
-            if (fCharToGlyphHash[i].fGlyphIndex >= SkToU16(insertion_index)) {
-                fCharToGlyphHash[i].fGlyphIndex += 1;
-            }
-        }
-    }
+    return &fPackedUnicharIDToPackedGlyphID[SkChecksum::CheapMix(packedUnicharID) & kHashMask];
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -159,11 +90,11 @@
 
 uint16_t SkGlyphCache::unicharToGlyph(SkUnichar charCode) {
     VALIDATE();
-    uint32_t id = SkGlyph::MakeID(charCode);
-    const CharGlyphRec& rec = *this->getCharGlyphRec(id);
+    PackedUnicharID packedUnicharID = SkGlyph::MakeID(charCode);
+    const CharGlyphRec& rec = *this->getCharGlyphRec(packedUnicharID);
 
-    if (rec.fID == id) {
-        return fGlyphArray[rec.fGlyphIndex].getGlyphID();
+    if (rec.fPackedUnicharID == packedUnicharID) {
+        return SkGlyph::ID2Code(rec.fPackedGlyphID);
     } else {
         return fScalerContext->charToGlyphID(charCode);
     }
@@ -186,8 +117,8 @@
 
 const SkGlyph& SkGlyphCache::getGlyphIDAdvance(uint16_t glyphID) {
     VALIDATE();
-    uint32_t id = SkGlyph::MakeID(glyphID);
-    return *this->lookupByCombinedID(id, kJustAdvance_MetricsType);
+    PackedGlyphID packedGlyphID = SkGlyph::MakeID(glyphID);
+    return *this->lookupByPackedGlyphID(packedGlyphID, kJustAdvance_MetricsType);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -197,58 +128,44 @@
     return *this->lookupByChar(charCode, kFull_MetricsType);
 }
 
-const SkGlyph& SkGlyphCache::getUnicharMetrics(SkUnichar charCode,
-                                               SkFixed x, SkFixed y) {
+const SkGlyph& SkGlyphCache::getUnicharMetrics(SkUnichar charCode, SkFixed x, SkFixed y) {
     VALIDATE();
     return *this->lookupByChar(charCode, kFull_MetricsType, x, y);
 }
 
 const SkGlyph& SkGlyphCache::getGlyphIDMetrics(uint16_t glyphID) {
     VALIDATE();
-    uint32_t id = SkGlyph::MakeID(glyphID);
-    return *this->lookupByCombinedID(id, kFull_MetricsType);
+    PackedGlyphID packedGlyphID = SkGlyph::MakeID(glyphID);
+    return *this->lookupByPackedGlyphID(packedGlyphID, kFull_MetricsType);
 }
 
 const SkGlyph& SkGlyphCache::getGlyphIDMetrics(uint16_t glyphID, SkFixed x, SkFixed y) {
     VALIDATE();
-    uint32_t id = SkGlyph::MakeID(glyphID, x, y);
-    return *this->lookupByCombinedID(id, kFull_MetricsType);
+    PackedGlyphID packedGlyphID = SkGlyph::MakeID(glyphID, x, y);
+    return *this->lookupByPackedGlyphID(packedGlyphID, kFull_MetricsType);
 }
 
 SkGlyph* SkGlyphCache::lookupByChar(SkUnichar charCode, MetricsType type, SkFixed x, SkFixed y) {
-    uint32_t id = SkGlyph::MakeID(charCode, x, y);
+    PackedUnicharID id = SkGlyph::MakeID(charCode, x, y);
     CharGlyphRec* rec = this->getCharGlyphRec(id);
-    SkGlyph* glyph;
-    if (rec->fID != id) {
-        RecordHashCollisionIf(glyph_index != SkGlyph::kImpossibleID);
+    if (rec->fPackedUnicharID != id) {
         // this ID is based on the UniChar
-        rec->fID = id;
+        rec->fPackedUnicharID = id;
         // this ID is based on the glyph index
-        id = SkGlyph::MakeID(fScalerContext->charToGlyphID(charCode), x, y);
-        rec->fGlyphIndex = this->lookupMetrics(id, type);
-        glyph = &fGlyphArray[rec->fGlyphIndex];
+        PackedGlyphID combinedID = SkGlyph::MakeID(fScalerContext->charToGlyphID(charCode), x, y);
+        rec->fPackedGlyphID = combinedID;
+        return this->lookupByPackedGlyphID(combinedID, type);
     } else {
-        RecordHashSuccess();
-        glyph = &fGlyphArray[rec->fGlyphIndex];
-        if (type == kFull_MetricsType && glyph->isJustAdvance()) {
-            fScalerContext->getMetrics(glyph);
-        }
+        return this->lookupByPackedGlyphID(rec->fPackedGlyphID, type);
     }
-    return glyph;
 }
 
-SkGlyph* SkGlyphCache::lookupByCombinedID(uint32_t id, MetricsType type) {
-    uint32_t hash_index = ID2HashIndex(id);
-    uint16_t glyph_index = fGlyphHash[hash_index];
-    SkGlyph* glyph = &fGlyphArray[glyph_index];
+SkGlyph* SkGlyphCache::lookupByPackedGlyphID(PackedGlyphID packedGlyphID, MetricsType type) {
+    SkGlyph* glyph = fGlyphMap.find(packedGlyphID);
 
-    if (glyph->fID != id) {
-        RecordHashCollisionIf(glyph_index != SkGlyph::kImpossibleID);
-        glyph_index = this->lookupMetrics(id, type);
-        fGlyphHash[hash_index] = glyph_index;
-        glyph = &fGlyphArray[glyph_index];
+    if (NULL == glyph) {
+        glyph = this->allocateNewGlyph(packedGlyphID, type);
     } else {
-        RecordHashSuccess();
         if (type == kFull_MetricsType && glyph->isJustAdvance()) {
            fScalerContext->getMetrics(glyph);
         }
@@ -256,55 +173,25 @@
     return glyph;
 }
 
-uint16_t SkGlyphCache::lookupMetrics(uint32_t id, MetricsType mtype) {
-    SkASSERT(id != SkGlyph::kImpossibleID);
-    // Count is always greater than 0 because of the sentinel.
-    // The fGlyphArray cache is in descending order, so that the sentinel with a value of ~0 is
-    // always at index 0.
-    SkGlyph* gptr = fGlyphArray.begin();
-    int lo = 0;
-    int hi = fGlyphArray.count() - 1;
-    while (lo < hi) {
-        int mid = (hi + lo) >> 1;
-        if (gptr[mid].fID > id) {
-            lo = mid + 1;
-        } else {
-            hi = mid;
-        }
-    }
-
-    uint16_t glyph_index = hi;
-    SkGlyph* glyph = &gptr[glyph_index];
-    if (glyph->fID == id) {
-        if (kFull_MetricsType == mtype && glyph->isJustAdvance()) {
-            fScalerContext->getMetrics(glyph);
-        }
-        SkASSERT(glyph->fID != SkGlyph::kImpossibleID);
-        return glyph_index;
-    }
-
-    // check if we need to bump hi before falling though to the allocator
-    if (glyph->fID > id) {
-        glyph_index += 1;
-    }
-
-    // Not found, but hi contains the index of the insertion point of the new glyph.
+SkGlyph* SkGlyphCache::allocateNewGlyph(PackedGlyphID packedGlyphID, MetricsType mtype) {
     fMemoryUsed += sizeof(SkGlyph);
 
-    this->adjustCaches(glyph_index);
-
-    glyph = fGlyphArray.insert(glyph_index);
-    glyph->initGlyphFromCombinedID(id);
-
-    if (kJustAdvance_MetricsType == mtype) {
-        fScalerContext->getAdvance(glyph);
-    } else {
-        SkASSERT(kFull_MetricsType == mtype);
-        fScalerContext->getMetrics(glyph);
+    SkGlyph* glyphPtr;
+    {
+        SkGlyph glyph;
+        glyph.initGlyphFromCombinedID(packedGlyphID);
+        glyphPtr = fGlyphMap.set(glyph);
     }
 
-    SkASSERT(glyph->fID != SkGlyph::kImpossibleID);
-    return glyph_index;
+    if (kJustAdvance_MetricsType == mtype) {
+        fScalerContext->getAdvance(glyphPtr);
+    } else {
+        SkASSERT(kFull_MetricsType == mtype);
+        fScalerContext->getMetrics(glyphPtr);
+    }
+
+    SkASSERT(glyphPtr->fID != SkGlyph::kImpossibleID);
+    return glyphPtr;
 }
 
 const void* SkGlyphCache::findImage(const SkGlyph& glyph) {
@@ -354,11 +241,7 @@
                matrix[SkMatrix::kMScaleX], matrix[SkMatrix::kMSkewX],
                matrix[SkMatrix::kMSkewY], matrix[SkMatrix::kMScaleY],
                rec.fLumBits & 0xFF, rec.fDeviceGamma, rec.fPaintGamma, rec.fContrast,
-               fGlyphArray.count());
-#ifdef SK_GLYPHCACHE_TRACK_HASH_STATS
-    const int sum = SkTMax(fHashHitCount + fHashMissCount, 1);   // avoid divide-by-zero
-    msg.appendf(" hash:%2d\n", 100 * fHashHitCount / sum);
-#endif
+               fGlyphMap.count());
     SkDebugf("%s\n", msg.c_str());
 }
 
@@ -529,21 +412,9 @@
     SkDebugf("SkGlyphCache strikes:%d memory:%d\n",
              globals.getCacheCountUsed(), (int)globals.getTotalMemoryUsed());
 
-#ifdef SK_GLYPHCACHE_TRACK_HASH_STATS
-    int hitCount = 0;
-    int missCount = 0;
-#endif
-
     for (cache = globals.internalGetHead(); cache != NULL; cache = cache->fNext) {
-#ifdef SK_GLYPHCACHE_TRACK_HASH_STATS
-        hitCount += cache->fHashHitCount;
-        missCount += cache->fHashMissCount;
-#endif
         cache->dump();
     }
-#ifdef SK_GLYPHCACHE_TRACK_HASH_STATS
-    SkDebugf("Hash hit percent:%2d\n", 100 * hitCount / (hitCount + missCount));
-#endif
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/SkGlyphCache.h b/src/core/SkGlyphCache.h
index f3a946c..1c985bf 100644
--- a/src/core/SkGlyphCache.h
+++ b/src/core/SkGlyphCache.h
@@ -8,10 +8,10 @@
 #define SkGlyphCache_DEFINED
 
 #include "SkBitmap.h"
-#include "SkChecksum.h"
 #include "SkChunkAlloc.h"
 #include "SkDescriptor.h"
 #include "SkGlyph.h"
+#include "SkTHash.h"
 #include "SkScalerContext.h"
 #include "SkTemplates.h"
 #include "SkTDArray.h"
@@ -20,10 +20,6 @@
 
 class SkGlyphCache_Globals;
 
-// Enable this locally to add stats for hash-table hit rates. It also extends the dump() output
-// to show those stats.
-//#define SK_GLYPHCACHE_TRACK_HASH_STATS
-
 /** \class SkGlyphCache
 
     This class represents a strike: a specific combination of typeface, size, matrix, etc., and
@@ -181,9 +177,12 @@
         kHashMask           = kHashCount - 1
     };
 
+    typedef uint32_t PackedGlyphID;    // glyph-index + subpixel-pos
+    typedef uint32_t PackedUnicharID;  // unichar + subpixel-pos
+
     struct CharGlyphRec {
-        uint32_t    fID;    // unichar + subpixel
-        uint16_t    fGlyphIndex;
+        PackedUnicharID    fPackedUnicharID;
+        PackedGlyphID      fPackedGlyphID;
     };
 
     struct AuxProcRec {
@@ -199,56 +198,42 @@
     // Return the SkGlyph* associated with MakeID. The id parameter is the
     // combined glyph/x/y id generated by MakeID. If it is just a glyph id
     // then x and y are assumed to be zero.
-    SkGlyph* lookupByCombinedID(uint32_t id, MetricsType type);
+    SkGlyph* lookupByPackedGlyphID(PackedGlyphID packedGlyphID, MetricsType type);
 
     // Return a SkGlyph* associated with unicode id and position x and y.
     SkGlyph* lookupByChar(SkUnichar id, MetricsType type, SkFixed x = 0, SkFixed y = 0);
 
-    // Return the index of id in the fGlyphArray. If it does not exist,
-    // create a new one using MetricsType.
-    uint16_t lookupMetrics(uint32_t id, MetricsType type);
+    // Return a new SkGlyph for the glyph ID and subpixel position id. Limit the amount
+    // of work
+    // using type.
+    SkGlyph* allocateNewGlyph(PackedGlyphID packedGlyphID, MetricsType type);
+
     static bool DetachProc(const SkGlyphCache*, void*) { return true; }
 
     // The id arg is a combined id generated by MakeID.
-    CharGlyphRec* getCharGlyphRec(uint32_t id);
-    void adjustCaches(int insertion_index);
-
-    static inline unsigned ID2HashIndex(uint32_t h) {
-        return SkChecksum::CheapMix(h) & kHashMask;
-    }
+    CharGlyphRec* getCharGlyphRec(PackedUnicharID id);
 
     void invokeAndRemoveAuxProcs();
 
     inline static SkGlyphCache* FindTail(SkGlyphCache* head);
 
-    SkGlyphCache*        fNext, *fPrev;
-    SkDescriptor*        fDesc;
-    SkScalerContext*     fScalerContext;
-    SkPaint::FontMetrics fFontMetrics;
+    SkGlyphCache*          fNext;
+    SkGlyphCache*          fPrev;
+    SkDescriptor* const    fDesc;
+    SkScalerContext* const fScalerContext;
+    SkPaint::FontMetrics   fFontMetrics;
 
-    // A quick lookup to avoid the binary search looking for glyphs in fGlyphArray.
-    uint16_t             fGlyphHash[kHashCount];
+    // Map from a combined GlyphID and sub-pixel position to a SkGlyph.
+    SkTHashTable<SkGlyph, PackedGlyphID, SkGlyph::HashTraits> fGlyphMap;
 
-    // Contains the SkGlyphs that are used by fGlyphHash and fCharToGlyphHash. The ~0 element is
-    // reserved for a sentinel SkGlyph that reduces the logic to check for collisions in the hash
-    // arrays. The ~0 element has an fID of SkGlyph::kImpossibleID which never matches any
-    // combined id generated for a char or a glyph.
-    SkTDArray<SkGlyph>   fGlyphArray;
-    SkChunkAlloc         fGlyphAlloc;
+    SkChunkAlloc           fGlyphAlloc;
 
-    // no reason to use the same kHashCount as fGlyphHash, but we do for now
-    // Dynamically allocated when chars are encountered.
-    SkAutoTArray<CharGlyphRec> fCharToGlyphHash;
+    SkAutoTArray<CharGlyphRec> fPackedUnicharIDToPackedGlyphID;
 
     // used to track (approx) how much ram is tied-up in this cache
-    size_t  fMemoryUsed;
+    size_t                 fMemoryUsed;
 
-#ifdef SK_GLYPHCACHE_TRACK_HASH_STATS
-    int fHashHitCount;
-    int fHashMissCount;
-#endif
-
-    AuxProcRec* fAuxProcList;
+    AuxProcRec*            fAuxProcList;
 };
 
 class SkAutoGlyphCacheBase {