Adding a cache + memory pool for GPU TextBlobs
authorjoshualitt <joshualitt@chromium.org>
Wed, 8 Apr 2015 16:08:31 +0000 (09:08 -0700)
committerCommit bot <commit-bot@chromium.org>
Wed, 8 Apr 2015 16:08:31 +0000 (09:08 -0700)
BUG=skia:

Review URL: https://codereview.chromium.org/1055843002

gyp/gpu.gypi
include/core/SkTextBlob.h
include/gpu/GrContext.h
src/gpu/GrAtlasTextContext.cpp
src/gpu/GrAtlasTextContext.h
src/gpu/GrContext.cpp
src/gpu/GrDrawTarget.cpp
src/gpu/GrMemoryPool.cpp
src/gpu/GrMemoryPool.h
src/gpu/GrTextBlobCache.cpp [new file with mode: 0644]
src/gpu/GrTextBlobCache.h [new file with mode: 0644]

index cb57aae..a69eba1 100644 (file)
       '<(skia_src_path)/gpu/GrSurfacePriv.h',
       '<(skia_src_path)/gpu/GrSurface.cpp',
       '<(skia_src_path)/gpu/GrTemplates.h',
+      '<(skia_src_path)/gpu/GrTextBlobCache.cpp',
+      '<(skia_src_path)/gpu/GrTextBlobCache.h',
       '<(skia_src_path)/gpu/GrTextContext.cpp',
       '<(skia_src_path)/gpu/GrTextContext.h',
       '<(skia_src_path)/gpu/GrFontCache.cpp',
index 950ad8d..a091f05 100644 (file)
@@ -92,6 +92,7 @@ private:
     static unsigned ScalarsPerGlyph(GlyphPositioning pos);
 
     friend class GrAtlasTextContext;
+    friend class GrTextBlobCache;
     friend class GrTextContext;
     friend class SkBaseDevice;
     friend class SkTextBlobBuilder;
index f631f56..f6c3590 100644 (file)
@@ -36,6 +36,7 @@ class GrPipelineBuilder;
 class GrResourceEntry;
 class GrResourceCache;
 class GrTestTarget;
+class GrTextBlobCache;
 class GrTextContext;
 class GrTextureParams;
 class GrVertexBuffer;
@@ -658,6 +659,7 @@ public:
     GrBatchFontCache* getBatchFontCache() { return fBatchFontCache; }
     GrFontCache* getFontCache() { return fFontCache; }
     GrLayerCache* getLayerCache() { return fLayerCache.get(); }
+    GrTextBlobCache* getTextBlobCache() { return fTextBlobCache; }
     GrDrawTarget* getTextTarget();
     const GrIndexBuffer* getQuadIndexBuffer() const;
     GrAARectRenderer* getAARectRenderer() { return fAARectRenderer; }
@@ -700,6 +702,7 @@ private:
     GrBatchFontCache*               fBatchFontCache;
     GrFontCache*                    fFontCache;
     SkAutoTDelete<GrLayerCache>     fLayerCache;
+    SkAutoTDelete<GrTextBlobCache>  fTextBlobCache;
 
     GrPathRendererChain*            fPathRendererChain;
     GrSoftwarePathRenderer*         fSoftwarePathRenderer;
index 38edc8e..d6bb3e0 100644 (file)
@@ -15,6 +15,7 @@
 #include "GrFontScaler.h"
 #include "GrIndexBuffer.h"
 #include "GrStrokeInfo.h"
+#include "GrTextBlobCache.h"
 #include "GrTexturePriv.h"
 
 #include "SkAutoKern.h"
@@ -67,16 +68,13 @@ static size_t get_vertex_stride(GrMaskFormat maskFormat) {
 GrAtlasTextContext::GrAtlasTextContext(GrContext* context,
                                        SkGpuDevice* gpuDevice,
                                        const SkDeviceProperties& properties)
-                                       : INHERITED(context, gpuDevice, properties) {
+    : INHERITED(context, gpuDevice, properties) {
+    // We overallocate vertices in our textblobs based on the assumption that A8 has the greatest
+    // vertexStride
+    SK_COMPILE_ASSERT(kGrayTextVASize >= kColorTextVASize && kGrayTextVASize >= kLCDTextVASize,
+                      vertex_attribute_changed);
     fCurrStrike = NULL;
-}
-
-void GrAtlasTextContext::ClearCacheEntry(uint32_t key, BitmapTextBlob** blob) {
-    (*blob)->unref();
-}
-
-GrAtlasTextContext::~GrAtlasTextContext() {
-    fCache.foreach(&GrAtlasTextContext::ClearCacheEntry);
+    fCache = context->getTextBlobCache();
 }
 
 GrAtlasTextContext* GrAtlasTextContext::Create(GrContext* context,
@@ -110,72 +108,29 @@ inline SkGlyphCache* GrAtlasTextContext::setupCache(BitmapTextBlob::Run* run,
     return SkGlyphCache::DetachCache(run->fTypeface, run->fDescriptor.getDesc());
 }
 
-inline void GrAtlasTextContext::BlobGlyphCount(int* glyphCount, int* runCount,
-                                               const SkTextBlob* blob) {
-    SkTextBlob::RunIterator itCounter(blob);
-    for (; !itCounter.done(); itCounter.next(), (*runCount)++) {
-        *glyphCount += itCounter.glyphCount();
-    }
-}
-
-GrAtlasTextContext::BitmapTextBlob* GrAtlasTextContext::CreateBlob(int glyphCount,
-                                                                   int runCount) {
-    // We allocate size for the BitmapTextBlob itself, plus size for the vertices array,
-    // and size for the glyphIds array.
-    SK_COMPILE_ASSERT(kGrayTextVASize >= kColorTextVASize && kGrayTextVASize >= kLCDTextVASize,
-                      vertex_attribute_changed);
-    size_t verticesCount = glyphCount * kVerticesPerGlyph * kGrayTextVASize;
-    size_t length = sizeof(BitmapTextBlob) +
-                    verticesCount +
-                    glyphCount * sizeof(GrGlyph::PackedID) +
-                    sizeof(BitmapTextBlob::Run) * runCount;
-
-    BitmapTextBlob* cacheBlob = SkNEW_PLACEMENT(sk_malloc_throw(length), BitmapTextBlob);
-
-    // setup offsets for vertices / glyphs
-    cacheBlob->fVertices = sizeof(BitmapTextBlob) + reinterpret_cast<unsigned char*>(cacheBlob);
-    cacheBlob->fGlyphIDs =
-            reinterpret_cast<GrGlyph::PackedID*>(cacheBlob->fVertices + verticesCount);
-    cacheBlob->fRuns = reinterpret_cast<BitmapTextBlob::Run*>(cacheBlob->fGlyphIDs + glyphCount);
-
-    // Initialize runs
-    for (int i = 0; i < runCount; i++) {
-        SkNEW_PLACEMENT(&cacheBlob->fRuns[i], BitmapTextBlob::Run);
-    }
-    cacheBlob->fRunCount = runCount;
-    return cacheBlob;
-}
-
 void GrAtlasTextContext::drawTextBlob(GrRenderTarget* rt, const GrClip& clip,
                                       const SkPaint& skPaint, const SkMatrix& viewMatrix,
                                       const SkTextBlob* blob, SkScalar x, SkScalar y,
                                       SkDrawFilter* drawFilter, const SkIRect& clipBounds) {
-    BitmapTextBlob* cacheBlob;
-    BitmapTextBlob** foundBlob = fCache.find(blob->uniqueID());
+    uint32_t uniqueID = blob->uniqueID();
+    BitmapTextBlob* cacheBlob = fCache->find(uniqueID);
     SkIRect clipRect;
     clip.getConservativeBounds(rt->width(), rt->height(), &clipRect);
 
-    if (foundBlob) {
-        cacheBlob = *foundBlob;
+    if (cacheBlob) {
         if (MustRegenerateBlob(*cacheBlob, skPaint, viewMatrix, x, y)) {
             // We have to remake the blob because changes may invalidate our masks.
             // TODO we could probably get away reuse most of the time if the pointer is unique,
             // but we'd have to clear the subrun information
-            cacheBlob->unref();
-            int glyphCount = 0;
-            int runCount = 0;
-            BlobGlyphCount(&glyphCount, &runCount, blob);
-            cacheBlob = CreateBlob(glyphCount, runCount);
-            fCache.set(blob->uniqueID(), cacheBlob);
+            fCache->remove(cacheBlob);
+            cacheBlob = fCache->createCachedBlob(blob, kGrayTextVASize);
             this->regenerateTextBlob(cacheBlob, skPaint, viewMatrix, blob, x, y, drawFilter,
                                      clipRect);
+        } else {
+            fCache->makeMRU(cacheBlob);
         }
     } else {
-        int glyphCount = 0;
-        int runCount = 0;
-        BlobGlyphCount(&glyphCount, &runCount, blob);
-        cacheBlob = CreateBlob(glyphCount, runCount);
-        fCache.set(blob->uniqueID(), cacheBlob);
+        cacheBlob = fCache->createCachedBlob(blob, kGrayTextVASize);
         this->regenerateTextBlob(cacheBlob, skPaint, viewMatrix, blob, x, y, drawFilter, clipRect);
     }
 
@@ -269,7 +224,7 @@ void GrAtlasTextContext::onDrawText(GrRenderTarget* rt, const GrClip& clip,
                                     const char text[], size_t byteLength,
                                     SkScalar x, SkScalar y, const SkIRect& regionClipBounds) {
     int glyphCount = skPaint.countText(text, byteLength);
-    SkAutoTUnref<BitmapTextBlob> blob(CreateBlob(glyphCount, 1));
+    SkAutoTUnref<BitmapTextBlob> blob(fCache->createBlob(glyphCount, 1, kGrayTextVASize));
     blob->fViewMatrix = viewMatrix;
     blob->fX = x;
     blob->fY = y;
@@ -383,7 +338,7 @@ void GrAtlasTextContext::onDrawPosText(GrRenderTarget* rt, const GrClip& clip,
                                        const SkScalar pos[], int scalarsPerPosition,
                                        const SkPoint& offset, const SkIRect& regionClipBounds) {
     int glyphCount = skPaint.countText(text, byteLength);
-    SkAutoTUnref<BitmapTextBlob> blob(CreateBlob(glyphCount, 1));
+    SkAutoTUnref<BitmapTextBlob> blob(fCache->createBlob(glyphCount, 1, kGrayTextVASize));
     blob->fStyle = skPaint.getStyle();
     blob->fViewMatrix = viewMatrix;
 
index a75eb0e..8fa6bb5 100644 (file)
 #include "GrBatchAtlas.h"
 #include "GrGeometryProcessor.h"
 #include "SkDescriptor.h"
+#include "GrMemoryPool.h"
 #include "SkTextBlob.h"
-#include "SkTHash.h"
+#include "SkTInternalLList.h"
 
 class GrBatchTextStrike;
 class GrPipelineBuilder;
+class GrTextBlobCache;
 
 /*
  * This class implements GrTextContext using standard bitmap fonts, and can also process textblobs.
@@ -27,8 +29,6 @@ class GrAtlasTextContext : public GrTextContext {
 public:
     static GrAtlasTextContext* Create(GrContext*, SkGpuDevice*, const SkDeviceProperties&);
 
-    virtual ~GrAtlasTextContext();
-
 private:
     GrAtlasTextContext(GrContext*, SkGpuDevice*, const SkDeviceProperties&);
 
@@ -59,6 +59,8 @@ private:
      * TODO this is currently a bug
      */
     struct BitmapTextBlob : public SkRefCnt {
+        SK_DECLARE_INTERNAL_LLIST_INTERFACE(BitmapTextBlob);
+
         /*
          * Each Run inside of the blob can have its texture coordinates regenerated if required.
          * To determine if regeneration is necessary, fAtlasGeneration is used.  If there have been
@@ -128,17 +130,26 @@ private:
         SkScalar fY;
         SkPaint::Style fStyle;
         int fRunCount;
+        uint32_t fUniqueID;
+        GrMemoryPool* fPool;
 
         // all glyph / vertex offsets are into these pools.
         unsigned char* fVertices;
         GrGlyph::PackedID* fGlyphIDs;
         Run* fRuns;
 
+        static const uint32_t& GetKey(const BitmapTextBlob& blob) {
+            return blob.fUniqueID;
+        }
+
         static uint32_t Hash(const uint32_t& key) {
             return SkChecksum::Mix(key);
         }
 
-        void operator delete(void* p) { sk_free(p); }
+        void operator delete(void* p) {
+            BitmapTextBlob* blob = reinterpret_cast<BitmapTextBlob*>(p);
+            blob->fPool->release(p);
+        }
         void* operator new(size_t) {
             SkFAIL("All blobs are created by placement new.");
             return sk_malloc_throw(0);
@@ -153,8 +164,6 @@ private:
     typedef BitmapTextBlob::Run Run;
     typedef Run::SubRunInfo PerSubRunInfo;
 
-    BitmapTextBlob* CreateBlob(int glyphCount, int runCount);
-
     void appendGlyph(BitmapTextBlob*, int runIndex, GrGlyph::PackedID, int left, int top,
                      GrColor color, GrFontScaler*, const SkIRect& clipRect);
 
@@ -190,17 +199,10 @@ private:
                             const SkTextBlob* blob, SkScalar x, SkScalar y,
                             SkDrawFilter* drawFilter, const SkIRect& clipRect);
 
-    // TODO this currently only uses the public interface of SkTextBlob, however, I may need to add
-    // functionality to it while looping over the runs so I'm putting this here for the time being.
-    // If this lands in Chrome without changes, move it to SkTextBlob.
-    static inline void BlobGlyphCount(int* glyphCount, int* runCount, const SkTextBlob*);
-
     GrBatchTextStrike* fCurrStrike;
+    GrTextBlobCache* fCache;
 
-    // TODO use real cache
-    static void ClearCacheEntry(uint32_t key, BitmapTextBlob**);
-    SkTHashMap<uint32_t, BitmapTextBlob*, BitmapTextBlob::Hash> fCache;
-
+    friend class GrTextBlobCache;
     friend class BitmapTextBatch;
 
     typedef GrTextContext INHERITED;
index 40e714b..89429f1 100755 (executable)
@@ -33,6 +33,7 @@
 #include "GrStencilAndCoverTextContext.h"
 #include "GrStrokeInfo.h"
 #include "GrSurfacePriv.h"
+#include "GrTextBlobCache.h"
 #include "GrTexturePriv.h"
 #include "GrTraceMarker.h"
 #include "GrTracing.h"
@@ -136,6 +137,8 @@ void GrContext::initCommon() {
     // GrBatchFontCache will eventually replace GrFontCache
     fBatchFontCache = SkNEW(GrBatchFontCache);
     fBatchFontCache->init(this);
+
+    fTextBlobCache.reset(SkNEW(GrTextBlobCache));
 }
 
 GrContext::~GrContext() {
index 420f9ea..7eef922 100644 (file)
@@ -13,6 +13,7 @@
 #include "GrDrawTargetCaps.h"
 #include "GrPath.h"
 #include "GrPipeline.h"
+#include "GrMemoryPool.h"
 #include "GrRenderTarget.h"
 #include "GrRenderTargetPriv.h"
 #include "GrSurfacePriv.h"
index 5009f20..e59ed83 100644 (file)
 
 GrMemoryPool::GrMemoryPool(size_t preallocSize, size_t minAllocSize) {
     SkDEBUGCODE(fAllocationCnt = 0);
+    SkDEBUGCODE(fAllocBlockCnt = 0);
 
     minAllocSize = SkTMax<size_t>(minAllocSize, 1 << 10);
     fMinAllocSize = GrSizeAlignUp(minAllocSize + kPerAllocPad, kAlignment),
     fPreallocSize = GrSizeAlignUp(preallocSize + kPerAllocPad, kAlignment);
     fPreallocSize = SkTMax(fPreallocSize, fMinAllocSize);
+    fSize = fPreallocSize;
 
     fHead = CreateBlock(fPreallocSize);
     fTail = fHead;
@@ -50,6 +52,8 @@ void* GrMemoryPool::allocate(size_t size) {
         SkASSERT(NULL == fTail->fNext);
         fTail->fNext = block;
         fTail = block;
+        fSize += block->fSize;
+        SkDEBUGCODE(++fAllocBlockCnt);
     }
     SkASSERT(fTail->fFreeSize >= size);
     intptr_t ptr = fTail->fCurrPtr;
@@ -61,6 +65,7 @@ void* GrMemoryPool::allocate(size_t size) {
     fTail->fCurrPtr += size;
     fTail->fFreeSize -= size;
     fTail->fLiveCount += 1;
+
     SkDEBUGCODE(++fAllocationCnt);
     VALIDATE;
     return reinterpret_cast<void*>(ptr);
@@ -73,8 +78,7 @@ void GrMemoryPool::release(void* p) {
     if (1 == block->fLiveCount) {
         // the head block is special, it is reset rather than deleted
         if (fHead == block) {
-            fHead->fCurrPtr = reinterpret_cast<intptr_t>(fHead) +
-                                kHeaderSize;
+            fHead->fCurrPtr = reinterpret_cast<intptr_t>(fHead) + kHeaderSize;
             fHead->fLiveCount = 0;
             fHead->fFreeSize = fPreallocSize;
         } else {
@@ -88,7 +92,9 @@ void GrMemoryPool::release(void* p) {
                 SkASSERT(fTail == block);
                 fTail = prev;
             }
+            fSize -= block->fSize;
             DeleteBlock(block);
+            SkDEBUGCODE(fAllocBlockCnt--);
         }
     } else {
         --block->fLiveCount;
@@ -103,14 +109,16 @@ void GrMemoryPool::release(void* p) {
 }
 
 GrMemoryPool::BlockHeader* GrMemoryPool::CreateBlock(size_t size) {
+    size_t paddedSize = size + kHeaderSize;
     BlockHeader* block =
-        reinterpret_cast<BlockHeader*>(sk_malloc_throw(size + kHeaderSize));
+        reinterpret_cast<BlockHeader*>(sk_malloc_throw(paddedSize));
     // we assume malloc gives us aligned memory
     SkASSERT(!(reinterpret_cast<intptr_t>(block) % kAlignment));
     block->fLiveCount = 0;
     block->fFreeSize = size;
     block->fCurrPtr = reinterpret_cast<intptr_t>(block) + kHeaderSize;
     block->fPrevPtr = 0; // gcc warns on assigning NULL to an intptr_t.
+    block->fSize = paddedSize;
     return block;
 }
 
@@ -157,5 +165,6 @@ void GrMemoryPool::validate() {
     } while ((block = block->fNext));
     SkASSERT(allocCount == fAllocationCnt);
     SkASSERT(prev == fTail);
+    SkASSERT(fAllocBlockCnt != 0 || fSize == fPreallocSize);
 #endif
 }
index 5ab8958..4de641d 100644 (file)
@@ -43,6 +43,11 @@ public:
      */
     bool isEmpty() const { return fTail == fHead && !fHead->fLiveCount; }
 
+    /**
+     * Returns the total allocated size of the GrMemoryPool
+     */
+    size_t size() const { return fSize; }
+
 private:
     struct BlockHeader;
 
@@ -60,6 +65,7 @@ private:
         intptr_t     fCurrPtr;   ///< ptr to the start of blocks free space.
         intptr_t     fPrevPtr;   ///< ptr to the last allocation made
         size_t       fFreeSize;  ///< amount of free space left in the block.
+        size_t       fSize;      ///< total allocated size of the block
     };
 
     enum {
@@ -68,12 +74,14 @@ private:
         kHeaderSize   = GR_CT_ALIGN_UP(sizeof(BlockHeader), kAlignment),
         kPerAllocPad  = GR_CT_ALIGN_UP(sizeof(BlockHeader*), kAlignment),
     };
+    size_t                            fSize;
     size_t                            fPreallocSize;
     size_t                            fMinAllocSize;
     BlockHeader*                      fHead;
     BlockHeader*                      fTail;
 #ifdef SK_DEBUG
     int                               fAllocationCnt;
+    int                               fAllocBlockCnt;
 #endif
 };
 
diff --git a/src/gpu/GrTextBlobCache.cpp b/src/gpu/GrTextBlobCache.cpp
new file mode 100644 (file)
index 0000000..9141b17
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrTextBlobCache.h"
+
+static const int kVerticesPerGlyph = 4;
+
+GrTextBlobCache::~GrTextBlobCache() {
+    SkTDynamicHash<BitmapTextBlob, uint32_t>::Iter iter(&fCache);
+    while (!iter.done()) {
+        (&(*iter))->unref();
+        ++iter;
+    }
+}
+
+GrAtlasTextContext::BitmapTextBlob* GrTextBlobCache::createBlob(int glyphCount, int runCount,
+                                                                size_t maxVASize) {
+    // We allocate size for the BitmapTextBlob itself, plus size for the vertices array,
+    // and size for the glyphIds array.
+    size_t verticesCount = glyphCount * kVerticesPerGlyph * maxVASize;
+    size_t size = sizeof(BitmapTextBlob) +
+                  verticesCount +
+                  glyphCount * sizeof(GrGlyph::PackedID) +
+                  sizeof(BitmapTextBlob::Run) * runCount;
+
+    BitmapTextBlob* cacheBlob = SkNEW_PLACEMENT(fPool.allocate(size), BitmapTextBlob);
+
+    // setup offsets for vertices / glyphs
+    cacheBlob->fVertices = sizeof(BitmapTextBlob) + reinterpret_cast<unsigned char*>(cacheBlob);
+    cacheBlob->fGlyphIDs =
+            reinterpret_cast<GrGlyph::PackedID*>(cacheBlob->fVertices + verticesCount);
+    cacheBlob->fRuns = reinterpret_cast<BitmapTextBlob::Run*>(cacheBlob->fGlyphIDs + glyphCount);
+
+    // Initialize runs
+    for (int i = 0; i < runCount; i++) {
+        SkNEW_PLACEMENT(&cacheBlob->fRuns[i], BitmapTextBlob::Run);
+    }
+    cacheBlob->fRunCount = runCount;
+    cacheBlob->fPool = &fPool;
+    return cacheBlob;
+}
diff --git a/src/gpu/GrTextBlobCache.h b/src/gpu/GrTextBlobCache.h
new file mode 100644 (file)
index 0000000..7a2b2a7
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrTextBlobCache_DEFINED
+#define GrTextBlobCache_DEFINED
+
+#include "GrAtlasTextContext.h"
+#include "SkTDynamicHash.h"
+#include "SkTextBlob.h"
+
+class GrTextBlobCache {
+public:
+    typedef GrAtlasTextContext::BitmapTextBlob BitmapTextBlob;
+
+    GrTextBlobCache() : fPool(kPreAllocSize, kMinGrowthSize) {}
+    ~GrTextBlobCache();
+
+    // creates an uncached blob
+    BitmapTextBlob* createBlob(int glyphCount, int runCount, size_t maxVASize);
+
+    BitmapTextBlob* createCachedBlob(const SkTextBlob* blob, size_t maxVAStride) {
+        int glyphCount = 0;
+        int runCount = 0;
+        BlobGlyphCount(&glyphCount, &runCount, blob);
+        BitmapTextBlob* cacheBlob = this->createBlob(glyphCount, runCount, maxVAStride);
+        cacheBlob->fUniqueID = blob->uniqueID();
+        this->add(cacheBlob);
+        return cacheBlob;
+    }
+
+    BitmapTextBlob* find(uint32_t uniqueID) {
+        return fCache.find(uniqueID);
+    }
+
+    void remove(BitmapTextBlob* blob) {
+        fCache.remove(blob->fUniqueID);
+        fBlobList.remove(blob);
+        blob->unref();
+    }
+
+    void add(BitmapTextBlob* blob) {
+        fCache.add(blob);
+        fBlobList.addToHead(blob);
+
+        // If we are overbudget, then unref until we are below budget again
+        if (fPool.size() > kBudget) {
+            BitmapBlobList::Iter iter;
+            iter.init(fBlobList, BitmapBlobList::Iter::kTail_IterStart);
+            BitmapTextBlob* lruBlob = iter.get();
+            SkASSERT(lruBlob);
+            do {
+                fCache.remove(lruBlob->fUniqueID);
+                fBlobList.remove(lruBlob);
+                lruBlob->unref();
+                iter.prev();
+            } while (fPool.size() > kBudget && (lruBlob = iter.get()));
+        }
+    }
+
+    void makeMRU(BitmapTextBlob* blob) {
+        if (fBlobList.head() == blob) {
+            return;
+        }
+
+        fBlobList.remove(blob);
+        fBlobList.addToHead(blob);
+    }
+
+private:
+    // TODO move to SkTextBlob
+    void BlobGlyphCount(int* glyphCount, int* runCount, const SkTextBlob* blob) {
+        SkTextBlob::RunIterator itCounter(blob);
+        for (; !itCounter.done(); itCounter.next(), (*runCount)++) {
+            *glyphCount += itCounter.glyphCount();
+        }
+    }
+
+    typedef SkTInternalLList<BitmapTextBlob> BitmapBlobList;
+
+    // Budget was chosen to be ~4 megabytes.  The min alloc and pre alloc sizes in the pool are
+    // based off of the largest cached textblob I have seen in the skps(a couple of kilobytes).
+    static const int kPreAllocSize = 1 << 17;
+    static const int kMinGrowthSize = 1 << 17;
+    static const int kBudget = 1 << 20;
+    BitmapBlobList fBlobList;
+    SkTDynamicHash<BitmapTextBlob, uint32_t> fCache;
+    GrMemoryPool fPool;
+};
+
+#endif