BitmapTextBatch and BitmapTextBlob

BUG=skia:

Committed: https://skia.googlesource.com/skia/+/eed1dae04932483579b02c10f0706127d3f5d984

Review URL: https://codereview.chromium.org/1011403004
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index 8bb1b25..929ff05 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -243,8 +243,21 @@
         return SkStringPrintf("Couldn't decode %s as a picture.", fPath.c_str());
     }
     stream.reset((SkStream*)NULL);  // Might as well drop this when we're done with it.
+
     canvas->clipRect(kSKPViewport);
+    // Testing TextBlob batching requires that we see individual text blobs more than once
+    // TODO remove this and add a flag to DM so we can run skps multiple times
+//#define DOUBLE_LOOP
+#ifdef DOUBLE_LOOP
+    {
+        SkAutoCanvasRestore acr(canvas, true);
+#endif
+        canvas->drawPicture(pic);
+#ifdef DOUBLE_LOOP
+    }
+    canvas->clear(0);
     canvas->drawPicture(pic);
+#endif
     return "";
 }
 
diff --git a/gyp/gpu.gypi b/gyp/gpu.gypi
index 98c9175..0d9c4fe 100644
--- a/gyp/gpu.gypi
+++ b/gyp/gpu.gypi
@@ -65,6 +65,8 @@
       '<(skia_src_path)/gpu/GrBatch.h',
       '<(skia_src_path)/gpu/GrBatchAtlas.cpp',
       '<(skia_src_path)/gpu/GrBatchAtlas.h',
+      '<(skia_src_path)/gpu/GrBatchFontCache.cpp',
+      '<(skia_src_path)/gpu/GrBatchFontCache.h',
       '<(skia_src_path)/gpu/GrBatchTarget.cpp',
       '<(skia_src_path)/gpu/GrBatchTarget.h',
       '<(skia_src_path)/gpu/GrBitmapTextContext.cpp',
diff --git a/include/core/SkPaint.h b/include/core/SkPaint.h
index 4bf8c05..5015c49 100644
--- a/include/core/SkPaint.h
+++ b/include/core/SkPaint.h
@@ -1122,6 +1122,7 @@
     friend class SkGraphics; // So Term() can be called.
     friend class SkPDFDevice;
     friend class GrBitmapTextContext;
+    friend class GrBitmapTextContextB;
     friend class GrDistanceFieldTextContext;
     friend class GrStencilAndCoverTextContext;
     friend class GrPathRendering;
diff --git a/include/core/SkTextBlob.h b/include/core/SkTextBlob.h
index d31ec5c..4ec9c5f 100644
--- a/include/core/SkTextBlob.h
+++ b/include/core/SkTextBlob.h
@@ -91,6 +91,7 @@
 
     static unsigned ScalarsPerGlyph(GlyphPositioning pos);
 
+    friend class GrBitmapTextContextB;
     friend class GrTextContext;
     friend class SkBaseDevice;
     friend class SkTextBlobBuilder;
diff --git a/include/gpu/GrContext.h b/include/gpu/GrContext.h
index 335b416..f631f56 100644
--- a/include/gpu/GrContext.h
+++ b/include/gpu/GrContext.h
@@ -19,6 +19,7 @@
 #include "SkTypes.h"
 
 class GrAARectRenderer;
+class GrBatchFontCache;
 class GrDrawTarget;
 class GrFontCache;
 class GrFragmentProcessor;
@@ -654,6 +655,7 @@
     // Functions intended for internal use only.
     GrGpu* getGpu() { return fGpu; }
     const GrGpu* getGpu() const { return fGpu; }
+    GrBatchFontCache* getBatchFontCache() { return fBatchFontCache; }
     GrFontCache* getFontCache() { return fFontCache; }
     GrLayerCache* getLayerCache() { return fLayerCache.get(); }
     GrDrawTarget* getTextTarget();
@@ -695,6 +697,7 @@
     GrGpu*                          fGpu;
 
     GrResourceCache*                fResourceCache;
+    GrBatchFontCache*               fBatchFontCache;
     GrFontCache*                    fFontCache;
     SkAutoTDelete<GrLayerCache>     fLayerCache;
 
diff --git a/src/gpu/GrAADistanceFieldPathRenderer.cpp b/src/gpu/GrAADistanceFieldPathRenderer.cpp
index 8b119f0..789406b 100755
--- a/src/gpu/GrAADistanceFieldPathRenderer.cpp
+++ b/src/gpu/GrAADistanceFieldPathRenderer.cpp
@@ -293,8 +293,7 @@
             instancesToFlush++;
         }
 
-        this->flush(batchTarget, dfProcessor, pipeline, &drawInfo, instancesToFlush,
-                    maxInstancesPerDraw);
+        this->flush(batchTarget, &drawInfo, instancesToFlush, maxInstancesPerDraw);
     }
 
     SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; }
@@ -421,8 +420,7 @@
         bool success = atlas->addToAtlas(&id, batchTarget, width, height, dfStorage.get(),
                                          &atlasLocation);
         if (!success) {
-            this->flush(batchTarget, dfProcessor, pipeline, drawInfo, *instancesToFlush,
-                        maxInstancesPerDraw);
+            this->flush(batchTarget, drawInfo, *instancesToFlush, maxInstancesPerDraw);
             this->initDraw(batchTarget, dfProcessor, pipeline);
             *instancesToFlush = 0;
 
@@ -516,8 +514,6 @@
     }
 
     void flush(GrBatchTarget* batchTarget,
-               const GrGeometryProcessor* dfProcessor,
-               const GrPipeline* pipeline,
                GrDrawTarget::DrawInfo* drawInfo,
                int instanceCount,
                int maxInstancesPerDraw) {
diff --git a/src/gpu/GrBatchAtlas.cpp b/src/gpu/GrBatchAtlas.cpp
index 566cd5d..3374e00 100644
--- a/src/gpu/GrBatchAtlas.cpp
+++ b/src/gpu/GrBatchAtlas.cpp
@@ -227,7 +227,8 @@
     , fNumPlotsX(numPlotsX)
     , fNumPlotsY(numPlotsY)
     , fPlotWidth(texture->width() / numPlotsX)
-    , fPlotHeight(texture->height() / numPlotsY) {
+    , fPlotHeight(texture->height() / numPlotsY)
+    , fAtlasGeneration(kInvalidAtlasGeneration + 1) {
     SkASSERT(fPlotWidth * fNumPlotsX == texture->width());
     SkASSERT(fPlotHeight * fNumPlotsY == texture->height());
 
@@ -243,7 +244,7 @@
         for (int x = fNumPlotsX - 1, c = 0; x >= 0; --x, ++c) {
             int id = r * fNumPlotsX + c;
             currPlot->reset(SkNEW(BatchPlot));
-            (*currPlot)->init(this, texture, id, 0, x, y, fPlotWidth, fPlotHeight, fBPP);
+            (*currPlot)->init(this, texture, id, 1, x, y, fPlotWidth, fPlotHeight, fBPP);
 
             // build LRU list
             fPlotList.addToHead(currPlot->get());
@@ -318,6 +319,7 @@
         SkDEBUGCODE(bool verify = )plot->addSubImage(width, height, image, loc, fBPP * width);
         SkASSERT(verify);
         this->updatePlot(batchTarget, id, plot);
+        fAtlasGeneration++;
         return true;
     }
 
@@ -352,6 +354,7 @@
     batchTarget->upload(uploader);
     *id = newPlot->id();
     plot->unref();
+    fAtlasGeneration++;
     return true;
 }
 
diff --git a/src/gpu/GrBatchAtlas.h b/src/gpu/GrBatchAtlas.h
index b514b9d..cd8123f 100644
--- a/src/gpu/GrBatchAtlas.h
+++ b/src/gpu/GrBatchAtlas.h
@@ -25,6 +25,8 @@
     // An AtlasID is an opaque handle which callers can use to determine if the atlas contains
     // a specific piece of data
     typedef uint32_t AtlasID;
+    static const uint32_t kInvalidAtlasID = 0;
+    static const uint64_t kInvalidAtlasGeneration = 0;
 
     // A function pointer for use as a callback during eviction.  Whenever GrBatchAtlas evicts a
     // specific AtlasID, it will call all of the registered listeners so they can optionally process
@@ -43,6 +45,7 @@
 
     GrTexture* getTexture() const { return fTexture; }
 
+    uint64_t atlasGeneration() const { return fAtlasGeneration; }
     bool hasID(AtlasID id);
     void setLastRefToken(AtlasID id, BatchToken batchToken);
     void registerEvictionCallback(EvictionFunc func, void* userData) {
@@ -72,6 +75,7 @@
     int fPlotWidth;
     int fPlotHeight;
     size_t fBPP;
+    uint64_t fAtlasGeneration;
 
     struct EvictionData {
         EvictionFunc fFunc;
diff --git a/src/gpu/GrBatchFontCache.cpp b/src/gpu/GrBatchFontCache.cpp
new file mode 100644
index 0000000..c429d53
--- /dev/null
+++ b/src/gpu/GrBatchFontCache.cpp
@@ -0,0 +1,314 @@
+/*
+ * 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 "GrBatchFontCache.h"
+#include "GrFontAtlasSizes.h"
+#include "GrGpu.h"
+#include "GrRectanizer.h"
+#include "GrSurfacePriv.h"
+#include "SkString.h"
+
+#include "SkDistanceFieldGen.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+static GrBatchAtlas* make_atlas(GrContext* context, GrPixelConfig config,
+                                int textureWidth, int textureHeight,
+                                int numPlotsX, int numPlotsY) {
+    GrSurfaceDesc desc;
+    desc.fFlags = kNone_GrSurfaceFlags;
+    desc.fWidth = textureWidth;
+    desc.fHeight = textureHeight;
+    desc.fConfig = config;
+
+    // We don't want to flush the context so we claim we're in the middle of flushing so as to
+    // guarantee we do not recieve a texture with pending IO
+    GrTexture* texture = context->refScratchTexture(desc, GrContext::kApprox_ScratchTexMatch, true);
+    if (!texture) {
+        return NULL;
+    }
+    return SkNEW_ARGS(GrBatchAtlas, (texture, numPlotsX, numPlotsY));
+}
+
+int GrBatchFontCache::MaskFormatToAtlasIndex(GrMaskFormat format) {
+    static const int sAtlasIndices[] = {
+        kA8_GrMaskFormat,
+        kA565_GrMaskFormat,
+        kARGB_GrMaskFormat,
+    };
+    SK_COMPILE_ASSERT(SK_ARRAY_COUNT(sAtlasIndices) == kMaskFormatCount, array_size_mismatch);
+
+    SkASSERT(sAtlasIndices[format] < kMaskFormatCount);
+    return sAtlasIndices[format];
+}
+
+GrMaskFormat GrBatchFontCache::AtlasIndexToMaskFormat(int atlasIndex) {
+    static GrMaskFormat sMaskFormats[] = {
+        kA8_GrMaskFormat,
+        kA565_GrMaskFormat,
+        kARGB_GrMaskFormat,
+    };
+    SK_COMPILE_ASSERT(SK_ARRAY_COUNT(sMaskFormats) == kMaskFormatCount, array_size_mismatch);
+
+    SkASSERT(sMaskFormats[atlasIndex] < kMaskFormatCount);
+    return sMaskFormats[atlasIndex];
+}
+
+GrBatchFontCache::GrBatchFontCache()
+    : fPreserveStrike(NULL) {
+}
+
+void GrBatchFontCache::init(GrContext* context) {
+    for (int i = 0; i < kMaskFormatCount; i++) {
+        GrMaskFormat format = AtlasIndexToMaskFormat(i);
+        GrPixelConfig config = this->getPixelConfig(format);
+
+        if (kA8_GrMaskFormat == format) {
+            fAtlases[i] = make_atlas(context, config,
+                                     GR_FONT_ATLAS_A8_TEXTURE_WIDTH,
+                                     GR_FONT_ATLAS_TEXTURE_HEIGHT,
+                                     GR_FONT_ATLAS_A8_NUM_PLOTS_X,
+                                     GR_FONT_ATLAS_NUM_PLOTS_Y);
+        } else {
+            fAtlases[i] = make_atlas(context, config,
+                                     GR_FONT_ATLAS_TEXTURE_WIDTH,
+                                     GR_FONT_ATLAS_TEXTURE_HEIGHT,
+                                     GR_FONT_ATLAS_NUM_PLOTS_X,
+                                     GR_FONT_ATLAS_NUM_PLOTS_Y);
+        }
+
+        if (fAtlases[i]) {
+            fAtlases[i]->registerEvictionCallback(&GrBatchFontCache::HandleEviction, (void*)this);
+        }
+    }
+}
+
+GrBatchFontCache::~GrBatchFontCache() {
+    SkTDynamicHash<GrBatchTextStrike, GrFontDescKey>::Iter iter(&fCache);
+    while (!iter.done()) {
+        SkDELETE(&(*iter));
+        ++iter;
+    }
+    for (int i = 0; i < kMaskFormatCount; ++i) {
+        SkDELETE(fAtlases[i]);
+    }
+}
+
+GrBatchTextStrike* GrBatchFontCache::generateStrike(GrFontScaler* scaler) {
+    GrBatchTextStrike* strike = SkNEW_ARGS(GrBatchTextStrike, (this, scaler->getKey()));
+    fCache.add(strike);
+    return strike;
+}
+
+void GrBatchFontCache::freeAll() {
+    SkTDynamicHash<GrBatchTextStrike, GrFontDescKey>::Iter iter(&fCache);
+    while (!iter.done()) {
+        SkDELETE(&(*iter));
+        ++iter;
+    }
+    fCache.rewind();
+    for (int i = 0; i < kMaskFormatCount; ++i) {
+        SkDELETE(fAtlases[i]);
+        fAtlases[i] = NULL;
+    }
+}
+
+inline GrBatchAtlas* GrBatchFontCache::getAtlas(GrMaskFormat format) const {
+    int atlasIndex = MaskFormatToAtlasIndex(format);
+    SkASSERT(fAtlases[atlasIndex]);
+    return fAtlases[atlasIndex];
+}
+
+bool GrBatchFontCache::hasGlyph(GrGlyph* glyph) {
+    SkASSERT(glyph);
+    return this->getAtlas(glyph->fMaskFormat)->hasID(glyph->fID);
+}
+
+void GrBatchFontCache::setGlyphRefToken(GrGlyph* glyph, GrBatchAtlas::BatchToken batchToken) {
+    SkASSERT(glyph);
+    SkASSERT(this->getAtlas(glyph->fMaskFormat)->hasID(glyph->fID));
+    this->getAtlas(glyph->fMaskFormat)->setLastRefToken(glyph->fID, batchToken);
+}
+
+bool GrBatchFontCache::addToAtlas(GrBatchTextStrike* strike, GrBatchAtlas::AtlasID* id,
+                                  GrBatchTarget* batchTarget,
+                                  GrMaskFormat format, int width, int height, const void* image,
+                                  SkIPoint16* loc) {
+    fPreserveStrike = strike;
+    return this->getAtlas(format)->addToAtlas(id, batchTarget, width, height, image, loc);
+}
+
+uint64_t GrBatchFontCache::atlasGeneration(GrMaskFormat format) const {
+    return this->getAtlas(format)->atlasGeneration();
+}
+
+GrTexture* GrBatchFontCache::getTexture(GrMaskFormat format) {
+    int atlasIndex = MaskFormatToAtlasIndex(format);
+    SkASSERT(fAtlases[atlasIndex]);
+    return fAtlases[atlasIndex]->getTexture();
+}
+
+GrPixelConfig GrBatchFontCache::getPixelConfig(GrMaskFormat format) const {
+    static const GrPixelConfig kPixelConfigs[] = {
+        kAlpha_8_GrPixelConfig,
+        kRGB_565_GrPixelConfig,
+        kSkia8888_GrPixelConfig
+    };
+    SK_COMPILE_ASSERT(SK_ARRAY_COUNT(kPixelConfigs) == kMaskFormatCount, array_size_mismatch);
+
+    return kPixelConfigs[format];
+}
+
+void GrBatchFontCache::HandleEviction(GrBatchAtlas::AtlasID id, void* ptr) {
+    GrBatchFontCache* fontCache = reinterpret_cast<GrBatchFontCache*>(ptr);
+
+    SkTDynamicHash<GrBatchTextStrike, GrFontDescKey>::Iter iter(&fontCache->fCache);
+    for (; !iter.done(); ++iter) {
+        GrBatchTextStrike* strike = &*iter;
+        strike->removeID(id);
+
+        // clear out any empty strikes.  We will preserve the strike whose call to addToAtlas
+        // triggered the eviction
+        if (strike != fontCache->fPreserveStrike && 0 == strike->fAtlasedGlyphs) {
+            fontCache->fCache.remove(*(strike->fFontScalerKey));
+            SkDELETE(strike);
+        }
+    }
+}
+
+void GrBatchFontCache::dump() const {
+    static int gDumpCount = 0;
+    for (int i = 0; i < kMaskFormatCount; ++i) {
+        if (fAtlases[i]) {
+            GrTexture* texture = fAtlases[i]->getTexture();
+            if (texture) {
+                SkString filename;
+#ifdef SK_BUILD_FOR_ANDROID
+                filename.printf("/sdcard/fontcache_%d%d.png", gDumpCount, i);
+#else
+                filename.printf("fontcache_%d%d.png", gDumpCount, i);
+#endif
+                texture->surfacePriv().savePixels(filename.c_str());
+            }
+        }
+    }
+    ++gDumpCount;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+/*
+    The text strike is specific to a given font/style/matrix setup, which is
+    represented by the GrHostFontScaler object we are given in getGlyph().
+
+    We map a 32bit glyphID to a GrGlyph record, which in turn points to a
+    atlas and a position within that texture.
+ */
+
+GrBatchTextStrike::GrBatchTextStrike(GrBatchFontCache* cache, const GrFontDescKey* key)
+    : fFontScalerKey(SkRef(key))
+    , fPool(9/*start allocations at 512 bytes*/)
+    , fAtlasedGlyphs(0) {
+
+    fBatchFontCache = cache;     // no need to ref, it won't go away before we do
+}
+
+GrBatchTextStrike::~GrBatchTextStrike() {
+    SkTDynamicHash<GrGlyph, GrGlyph::PackedID>::Iter iter(&fCache);
+    while (!iter.done()) {
+        (*iter).free();
+        ++iter;
+    }
+}
+
+GrGlyph* GrBatchTextStrike::generateGlyph(GrGlyph::PackedID packed,
+                                          GrFontScaler* scaler) {
+    SkIRect bounds;
+    if (GrGlyph::kDistance_MaskStyle == GrGlyph::UnpackMaskStyle(packed)) {
+        if (!scaler->getPackedGlyphDFBounds(packed, &bounds)) {
+            return NULL;
+        }
+    } else {
+        if (!scaler->getPackedGlyphBounds(packed, &bounds)) {
+            return NULL;
+        }
+    }
+    GrMaskFormat format = scaler->getPackedGlyphMaskFormat(packed);
+    
+    GrGlyph* glyph = (GrGlyph*)fPool.alloc(sizeof(GrGlyph), SK_MALLOC_THROW);
+    glyph->init(packed, bounds, format);
+    fCache.add(glyph);
+    return glyph;
+}
+
+void GrBatchTextStrike::removeID(GrBatchAtlas::AtlasID id) {
+    SkTDynamicHash<GrGlyph, GrGlyph::PackedID>::Iter iter(&fCache);
+    while (!iter.done()) {
+        if (id == (*iter).fID) {
+            (*iter).fID = GrBatchAtlas::kInvalidAtlasID;
+            fAtlasedGlyphs--;
+            SkASSERT(fAtlasedGlyphs >= 0);
+        }
+        ++iter;
+    }
+}
+
+bool GrBatchTextStrike::glyphTooLargeForAtlas(GrGlyph* glyph) {
+    int width = glyph->fBounds.width();
+    int height = glyph->fBounds.height();
+    bool useDistanceField =
+            (GrGlyph::kDistance_MaskStyle == GrGlyph::UnpackMaskStyle(glyph->fPackedID));
+    int pad = useDistanceField ? 2 * SK_DistanceFieldPad : 0;
+    int plotWidth = (kA8_GrMaskFormat == glyph->fMaskFormat) ? GR_FONT_ATLAS_A8_PLOT_WIDTH
+                                                             : GR_FONT_ATLAS_PLOT_WIDTH;
+    if (width + pad > plotWidth) {
+        return true;
+    }
+    if (height + pad > GR_FONT_ATLAS_PLOT_HEIGHT) {
+        return true;
+    }
+
+    return false;
+}
+
+bool GrBatchTextStrike::addGlyphToAtlas(GrBatchTarget* batchTarget, GrGlyph* glyph,
+                                        GrFontScaler* scaler) {
+    SkASSERT(glyph);
+    SkASSERT(scaler);
+    SkASSERT(fCache.find(glyph->fPackedID));
+    SkASSERT(NULL == glyph->fPlot);
+
+    SkAutoUnref ar(SkSafeRef(scaler));
+
+    int bytesPerPixel = GrMaskFormatBytesPerPixel(glyph->fMaskFormat);
+
+    size_t size = glyph->fBounds.area() * bytesPerPixel;
+    GrAutoMalloc<1024> storage(size);
+
+    if (GrGlyph::kDistance_MaskStyle == GrGlyph::UnpackMaskStyle(glyph->fPackedID)) {
+        if (!scaler->getPackedGlyphDFImage(glyph->fPackedID, glyph->width(),
+                                           glyph->height(),
+                                           storage.get())) {
+            return false;
+        }
+    } else {
+        if (!scaler->getPackedGlyphImage(glyph->fPackedID, glyph->width(),
+                                         glyph->height(),
+                                         glyph->width() * bytesPerPixel,
+                                         storage.get())) {
+            return false;
+        }
+    }
+
+    bool success = fBatchFontCache->addToAtlas(this, &glyph->fID, batchTarget, glyph->fMaskFormat,
+                                               glyph->width(), glyph->height(),
+                                               storage.get(), &glyph->fAtlasLocation);
+    if (success) {
+        fAtlasedGlyphs++;
+    }
+    return success;
+}
diff --git a/src/gpu/GrBatchFontCache.h b/src/gpu/GrBatchFontCache.h
new file mode 100644
index 0000000..6300fbe
--- /dev/null
+++ b/src/gpu/GrBatchFontCache.h
@@ -0,0 +1,137 @@
+/*
+ * 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 GrBatchFontCache_DEFINED
+#define GrBatchFontCache_DEFINED
+
+#include "GrBatchAtlas.h"
+#include "GrDrawTarget.h"
+#include "GrFontScaler.h"
+#include "GrGlyph.h"
+#include "SkTDynamicHash.h"
+#include "SkVarAlloc.h"
+
+class GrBatchFontCache;
+class GrBatchTarget;
+class GrGpu;
+
+/**
+ *  The GrBatchTextStrike manages a pool of CPU backing memory for Glyph Masks.  This backing memory
+ *  is abstracted by GrGlyph, and indexed by a PackedID and GrFontScaler.  The GrFontScaler is what
+ *  actually creates the mask.
+ */
+class GrBatchTextStrike {
+public:
+    GrBatchTextStrike(GrBatchFontCache*, const GrFontDescKey* fontScalerKey);
+    ~GrBatchTextStrike();
+
+    const GrFontDescKey* getFontScalerKey() const { return fFontScalerKey; }
+    GrBatchFontCache* getBatchFontCache() const { return fBatchFontCache; }
+
+    inline GrGlyph* getGlyph(GrGlyph::PackedID packed, GrFontScaler* scaler) {
+        GrGlyph* glyph = fCache.find(packed);
+        if (NULL == glyph) {
+            glyph = this->generateGlyph(packed, scaler);
+        }
+        return glyph;
+    }
+
+    // returns true if glyph (or glyph+padding for distance field)
+    // is too large to ever fit in texture atlas subregions (GrPlots)
+    bool glyphTooLargeForAtlas(GrGlyph*);
+    // returns true if glyph successfully added to texture atlas, false otherwise
+    bool addGlyphToAtlas(GrBatchTarget*, GrGlyph*, GrFontScaler*);
+
+    // testing
+    int countGlyphs() const { return fCache.count(); }
+
+    // remove any references to this plot
+    void removeID(GrBatchAtlas::AtlasID);
+
+    static const GrFontDescKey& GetKey(const GrBatchTextStrike& ts) {
+        return *(ts.fFontScalerKey);
+    }
+    static uint32_t Hash(const GrFontDescKey& key) {
+        return key.getHash();
+    }
+
+private:
+    SkTDynamicHash<GrGlyph, GrGlyph::PackedID> fCache;
+    SkAutoTUnref<const GrFontDescKey> fFontScalerKey;
+    SkVarAlloc fPool;
+
+    GrBatchFontCache* fBatchFontCache;
+    int fAtlasedGlyphs;
+
+    GrGlyph* generateGlyph(GrGlyph::PackedID packed, GrFontScaler* scaler);
+
+    friend class GrBatchFontCache;
+};
+
+/*
+ * GrBatchFontCache manages strikes which are indexed by a GrFontScaler.  These strikes can then be
+ * used to individual Glyph Masks.  The GrBatchFontCache also manages GrBatchAtlases, though this is
+ * more or less transparent to the client(aside from atlasGeneration, described below)
+ */
+class GrBatchFontCache {
+public:
+    GrBatchFontCache();
+    ~GrBatchFontCache();
+
+    // Initializes the GrBatchFontCache on the owning GrContext
+    void init(GrContext*);
+
+    inline GrBatchTextStrike* getStrike(GrFontScaler* scaler) {
+
+        GrBatchTextStrike* strike = fCache.find(*(scaler->getKey()));
+        if (NULL == strike) {
+            strike = this->generateStrike(scaler);
+        }
+        return strike;
+    }
+
+    bool hasGlyph(GrGlyph* glyph);
+
+    // To ensure the GrBatchAtlas does not evict the Glyph Mask from its texture backing store,
+    // the client must pass in the currentToken from the GrBatchTarget along with the GrGlyph
+    void setGlyphRefToken(GrGlyph*, GrBatchAtlas::BatchToken);
+
+    // add to texture atlas that matches this format
+    bool addToAtlas(GrBatchTextStrike*, GrBatchAtlas::AtlasID*, GrBatchTarget*,
+                    GrMaskFormat, int width, int height, const void* image,
+                    SkIPoint16* loc);
+
+    // Some clients may wish to verify the integrity of the texture backing store of the
+    // GrBatchAtlas.  The atlasGeneration returned below is a monitonically increasing number which
+    // changes everytime something is removed from the texture backing store.
+    uint64_t atlasGeneration(GrMaskFormat) const;
+
+    void freeAll();
+
+    GrTexture* getTexture(GrMaskFormat);
+    GrPixelConfig getPixelConfig(GrMaskFormat) const;
+
+    void dump() const;
+
+private:
+    // There is a 1:1 mapping between GrMaskFormats and atlas indices
+    static int MaskFormatToAtlasIndex(GrMaskFormat);
+    static GrMaskFormat AtlasIndexToMaskFormat(int atlasIndex);
+
+    GrBatchTextStrike* generateStrike(GrFontScaler*);
+
+    inline GrBatchAtlas* getAtlas(GrMaskFormat) const;
+
+    static void HandleEviction(GrBatchAtlas::AtlasID, void*);
+
+    SkTDynamicHash<GrBatchTextStrike, GrFontDescKey> fCache;
+
+    GrBatchAtlas* fAtlases[kMaskFormatCount];
+    GrBatchTextStrike* fPreserveStrike;
+};
+
+#endif
diff --git a/src/gpu/GrBitmapTextContext.cpp b/src/gpu/GrBitmapTextContext.cpp
index 4eeb172..4592667 100755
--- a/src/gpu/GrBitmapTextContext.cpp
+++ b/src/gpu/GrBitmapTextContext.cpp
@@ -4,9 +4,12 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
-
 #include "GrBitmapTextContext.h"
+
 #include "GrAtlas.h"
+#include "GrBatch.h"
+#include "GrBatchFontCache.h"
+#include "GrBatchTarget.h"
 #include "GrDefaultGeoProcFactory.h"
 #include "GrDrawTarget.h"
 #include "GrFontCache.h"
@@ -18,6 +21,7 @@
 #include "SkAutoKern.h"
 #include "SkColorPriv.h"
 #include "SkDraw.h"
+#include "SkDrawFilter.h"
 #include "SkDrawProcs.h"
 #include "SkGlyphCache.h"
 #include "SkGpuDevice.h"
@@ -25,6 +29,7 @@
 #include "SkPath.h"
 #include "SkRTConf.h"
 #include "SkStrokeRec.h"
+#include "SkTextBlob.h"
 #include "SkTextMapStateProc.h"
 
 #include "effects/GrBitmapTextGeoProc.h"
@@ -45,6 +50,934 @@
 static const int kIndicesPerGlyph = 6;
 };
 
+// TODO
+// More tests
+// move to SkCache
+// handle textblobs where the whole run is larger than the cache size
+// TODO implement micro speedy hash map for fast refing of glyphs
+
+GrBitmapTextContextB::GrBitmapTextContextB(GrContext* context,
+                                         SkGpuDevice* gpuDevice,
+                                         const SkDeviceProperties& properties)
+                                       : INHERITED(context, gpuDevice, properties) {
+    fCurrStrike = NULL;
+}
+
+void GrBitmapTextContextB::ClearCacheEntry(uint32_t key, BitmapTextBlob** blob) {
+    (*blob)->unref();
+}
+
+GrBitmapTextContextB::~GrBitmapTextContextB() {
+    fCache.foreach(&GrBitmapTextContextB::ClearCacheEntry);
+}
+
+GrBitmapTextContextB* GrBitmapTextContextB::Create(GrContext* context,
+                                                 SkGpuDevice* gpuDevice,
+                                                 const SkDeviceProperties& props) {
+    return SkNEW_ARGS(GrBitmapTextContextB, (context, gpuDevice, props));
+}
+
+bool GrBitmapTextContextB::canDraw(const GrRenderTarget*,
+                                  const GrClip&,
+                                  const GrPaint&,
+                                  const SkPaint& skPaint,
+                                  const SkMatrix& viewMatrix) {
+    return !SkDraw::ShouldDrawTextAsPaths(skPaint, viewMatrix);
+}
+
+inline void GrBitmapTextContextB::init(GrRenderTarget* rt, const GrClip& clip,
+                                      const GrPaint& paint, const SkPaint& skPaint,
+                                      const SkIRect& regionClipBounds) {
+    INHERITED::init(rt, clip, paint, skPaint, regionClipBounds);
+
+    fCurrStrike = NULL;
+}
+
+bool GrBitmapTextContextB::MustRegenerateBlob(const BitmapTextBlob& blob, const SkPaint& paint,
+                                             const SkMatrix& viewMatrix, SkScalar x, SkScalar y) {
+    // We always regenerate blobs with patheffects or mask filters we could cache these
+    // TODO find some way to cache the maskfilter / patheffects on the textblob
+    return !blob.fViewMatrix.cheapEqualTo(viewMatrix) || blob.fX != x || blob.fY != y ||
+            paint.getMaskFilter() || paint.getPathEffect() || paint.getStyle() != blob.fStyle;
+}
+
+void GrBitmapTextContextB::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());
+
+    SkIRect clipRect;
+    clip.getConservativeBounds(rt->width(), rt->height(), &clipRect);
+
+    if (foundBlob) {
+        cacheBlob = *foundBlob;
+        if (MustRegenerateBlob(*cacheBlob, skPaint, viewMatrix, x, y)) {
+            // We can get away with reusing the blob if there are no outstanding refs on it.
+            // However, we still have to reset all of the runs.
+            if (!cacheBlob->unique()) {
+                cacheBlob->unref();
+                cacheBlob = SkNEW(BitmapTextBlob);
+                fCache.set(blob->uniqueID(), cacheBlob);
+            }
+            this->regenerateTextBlob(cacheBlob, skPaint, viewMatrix, blob, x, y, drawFilter,
+                                     clipRect);
+        }
+    } else {
+        cacheBlob = SkNEW(BitmapTextBlob);
+        fCache.set(blob->uniqueID(), cacheBlob);
+        this->regenerateTextBlob(cacheBlob, skPaint, viewMatrix, blob, x, y, drawFilter, clipRect);
+    }
+
+    // Though for the time being runs in the textblob can override the paint, they only touch font
+    // info.
+    GrPaint grPaint;
+    SkPaint2GrPaintShader(fContext, rt, skPaint, viewMatrix, true, &grPaint);
+
+    this->flush(fContext->getTextTarget(), cacheBlob, rt, grPaint, clip, viewMatrix,
+                fSkPaint.getAlpha());
+}
+
+void GrBitmapTextContextB::regenerateTextBlob(BitmapTextBlob* cacheBlob,
+                                              const SkPaint& skPaint, const SkMatrix& viewMatrix,
+                                              const SkTextBlob* blob, SkScalar x, SkScalar y,
+                                              SkDrawFilter* drawFilter, const SkIRect& clipRect) {
+    cacheBlob->fViewMatrix = viewMatrix;
+    cacheBlob->fX = x;
+    cacheBlob->fY = y;
+    cacheBlob->fStyle = skPaint.getStyle();
+    cacheBlob->fRuns.reset(blob->fRunCount);
+
+    // Regenerate textblob
+    SkPaint runPaint = skPaint;
+    SkTextBlob::RunIterator it(blob);
+    for (int run = 0; !it.done(); it.next(), run++) {
+        size_t textLen = it.glyphCount() * sizeof(uint16_t);
+        const SkPoint& offset = it.offset();
+        // applyFontToPaint() always overwrites the exact same attributes,
+        // so it is safe to not re-seed the paint for this reason.
+        it.applyFontToPaint(&runPaint);
+
+        if (drawFilter && !drawFilter->filter(&runPaint, SkDrawFilter::kText_Type)) {
+            // A false return from filter() means we should abort the current draw.
+            runPaint = skPaint;
+            continue;
+        }
+
+        runPaint.setFlags(fGpuDevice->filterTextFlags(runPaint));
+
+        switch (it.positioning()) {
+            case SkTextBlob::kDefault_Positioning:
+                this->internalDrawText(cacheBlob, run, runPaint, viewMatrix,
+                                       (const char *)it.glyphs(), textLen,
+                                       x + offset.x(), y + offset.y(), clipRect);
+                break;
+            case SkTextBlob::kHorizontal_Positioning:
+                this->internalDrawPosText(cacheBlob, run, runPaint, viewMatrix,
+                                          (const char*)it.glyphs(), textLen, it.pos(), 1,
+                                          SkPoint::Make(x, y + offset.y()), clipRect);
+                break;
+            case SkTextBlob::kFull_Positioning:
+                this->internalDrawPosText(cacheBlob, run, runPaint, viewMatrix,
+                                          (const char*)it.glyphs(), textLen, it.pos(), 2,
+                                          SkPoint::Make(x, y), clipRect);
+                break;
+        }
+
+        if (drawFilter) {
+            // A draw filter may change the paint arbitrarily, so we must re-seed in this case.
+            runPaint = skPaint;
+        }
+    }
+}
+
+void GrBitmapTextContextB::onDrawText(GrRenderTarget* rt, const GrClip& clip,
+                                     const GrPaint& paint, const SkPaint& skPaint,
+                                     const SkMatrix& viewMatrix,
+                                     const char text[], size_t byteLength,
+                                     SkScalar x, SkScalar y, const SkIRect& regionClipBounds) {
+    SkAutoTUnref<BitmapTextBlob> blob(SkNEW(BitmapTextBlob));
+    blob->fViewMatrix = viewMatrix;
+    blob->fX = x;
+    blob->fY = y;
+    blob->fStyle = skPaint.getStyle();
+    blob->fRuns.push_back();
+
+    SkIRect clipRect;
+    clip.getConservativeBounds(rt->width(), rt->height(), &clipRect);
+    this->internalDrawText(blob, 0, skPaint, viewMatrix, text, byteLength, x, y, clipRect);
+    this->flush(fContext->getTextTarget(), blob, rt, paint, clip, viewMatrix, skPaint.getAlpha());
+}
+
+void GrBitmapTextContextB::internalDrawText(BitmapTextBlob* blob, int runIndex,
+                                            const SkPaint& skPaint,
+                                           const SkMatrix& viewMatrix,
+                                           const char text[], size_t byteLength,
+                                           SkScalar x, SkScalar y, const SkIRect& clipRect) {
+    SkASSERT(byteLength == 0 || text != NULL);
+
+    // nothing to draw
+    if (text == NULL || byteLength == 0) {
+        return;
+    }
+
+    fCurrStrike = NULL;
+    SkDrawCacheProc glyphCacheProc = skPaint.getDrawCacheProc();
+
+    // Get GrFontScaler from cache
+    BitmapTextBlob::Run& run = blob->fRuns[runIndex];
+    run.fDescriptor.reset(skPaint.getScalerContextDescriptor(&fDeviceProperties, &viewMatrix,
+                                                              false));
+    run.fTypeface.reset(SkSafeRef(skPaint.getTypeface()));
+    const SkDescriptor* desc = reinterpret_cast<const SkDescriptor*>(run.fDescriptor->data());
+    SkGlyphCache* cache = SkGlyphCache::DetachCache(run.fTypeface, desc);
+    GrFontScaler* fontScaler = GetGrFontScaler(cache);
+
+    // transform our starting point
+    {
+        SkPoint loc;
+        viewMatrix.mapXY(x, y, &loc);
+        x = loc.fX;
+        y = loc.fY;
+    }
+
+    // need to measure first
+    if (skPaint.getTextAlign() != SkPaint::kLeft_Align) {
+        SkVector    stopVector;
+        MeasureText(cache, glyphCacheProc, text, byteLength, &stopVector);
+
+        SkScalar    stopX = stopVector.fX;
+        SkScalar    stopY = stopVector.fY;
+
+        if (skPaint.getTextAlign() == SkPaint::kCenter_Align) {
+            stopX = SkScalarHalf(stopX);
+            stopY = SkScalarHalf(stopY);
+        }
+        x -= stopX;
+        y -= stopY;
+    }
+
+    const char* stop = text + byteLength;
+
+    SkAutoKern autokern;
+
+    SkFixed fxMask = ~0;
+    SkFixed fyMask = ~0;
+    SkScalar halfSampleX, halfSampleY;
+    if (cache->isSubpixel()) {
+        halfSampleX = halfSampleY = SkFixedToScalar(SkGlyph::kSubpixelRound);
+        SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(viewMatrix);
+        if (kX_SkAxisAlignment == baseline) {
+            fyMask = 0;
+            halfSampleY = SK_ScalarHalf;
+        } else if (kY_SkAxisAlignment == baseline) {
+            fxMask = 0;
+            halfSampleX = SK_ScalarHalf;
+        }
+    } else {
+        halfSampleX = halfSampleY = SK_ScalarHalf;
+    }
+
+    Sk48Dot16 fx = SkScalarTo48Dot16(x + halfSampleX);
+    Sk48Dot16 fy = SkScalarTo48Dot16(y + halfSampleY);
+
+    while (text < stop) {
+        const SkGlyph& glyph = glyphCacheProc(cache, &text, fx & fxMask, fy & fyMask);
+
+        fx += autokern.adjust(glyph);
+
+        if (glyph.fWidth) {
+            this->appendGlyph(blob,
+                              runIndex,
+                              GrGlyph::Pack(glyph.getGlyphID(),
+                                            glyph.getSubXFixed(),
+                                            glyph.getSubYFixed(),
+                                            GrGlyph::kCoverage_MaskStyle),
+                              Sk48Dot16FloorToInt(fx),
+                              Sk48Dot16FloorToInt(fy),
+                              fontScaler,
+                              clipRect);
+        }
+
+        fx += glyph.fAdvanceX;
+        fy += glyph.fAdvanceY;
+    }
+
+    SkGlyphCache::AttachCache(cache);
+}
+
+void GrBitmapTextContextB::onDrawPosText(GrRenderTarget* rt, const GrClip& clip,
+                                        const GrPaint& paint, const SkPaint& skPaint,
+                                        const SkMatrix& viewMatrix,
+                                        const char text[], size_t byteLength,
+                                        const SkScalar pos[], int scalarsPerPosition,
+                                        const SkPoint& offset, const SkIRect& regionClipBounds) {
+    SkAutoTUnref<BitmapTextBlob> blob(SkNEW(BitmapTextBlob));
+    blob->fStyle = skPaint.getStyle();
+    blob->fRuns.push_back();
+    blob->fViewMatrix = viewMatrix;
+
+    SkIRect clipRect;
+    clip.getConservativeBounds(rt->width(), rt->height(), &clipRect);
+    this->internalDrawPosText(blob, 0, skPaint, viewMatrix, text, byteLength, pos,
+                              scalarsPerPosition, offset, clipRect);
+    this->flush(fContext->getTextTarget(), blob, rt, paint, clip, viewMatrix, fSkPaint.getAlpha());
+}
+
+void GrBitmapTextContextB::internalDrawPosText(BitmapTextBlob* blob, int runIndex,
+                                               const SkPaint& skPaint,
+                                              const SkMatrix& viewMatrix,
+                                              const char text[], size_t byteLength,
+                                              const SkScalar pos[], int scalarsPerPosition,
+                                              const SkPoint& offset, const SkIRect& clipRect) {
+    SkASSERT(byteLength == 0 || text != NULL);
+    SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
+
+    // nothing to draw
+    if (text == NULL || byteLength == 0) {
+        return;
+    }
+
+    fCurrStrike = NULL;
+    SkDrawCacheProc glyphCacheProc = skPaint.getDrawCacheProc();
+
+    // Get GrFontScaler from cache
+    BitmapTextBlob::Run& run = blob->fRuns[runIndex];
+    run.fDescriptor.reset(skPaint.getScalerContextDescriptor(&fDeviceProperties, &viewMatrix,
+                                                              false));
+    run.fTypeface.reset(SkSafeRef(skPaint.getTypeface()));
+    const SkDescriptor* desc = reinterpret_cast<const SkDescriptor*>(run.fDescriptor->data());
+    SkGlyphCache* cache = SkGlyphCache::DetachCache(run.fTypeface, desc);
+    GrFontScaler* fontScaler = GetGrFontScaler(cache);
+
+    const char*        stop = text + byteLength;
+    SkTextAlignProc    alignProc(skPaint.getTextAlign());
+    SkTextMapStateProc tmsProc(viewMatrix, offset, scalarsPerPosition);
+    SkScalar halfSampleX = 0, halfSampleY = 0;
+
+    if (cache->isSubpixel()) {
+        // maybe we should skip the rounding if linearText is set
+        SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(viewMatrix);
+
+        SkFixed fxMask = ~0;
+        SkFixed fyMask = ~0;
+        if (kX_SkAxisAlignment == baseline) {
+            fyMask = 0;
+            halfSampleY = SK_ScalarHalf;
+        } else if (kY_SkAxisAlignment == baseline) {
+            fxMask = 0;
+            halfSampleX = SK_ScalarHalf;
+        }
+
+        if (SkPaint::kLeft_Align == skPaint.getTextAlign()) {
+            while (text < stop) {
+                SkPoint tmsLoc;
+                tmsProc(pos, &tmsLoc);
+                Sk48Dot16 fx = SkScalarTo48Dot16(tmsLoc.fX + halfSampleX);
+                Sk48Dot16 fy = SkScalarTo48Dot16(tmsLoc.fY + halfSampleY);
+
+                const SkGlyph& glyph = glyphCacheProc(cache, &text,
+                                                      fx & fxMask, fy & fyMask);
+
+                if (glyph.fWidth) {
+                    this->appendGlyph(blob,
+                                      runIndex,
+                                      GrGlyph::Pack(glyph.getGlyphID(),
+                                                    glyph.getSubXFixed(),
+                                                    glyph.getSubYFixed(),
+                                                    GrGlyph::kCoverage_MaskStyle),
+                                      Sk48Dot16FloorToInt(fx),
+                                      Sk48Dot16FloorToInt(fy),
+                                      fontScaler,
+                                      clipRect);
+                }
+                pos += scalarsPerPosition;
+            }
+        } else {
+            while (text < stop) {
+                const char* currentText = text;
+                const SkGlyph& metricGlyph = glyphCacheProc(cache, &text, 0, 0);
+
+                if (metricGlyph.fWidth) {
+                    SkDEBUGCODE(SkFixed prevAdvX = metricGlyph.fAdvanceX;)
+                    SkDEBUGCODE(SkFixed prevAdvY = metricGlyph.fAdvanceY;)
+                    SkPoint tmsLoc;
+                    tmsProc(pos, &tmsLoc);
+                    SkPoint alignLoc;
+                    alignProc(tmsLoc, metricGlyph, &alignLoc);
+
+                    Sk48Dot16 fx = SkScalarTo48Dot16(alignLoc.fX + halfSampleX);
+                    Sk48Dot16 fy = SkScalarTo48Dot16(alignLoc.fY + halfSampleY);
+
+                    // have to call again, now that we've been "aligned"
+                    const SkGlyph& glyph = glyphCacheProc(cache, &currentText,
+                                                          fx & fxMask, fy & fyMask);
+                    // the assumption is that the metrics haven't changed
+                    SkASSERT(prevAdvX == glyph.fAdvanceX);
+                    SkASSERT(prevAdvY == glyph.fAdvanceY);
+                    SkASSERT(glyph.fWidth);
+
+                    this->appendGlyph(blob,
+                                      runIndex,
+                                      GrGlyph::Pack(glyph.getGlyphID(),
+                                                    glyph.getSubXFixed(),
+                                                    glyph.getSubYFixed(),
+                                                    GrGlyph::kCoverage_MaskStyle),
+                                      Sk48Dot16FloorToInt(fx),
+                                      Sk48Dot16FloorToInt(fy),
+                                      fontScaler,
+                                      clipRect);
+                }
+                pos += scalarsPerPosition;
+            }
+        }
+    } else {    // not subpixel
+
+        if (SkPaint::kLeft_Align == skPaint.getTextAlign()) {
+            while (text < stop) {
+                // the last 2 parameters are ignored
+                const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
+
+                if (glyph.fWidth) {
+                    SkPoint tmsLoc;
+                    tmsProc(pos, &tmsLoc);
+
+                    Sk48Dot16 fx = SkScalarTo48Dot16(tmsLoc.fX + SK_ScalarHalf); //halfSampleX;
+                    Sk48Dot16 fy = SkScalarTo48Dot16(tmsLoc.fY + SK_ScalarHalf); //halfSampleY;
+                    this->appendGlyph(blob,
+                                      runIndex,
+                                      GrGlyph::Pack(glyph.getGlyphID(),
+                                                    glyph.getSubXFixed(),
+                                                    glyph.getSubYFixed(),
+                                                    GrGlyph::kCoverage_MaskStyle),
+                                      Sk48Dot16FloorToInt(fx),
+                                      Sk48Dot16FloorToInt(fy),
+                                      fontScaler,
+                                      clipRect);
+                }
+                pos += scalarsPerPosition;
+            }
+        } else {
+            while (text < stop) {
+                // the last 2 parameters are ignored
+                const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
+
+                if (glyph.fWidth) {
+                    SkPoint tmsLoc;
+                    tmsProc(pos, &tmsLoc);
+
+                    SkPoint alignLoc;
+                    alignProc(tmsLoc, glyph, &alignLoc);
+
+                    Sk48Dot16 fx = SkScalarTo48Dot16(alignLoc.fX + SK_ScalarHalf); //halfSampleX;
+                    Sk48Dot16 fy = SkScalarTo48Dot16(alignLoc.fY + SK_ScalarHalf); //halfSampleY;
+                    this->appendGlyph(blob,
+                                      runIndex,
+                                      GrGlyph::Pack(glyph.getGlyphID(),
+                                                    glyph.getSubXFixed(),
+                                                    glyph.getSubYFixed(),
+                                                    GrGlyph::kCoverage_MaskStyle),
+                                      Sk48Dot16FloorToInt(fx),
+                                      Sk48Dot16FloorToInt(fy),
+                                      fontScaler,
+                                      clipRect);
+                }
+                pos += scalarsPerPosition;
+            }
+        }
+    }
+    SkGlyphCache::AttachCache(cache);
+}
+
+static size_t get_vertex_stride(GrMaskFormat maskFormat) {
+    switch (maskFormat) {
+        case kA8_GrMaskFormat:
+            return kGrayTextVASize;
+        case kARGB_GrMaskFormat:
+            return kColorTextVASize;
+        default:
+            return kLCDTextVASize;
+    }
+}
+
+void GrBitmapTextContextB::appendGlyph(BitmapTextBlob* blob, int runIndex, GrGlyph::PackedID packed,
+                                      int vx, int vy, GrFontScaler* scaler,
+                                      const SkIRect& clipRect) {
+    if (NULL == fCurrStrike) {
+        fCurrStrike = fContext->getBatchFontCache()->getStrike(scaler);
+    }
+
+    GrGlyph* glyph = fCurrStrike->getGlyph(packed, scaler);
+    if (NULL == glyph || glyph->fBounds.isEmpty()) {
+        return;
+    }
+
+    int x = vx + glyph->fBounds.fLeft;
+    int y = vy + glyph->fBounds.fTop;
+
+    // keep them as ints until we've done the clip-test
+    int width = glyph->fBounds.width();
+    int height = glyph->fBounds.height();
+
+    // check if we clipped out
+    if (clipRect.quickReject(x, y, x + width, y + height)) {
+        return;
+    }
+
+    // If the glyph is too large we fall back to paths
+    if (fCurrStrike->glyphTooLargeForAtlas(glyph)) {
+        if (NULL == glyph->fPath) {
+            SkPath* path = SkNEW(SkPath);
+            if (!scaler->getGlyphPath(glyph->glyphID(), path)) {
+                // flag the glyph as being dead?
+                SkDELETE(path);
+                return;
+            }
+            glyph->fPath = path;
+        }
+        SkASSERT(glyph->fPath);
+        blob->fBigGlyphs.push_back(BitmapTextBlob::BigGlyph(*glyph->fPath, vx, vy));
+        return;
+    }
+    GrMaskFormat format = glyph->fMaskFormat;
+    size_t vertexStride = get_vertex_stride(format);
+
+    BitmapTextBlob::Run& run = blob->fRuns[runIndex];
+    int glyphIdx = run.fInfos[format].fGlyphIDs.count();
+    *run.fInfos[format].fGlyphIDs.append() = packed;
+    run.fInfos[format].fVertices.append(static_cast<int>(vertexStride * kVerticesPerGlyph));
+
+    SkRect r;
+    r.fLeft = SkIntToScalar(x);
+    r.fTop = SkIntToScalar(y);
+    r.fRight = r.fLeft + SkIntToScalar(width);
+    r.fBottom = r.fTop + SkIntToScalar(height);
+
+    run.fVertexBounds.joinNonEmptyArg(r);
+    GrColor color = fPaint.getColor();
+    run.fColor = color;
+
+    intptr_t vertex = reinterpret_cast<intptr_t>(run.fInfos[format].fVertices.begin());
+    vertex += vertexStride * glyphIdx * kVerticesPerGlyph;
+
+    // V0
+    SkPoint* position = reinterpret_cast<SkPoint*>(vertex);
+    position->set(r.fLeft, r.fTop);
+    if (kA8_GrMaskFormat == format) {
+        SkColor* colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
+        *colorPtr = color;
+    }
+    vertex += vertexStride;
+
+    // V1
+    position = reinterpret_cast<SkPoint*>(vertex);
+    position->set(r.fLeft, r.fBottom);
+    if (kA8_GrMaskFormat == format) {
+        SkColor* colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
+        *colorPtr = color;
+    }
+    vertex += vertexStride;
+
+    // V2
+    position = reinterpret_cast<SkPoint*>(vertex);
+    position->set(r.fRight, r.fBottom);
+    if (kA8_GrMaskFormat == format) {
+        SkColor* colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
+        *colorPtr = color;
+    }
+    vertex += vertexStride;
+
+    // V3
+    position = reinterpret_cast<SkPoint*>(vertex);
+    position->set(r.fRight, r.fTop);
+    if (kA8_GrMaskFormat == format) {
+        SkColor* colorPtr = reinterpret_cast<SkColor*>(vertex + sizeof(SkPoint));
+        *colorPtr = color;
+    }
+}
+
+class BitmapTextBatch : public GrBatch {
+public:
+    typedef GrBitmapTextContextB::BitmapTextBlob Blob;
+    typedef Blob::Run Run;
+    typedef Run::PerFormatInfo TextInfo;
+    struct Geometry {
+        Geometry() {}
+        Geometry(const Geometry& geometry)
+            : fBlob(SkRef(geometry.fBlob.get()))
+            , fRun(geometry.fRun)
+            , fColor(geometry.fColor) {}
+        SkAutoTUnref<Blob> fBlob;
+        int fRun;
+        GrColor fColor;
+    };
+
+    static GrBatch* Create(const Geometry& geometry, GrColor color, GrMaskFormat maskFormat,
+                           GrBatchFontCache* fontCache) {
+        return SkNEW_ARGS(BitmapTextBatch, (geometry, color, maskFormat, fontCache));
+    }
+
+    const char* name() const override { return "BitmapTextBatch"; }
+
+    void getInvariantOutputColor(GrInitInvariantOutput* out) const override {
+        if (kARGB_GrMaskFormat == fMaskFormat) {
+            out->setUnknownFourComponents();
+        } else {
+            out->setKnownFourComponents(fBatch.fColor);
+        }
+    }
+
+    void getInvariantOutputCoverage(GrInitInvariantOutput* out) const override {
+        if (kARGB_GrMaskFormat != fMaskFormat) {
+            if (GrPixelConfigIsAlphaOnly(fPixelConfig)) {
+                out->setUnknownSingleComponent();
+            } else if (GrPixelConfigIsOpaque(fPixelConfig)) {
+                out->setUnknownOpaqueFourComponents();
+                out->setUsingLCDCoverage();
+            } else {
+                out->setUnknownFourComponents();
+                out->setUsingLCDCoverage();
+            }
+        } else {
+            out->setKnownSingleComponent(0xff);
+        }
+    }
+
+    void initBatchTracker(const GrPipelineInfo& init) override {
+        // Handle any color overrides
+        if (init.fColorIgnored) {
+            fBatch.fColor = GrColor_ILLEGAL;
+        } else if (GrColor_ILLEGAL != init.fOverrideColor) {
+            fBatch.fColor = init.fOverrideColor;
+        }
+
+        // setup batch properties
+        fBatch.fColorIgnored = init.fColorIgnored;
+        fBatch.fUsesLocalCoords = init.fUsesLocalCoords;
+        fBatch.fCoverageIgnored = init.fCoverageIgnored;
+    }
+
+    void generateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) override {
+        // if we have RGB, then we won't have any SkShaders so no need to use a localmatrix.
+        // TODO actually only invert if we don't have RGBA
+        SkMatrix localMatrix;
+        if (this->usesLocalCoords() && !this->viewMatrix().invert(&localMatrix)) {
+            SkDebugf("Cannot invert viewmatrix\n");
+            return;
+        }
+
+        GrTextureParams params(SkShader::kClamp_TileMode, GrTextureParams::kNone_FilterMode);
+        // This will be ignored in the non A8 case
+        bool opaqueVertexColors = GrColorIsOpaque(this->color());
+        SkAutoTUnref<const GrGeometryProcessor> gp(
+                GrBitmapTextGeoProc::Create(this->color(),
+                                            fFontCache->getTexture(fMaskFormat),
+                                            params,
+                                            fMaskFormat,
+                                            opaqueVertexColors,
+                                            localMatrix));
+
+        size_t vertexStride = gp->getVertexStride();
+        SkASSERT(vertexStride == get_vertex_stride(fMaskFormat));
+
+        this->initDraw(batchTarget, gp, pipeline);
+
+        int glyphCount = this->numGlyphs();
+        int instanceCount = fGeoData.count();
+        const GrVertexBuffer* vertexBuffer;
+        int firstVertex;
+
+        void* vertices = batchTarget->vertexPool()->makeSpace(vertexStride,
+                                                              glyphCount * kVerticesPerGlyph,
+                                                              &vertexBuffer,
+                                                              &firstVertex);
+        if (!vertices) {
+            SkDebugf("Could not allocate vertices\n");
+            return;
+        }
+
+        unsigned char* currVertex = reinterpret_cast<unsigned char*>(vertices);
+
+        // setup drawinfo
+        const GrIndexBuffer* quadIndexBuffer = batchTarget->quadIndexBuffer();
+        int maxInstancesPerDraw = quadIndexBuffer->maxQuads();
+
+        GrDrawTarget::DrawInfo drawInfo;
+        drawInfo.setPrimitiveType(kTriangles_GrPrimitiveType);
+        drawInfo.setStartVertex(0);
+        drawInfo.setStartIndex(0);
+        drawInfo.setVerticesPerInstance(kVerticesPerGlyph);
+        drawInfo.setIndicesPerInstance(kIndicesPerGlyph);
+        drawInfo.adjustStartVertex(firstVertex);
+        drawInfo.setVertexBuffer(vertexBuffer);
+        drawInfo.setIndexBuffer(quadIndexBuffer);
+
+        int instancesToFlush = 0;
+        for (int i = 0; i < instanceCount; i++) {
+            Geometry& args = fGeoData[i];
+            Blob* blob = args.fBlob;
+            Run& run = blob->fRuns[args.fRun];
+            TextInfo& info = run.fInfos[fMaskFormat];
+
+            uint64_t currentAtlasGen = fFontCache->atlasGeneration(fMaskFormat);
+            bool regenerateTextureCoords = info.fAtlasGeneration != currentAtlasGen;
+            bool regenerateColors = kA8_GrMaskFormat == fMaskFormat && run.fColor != args.fColor;
+            int glyphCount = info.fGlyphIDs.count();
+
+            // We regenerate both texture coords and colors in the blob itself, and update the
+            // atlas generation.  If we don't end up purging any unused plots, we can avoid
+            // regenerating the coords.  We could take a finer grained approach to updating texture
+            // coords but its not clear if the extra bookkeeping would offset any gains.
+            // To avoid looping over the glyphs twice, we do one loop and conditionally update color
+            // or coords as needed.  One final note, if we have to break a run for an atlas eviction
+            // then we can't really trust the atlas has all of the correct data.  Atlas evictions
+            // should be pretty rare, so we just always regenerate in those cases
+            if (regenerateTextureCoords || regenerateColors) {
+                // first regenerate texture coordinates / colors if need be
+                const SkDescriptor* desc = NULL;
+                SkGlyphCache* cache = NULL;
+                GrFontScaler* scaler = NULL;
+                GrBatchTextStrike* strike = NULL;
+                bool brokenRun = false;
+                if (regenerateTextureCoords) {
+                    desc = reinterpret_cast<const SkDescriptor*>(run.fDescriptor->data());
+                    cache = SkGlyphCache::DetachCache(run.fTypeface, desc);
+                    scaler = GrTextContext::GetGrFontScaler(cache);
+                    strike = fFontCache->getStrike(scaler);
+                }
+                for (int glyphIdx = 0; glyphIdx < glyphCount; glyphIdx++) {
+                    GrGlyph::PackedID glyphID = info.fGlyphIDs[glyphIdx];
+
+                    if (regenerateTextureCoords) {
+                        // Upload the glyph only if needed
+                        GrGlyph* glyph = strike->getGlyph(glyphID, scaler);
+                        SkASSERT(glyph);
+
+                        if (!fFontCache->hasGlyph(glyph) &&
+                            !strike->addGlyphToAtlas(batchTarget, glyph, scaler)) {
+                            this->flush(batchTarget, &drawInfo, instancesToFlush,
+                                        maxInstancesPerDraw);
+                            this->initDraw(batchTarget, gp, pipeline);
+                            instancesToFlush = 0;
+                            brokenRun = glyphIdx > 0;
+
+                            SkDEBUGCODE(bool success =) strike->addGlyphToAtlas(batchTarget, glyph,
+                                                                                scaler);
+                            SkASSERT(success);
+                        }
+
+                        fFontCache->setGlyphRefToken(glyph, batchTarget->currentToken());
+
+                        // Texture coords are the last vertex attribute so we get a pointer to the
+                        // first one and then map with stride in regenerateTextureCoords
+                        intptr_t vertex = reinterpret_cast<intptr_t>(info.fVertices.begin());
+                        vertex += vertexStride * glyphIdx * kVerticesPerGlyph;
+                        vertex += vertexStride - sizeof(SkIPoint16);
+
+                        this->regenerateTextureCoords(glyph, vertex, vertexStride);
+                    }
+
+                    if (regenerateColors) {
+                        intptr_t vertex = reinterpret_cast<intptr_t>(info.fVertices.begin());
+                        vertex += vertexStride * glyphIdx * kVerticesPerGlyph + sizeof(SkPoint);
+                        this->regenerateColors(vertex, vertexStride, args.fColor);
+                    }
+
+                    instancesToFlush++;
+                }
+
+                if (regenerateTextureCoords) {
+                    SkGlyphCache::AttachCache(cache);
+                    info.fAtlasGeneration = brokenRun ? GrBatchAtlas::kInvalidAtlasGeneration :
+                                                        fFontCache->atlasGeneration(fMaskFormat);
+                }
+            } else {
+                instancesToFlush += glyphCount;
+            }
+
+            // now copy all vertices
+            int byteCount = info.fVertices.count();
+            memcpy(currVertex, info.fVertices.begin(), byteCount);
+
+            currVertex += byteCount;
+        }
+
+        this->flush(batchTarget, &drawInfo, instancesToFlush, maxInstancesPerDraw);
+    }
+
+    SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; }
+
+private:
+    BitmapTextBatch(const Geometry& geometry, GrColor color, GrMaskFormat maskFormat,
+                    GrBatchFontCache* fontCache)
+            : fMaskFormat(maskFormat)
+            , fPixelConfig(fontCache->getPixelConfig(maskFormat))
+            , fFontCache(fontCache) {
+        this->initClassID<BitmapTextBatch>();
+        fGeoData.push_back(geometry);
+        fBatch.fColor = color;
+        fBatch.fViewMatrix = geometry.fBlob->fViewMatrix;
+        int numGlyphs = geometry.fBlob->fRuns[geometry.fRun].fInfos[maskFormat].fGlyphIDs.count();
+        fBatch.fNumGlyphs = numGlyphs;
+    }
+
+    void regenerateTextureCoords(GrGlyph* glyph, intptr_t vertex, size_t vertexStride) {
+        int width = glyph->fBounds.width();
+        int height = glyph->fBounds.height();
+        int u0 = glyph->fAtlasLocation.fX;
+        int v0 = glyph->fAtlasLocation.fY;
+        int u1 = u0 + width;
+        int v1 = v0 + height;
+
+        // we assume texture coords are the last vertex attribute, this is a bit fragile.
+        // TODO pass in this offset or something
+        SkIPoint16* textureCoords;
+        // V0
+        textureCoords = reinterpret_cast<SkIPoint16*>(vertex);
+        textureCoords->set(u0, v0);
+        vertex += vertexStride;
+
+        // V1
+        textureCoords = reinterpret_cast<SkIPoint16*>(vertex);
+        textureCoords->set(u0, v1);
+        vertex += vertexStride;
+
+        // V2
+        textureCoords = reinterpret_cast<SkIPoint16*>(vertex);
+        textureCoords->set(u1, v1);
+        vertex += vertexStride;
+
+        // V3
+        textureCoords = reinterpret_cast<SkIPoint16*>(vertex);
+        textureCoords->set(u1, v0);
+    }
+
+    void regenerateColors(intptr_t vertex, size_t vertexStride, GrColor color) {
+        for (int i = 0; i < kVerticesPerGlyph; i++) {
+            SkColor* vcolor = reinterpret_cast<SkColor*>(vertex);
+            *vcolor = color;
+            vertex += vertexStride;
+        }
+    }
+
+    void initDraw(GrBatchTarget* batchTarget,
+                  const GrGeometryProcessor* gp,
+                  const GrPipeline* pipeline) {
+        batchTarget->initDraw(gp, pipeline);
+
+        // TODO remove this when batch is everywhere
+        GrPipelineInfo init;
+        init.fColorIgnored = fBatch.fColorIgnored;
+        init.fOverrideColor = GrColor_ILLEGAL;
+        init.fCoverageIgnored = fBatch.fCoverageIgnored;
+        init.fUsesLocalCoords = this->usesLocalCoords();
+        gp->initBatchTracker(batchTarget->currentBatchTracker(), init);
+    }
+
+    void flush(GrBatchTarget* batchTarget,
+               GrDrawTarget::DrawInfo* drawInfo,
+               int instanceCount,
+               int maxInstancesPerDraw) {
+        while (instanceCount) {
+            drawInfo->setInstanceCount(SkTMin(instanceCount, maxInstancesPerDraw));
+            drawInfo->setVertexCount(drawInfo->instanceCount() * drawInfo->verticesPerInstance());
+            drawInfo->setIndexCount(drawInfo->instanceCount() * drawInfo->indicesPerInstance());
+
+            batchTarget->draw(*drawInfo);
+
+            drawInfo->setStartVertex(drawInfo->startVertex() + drawInfo->vertexCount());
+            instanceCount -= drawInfo->instanceCount();
+       }
+    }
+
+    GrColor color() const { return fBatch.fColor; }
+    const SkMatrix& viewMatrix() const { return fBatch.fViewMatrix; }
+    bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; }
+    int numGlyphs() const { return fBatch.fNumGlyphs; }
+
+    bool onCombineIfPossible(GrBatch* t) override {
+        BitmapTextBatch* that = t->cast<BitmapTextBatch>();
+
+        if (this->fMaskFormat != that->fMaskFormat) {
+            return false;
+        }
+
+        if (this->fMaskFormat != kA8_GrMaskFormat && this->color() != that->color()) {
+            return false;
+        }
+
+        if (this->usesLocalCoords() && !this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
+            return false;
+        }
+
+        fBatch.fNumGlyphs += that->numGlyphs();
+        fGeoData.push_back_n(that->geoData()->count(), that->geoData()->begin());
+        return true;
+    }
+
+    struct BatchTracker {
+        GrColor fColor;
+        SkMatrix fViewMatrix;
+        bool fUsesLocalCoords;
+        bool fColorIgnored;
+        bool fCoverageIgnored;
+        int fNumGlyphs;
+    };
+
+    BatchTracker fBatch;
+    SkSTArray<1, Geometry, true> fGeoData;
+    GrMaskFormat fMaskFormat;
+    GrPixelConfig fPixelConfig;
+    GrBatchFontCache* fFontCache;
+};
+
+void GrBitmapTextContextB::flushSubRun(GrDrawTarget* target, BitmapTextBlob* blob, int i,
+                                       GrPipelineBuilder* pipelineBuilder, GrMaskFormat format,
+                                       GrColor color, int paintAlpha) {
+    if (0 == blob->fRuns[i].fInfos[format].fGlyphIDs.count()) {
+        return;
+    }
+
+    if (kARGB_GrMaskFormat == format) {
+        color = SkColorSetARGB(paintAlpha, paintAlpha, paintAlpha, paintAlpha);
+    }
+
+    BitmapTextBatch::Geometry geometry;
+    geometry.fBlob.reset(SkRef(blob));
+    geometry.fRun = i;
+    geometry.fColor = color;
+    SkAutoTUnref<GrBatch> batch(BitmapTextBatch::Create(geometry, color, format,
+                                                        fContext->getBatchFontCache()));
+
+    target->drawBatch(pipelineBuilder, batch, &blob->fRuns[i].fVertexBounds);
+}
+
+void GrBitmapTextContextB::flush(GrDrawTarget* target, BitmapTextBlob* blob, GrRenderTarget* rt,
+                                 const GrPaint& paint, const GrClip& clip,
+                                 const SkMatrix& viewMatrix, int paintAlpha) {
+    GrPipelineBuilder pipelineBuilder;
+    pipelineBuilder.setFromPaint(paint, rt, clip);
+
+    GrColor color = paint.getColor();
+    for (int i = 0; i < blob->fRuns.count(); i++) {
+        this->flushSubRun(target, blob, i, &pipelineBuilder, kA8_GrMaskFormat, color, paintAlpha);
+        this->flushSubRun(target, blob, i, &pipelineBuilder, kA565_GrMaskFormat, color, paintAlpha);
+        this->flushSubRun(target, blob, i, &pipelineBuilder, kARGB_GrMaskFormat, color, paintAlpha);
+    }
+
+    // Now flush big glyphs
+    for (int i = 0; i < blob->fBigGlyphs.count(); i++) {
+        BitmapTextBlob::BigGlyph& bigGlyph = blob->fBigGlyphs[i];
+        SkMatrix translate;
+        translate.setTranslate(SkIntToScalar(bigGlyph.fVx), SkIntToScalar(bigGlyph.fVy));
+        SkPath tmpPath(bigGlyph.fPath);
+        tmpPath.transform(translate);
+        GrStrokeInfo strokeInfo(SkStrokeRec::kFill_InitStyle);
+        fContext->drawPath(rt, clip, paint, SkMatrix::I(), tmpPath, strokeInfo);
+    }
+}
+
 GrBitmapTextContext::GrBitmapTextContext(GrContext* context,
                                          SkGpuDevice* gpuDevice,
                                          const SkDeviceProperties& properties)
@@ -353,17 +1286,6 @@
     this->finish();
 }
 
-static size_t get_vertex_stride(GrMaskFormat maskFormat) {
-    switch (maskFormat) {
-        case kA8_GrMaskFormat:
-            return kGrayTextVASize;
-        case kARGB_GrMaskFormat:
-            return kColorTextVASize;
-        default:
-            return kLCDTextVASize;
-    }
-}
-
 static void* alloc_vertices(GrDrawTarget* drawTarget,
                             int numVertices,
                             GrMaskFormat maskFormat) {
@@ -387,33 +1309,33 @@
         if (fStrike->addGlyphToAtlas(glyph, scaler)) {
             return true;
         }
-        
+
         // try to clear out an unused plot before we flush
         if (fContext->getFontCache()->freeUnusedPlot(fStrike, glyph) &&
             fStrike->addGlyphToAtlas(glyph, scaler)) {
             return true;
         }
-        
+
         if (c_DumpFontCache) {
 #ifdef SK_DEVELOPER
             fContext->getFontCache()->dump();
 #endif
         }
-        
+
         // before we purge the cache, we must flush any accumulated draws
         this->flush();
         fContext->flush();
-        
+
         // we should have an unused plot now
         if (fContext->getFontCache()->freeUnusedPlot(fStrike, glyph) &&
             fStrike->addGlyphToAtlas(glyph, scaler)) {
             return true;
         }
-        
+
         // we should never get here
         SkASSERT(false);
     }
-    
+
     return false;
 }
 
@@ -639,4 +1561,3 @@
 
     GrTextContext::finish();
 }
-
diff --git a/src/gpu/GrBitmapTextContext.h b/src/gpu/GrBitmapTextContext.h
index f843fc7..3bf3a94 100644
--- a/src/gpu/GrBitmapTextContext.h
+++ b/src/gpu/GrBitmapTextContext.h
@@ -11,6 +11,127 @@
 #include "GrTextContext.h"
 
 #include "GrGeometryProcessor.h"
+#include "SkTHash.h"
+
+class GrBatchTextStrike;
+class GrPipelineBuilder;
+
+/*
+ * This class implements GrTextContext using standard bitmap fonts, and can also process textblobs.
+ * TODO replace GrBitmapTextContext
+ */
+class GrBitmapTextContextB : public GrTextContext {
+public:
+    static GrBitmapTextContextB* Create(GrContext*, SkGpuDevice*, const SkDeviceProperties&);
+
+    virtual ~GrBitmapTextContextB();
+
+private:
+    GrBitmapTextContextB(GrContext*, SkGpuDevice*, const SkDeviceProperties&);
+
+    bool canDraw(const GrRenderTarget*, const GrClip&, const GrPaint&,
+                 const SkPaint&, const SkMatrix& viewMatrix) override;
+
+    void onDrawText(GrRenderTarget*, const GrClip&, const GrPaint&, const SkPaint&,
+                    const SkMatrix& viewMatrix, const char text[], size_t byteLength,
+                    SkScalar x, SkScalar y, const SkIRect& regionClipBounds) override;
+    void onDrawPosText(GrRenderTarget*, const GrClip&, const GrPaint&, const SkPaint&,
+                       const SkMatrix& viewMatrix,
+                       const char text[], size_t byteLength,
+                       const SkScalar pos[], int scalarsPerPosition,
+                       const SkPoint& offset, const SkIRect& regionClipBounds) override;
+    void drawTextBlob(GrRenderTarget*, const GrClip&, const SkPaint&,
+                      const SkMatrix& viewMatrix, const SkTextBlob*, SkScalar x, SkScalar y,
+                      SkDrawFilter*, const SkIRect& clipBounds) override;
+
+    void init(GrRenderTarget*, const GrClip&, const GrPaint&, const SkPaint&,
+              const SkIRect& regionClipBounds);
+
+    /*
+     * A BitmapTextBlob contains a fully processed SkTextBlob, suitable for nearly immediate drawing
+     * on the GPU.  These are initially created with valid positions and colors, but invalid
+     * texture coordinates.  The BitmapTextBlob itself has a few Blob-wide properties, and also
+     * consists of a number of runs.  Runs inside a blob are flushed individually so they can be
+     * reordered.
+     *
+     * The only thing(aside from a memcopy) required to flush a BitmapTextBlob is to ensure that
+     * the GrAtlas will not evict anything the Blob needs.
+     * TODO this is currently a bug
+     */
+    struct BitmapTextBlob : public SkRefCnt {
+        // 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
+        // any evictions inside of the atlas, then we will simply regenerate Runs.  We could track
+        // this at a more fine grained level, but its not clear if this is worth it, as evictions
+        // should be fairly rare.
+        // One additional point, each run can contain glyphs with any of the three mask formats.
+        // We maintain separate arrays for each format type, and flush them separately.  In practice
+        // most of the time a run will have the same format type
+        struct Run {
+            Run() : fColor(GrColor_ILLEGAL) { fVertexBounds.setLargestInverted(); }
+            struct PerFormatInfo {
+                PerFormatInfo() : fAtlasGeneration(GrBatchAtlas::kInvalidAtlasGeneration) {}
+                SkTDArray<unsigned char> fVertices;
+                SkTDArray<GrGlyph::PackedID> fGlyphIDs;
+                uint64_t fAtlasGeneration;
+            };
+            SkAutoTUnref<const SkData> fDescriptor;
+            SkAutoTUnref<SkTypeface> fTypeface;
+            PerFormatInfo fInfos[kMaskFormatCount];
+            SkRect fVertexBounds;
+            GrColor fColor;
+        };
+        SkSTArray<1, Run, true> fRuns;
+        struct BigGlyph {
+            BigGlyph(const SkPath& path, int vx, int vy) : fPath(path), fVx(vx), fVy(vy) {}
+            SkPath fPath;
+            int fVx;
+            int fVy;
+        };
+        SkTArray<BigGlyph> fBigGlyphs;
+        SkTextBlob* fBlob;
+        SkMatrix fViewMatrix;
+        SkScalar fX;
+        SkScalar fY;
+        SkPaint::Style fStyle;
+
+        static uint32_t Hash(const uint32_t& key) {
+            return SkChecksum::Mix(key);
+        }
+    };
+
+    void appendGlyph(BitmapTextBlob*, int runIndex, GrGlyph::PackedID, int left, int top,
+                     GrFontScaler*, const SkIRect& clipRect);
+    void flushSubRun(GrDrawTarget*, BitmapTextBlob*, int i, GrPipelineBuilder*, GrMaskFormat,
+               GrColor color, int paintAlpha);
+    void flush(GrDrawTarget*, BitmapTextBlob*, GrRenderTarget*, const GrPaint&, const GrClip&,
+               const SkMatrix& viewMatrix, int paintAlpha);
+
+    void internalDrawText(BitmapTextBlob*, int runIndex, const SkPaint&,
+                          const SkMatrix& viewMatrix, const char text[], size_t byteLength,
+                          SkScalar x, SkScalar y, const SkIRect& clipRect);
+    void internalDrawPosText(BitmapTextBlob*, int runIndex, const SkPaint&,
+                             const SkMatrix& viewMatrix,
+                             const char text[], size_t byteLength,
+                             const SkScalar pos[], int scalarsPerPosition,
+                             const SkPoint& offset, const SkIRect& clipRect);
+
+    static inline bool MustRegenerateBlob(const BitmapTextBlob&, const SkPaint&,
+                                          const SkMatrix& viewMatrix, SkScalar x, SkScalar y);
+    void regenerateTextBlob(BitmapTextBlob* bmp, const SkPaint& skPaint, const SkMatrix& viewMatrix,
+                            const SkTextBlob* blob, SkScalar x, SkScalar y,
+                            SkDrawFilter* drawFilter, const SkIRect& clipRect);
+
+    GrBatchTextStrike* fCurrStrike;
+
+    // TODO use real cache
+    static void ClearCacheEntry(uint32_t key, BitmapTextBlob**);
+    SkTHashMap<uint32_t, BitmapTextBlob*, BitmapTextBlob::Hash> fCache;
+
+    friend class BitmapTextBatch;
+
+    typedef GrTextContext INHERITED;
+};
 
 class GrTextStrike;
 
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index 4c7f3ea..a1f1c25 100755
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -10,7 +10,9 @@
 
 #include "GrAARectRenderer.h"
 #include "GrBatch.h"
+#include "GrBatchFontCache.h"
 #include "GrBatchTarget.h"
+#include "GrBitmapTextContext.h"
 #include "GrBufferAllocPool.h"
 #include "GrDefaultGeoProcFactory.h"
 #include "GrFontCache.h"
@@ -94,6 +96,7 @@
     fPathRendererChain = NULL;
     fSoftwarePathRenderer = NULL;
     fResourceCache = NULL;
+    fBatchFontCache = NULL;
     fFontCache = NULL;
     fDrawBuffer = NULL;
     fDrawBufferVBAllocPool = NULL;
@@ -129,6 +132,10 @@
     fDidTestPMConversions = false;
 
     this->setupDrawBuffer();
+
+    // GrBatchFontCache will eventually replace GrFontCache
+    fBatchFontCache = SkNEW(GrBatchFontCache);
+    fBatchFontCache->init(this);
 }
 
 GrContext::~GrContext() {
@@ -143,6 +150,7 @@
     }
 
     SkDELETE(fResourceCache);
+    SkDELETE(fBatchFontCache);
     SkDELETE(fFontCache);
     SkDELETE(fDrawBuffer);
     SkDELETE(fDrawBufferVBAllocPool);
@@ -180,6 +188,7 @@
     fAARectRenderer->reset();
     fOvalRenderer->reset();
 
+    fBatchFontCache->freeAll();
     fFontCache->freeAll();
     fLayerCache->freeAll();
 }
@@ -198,6 +207,7 @@
     fAARectRenderer->reset();
     fOvalRenderer->reset();
 
+    fBatchFontCache->freeAll();
     fFontCache->freeAll();
     fLayerCache->freeAll();
     // a path renderer may be holding onto resources
@@ -226,8 +236,12 @@
         }
     } 
 
+#ifdef USE_BITMAP_TEXTBLOBS
+    return GrBitmapTextContextB::Create(this, gpuDevice, leakyProperties);
+#else
     return GrDistanceFieldTextContext::Create(this, gpuDevice, leakyProperties,
                                               enableDistanceFieldFonts);
+#endif
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/src/gpu/GrDistanceFieldTextContext.cpp b/src/gpu/GrDistanceFieldTextContext.cpp
index f0915a7..acab0cf 100755
--- a/src/gpu/GrDistanceFieldTextContext.cpp
+++ b/src/gpu/GrDistanceFieldTextContext.cpp
@@ -74,7 +74,11 @@
                                                                bool enable) {
     GrDistanceFieldTextContext* textContext = SkNEW_ARGS(GrDistanceFieldTextContext, 
                                                          (context, gpuDevice, props, enable));
+#ifdef USE_BITMAP_TEXTBLOBS
+    textContext->fFallbackTextContext = GrBitmapTextContextB::Create(context, gpuDevice, props);
+#else
     textContext->fFallbackTextContext = GrBitmapTextContext::Create(context, gpuDevice, props);
+#endif
 
     return textContext;
 }
diff --git a/src/gpu/GrFontAtlasSizes.h b/src/gpu/GrFontAtlasSizes.h
index d5c5e28..8a3091c 100644
--- a/src/gpu/GrFontAtlasSizes.h
+++ b/src/gpu/GrFontAtlasSizes.h
@@ -9,6 +9,30 @@
 #ifndef GrFontAtlasSizes_DEFINED
 #define GrFontAtlasSizes_DEFINED
 
+// For debugging atlas which evict all of the time
+//#define DEBUG_CONSTANT_EVICT
+#ifdef DEBUG_CONSTANT_EVICT
+#define GR_FONT_ATLAS_TEXTURE_WIDTH    256//1024
+#define GR_FONT_ATLAS_A8_TEXTURE_WIDTH 256//2048
+#define GR_FONT_ATLAS_TEXTURE_HEIGHT   256//2048
+
+#define GR_FONT_ATLAS_PLOT_WIDTH       256
+#define GR_FONT_ATLAS_A8_PLOT_WIDTH    256//512
+#define GR_FONT_ATLAS_PLOT_HEIGHT      256
+
+#define GR_FONT_ATLAS_NUM_PLOTS_X     (GR_FONT_ATLAS_TEXTURE_WIDTH / GR_FONT_ATLAS_PLOT_WIDTH)
+#define GR_FONT_ATLAS_A8_NUM_PLOTS_X  (GR_FONT_ATLAS_A8_TEXTURE_WIDTH / GR_FONT_ATLAS_A8_PLOT_WIDTH)
+#define GR_FONT_ATLAS_NUM_PLOTS_Y     (GR_FONT_ATLAS_TEXTURE_HEIGHT / GR_FONT_ATLAS_PLOT_HEIGHT)
+
+// one over width and height
+#define GR_FONT_ATLAS_RECIP_WIDTH      "0.00390625"//"0.0009765625"
+#define GR_FONT_ATLAS_A8_RECIP_WIDTH   "0.00390625"//"0.00048828125"
+#define GR_FONT_ATLAS_RECIP_HEIGHT     "0.00390625"//"0.00048828125"
+
+// 1/(3*width)
+// only used for distance fields, which are A8
+#define GR_FONT_ATLAS_LCD_DELTA        "0.001302083"//"0.000162760417"
+#else
 #define GR_FONT_ATLAS_TEXTURE_WIDTH    1024
 #define GR_FONT_ATLAS_A8_TEXTURE_WIDTH 2048
 #define GR_FONT_ATLAS_TEXTURE_HEIGHT   2048
@@ -29,5 +53,5 @@
 // 1/(3*width)
 // only used for distance fields, which are A8
 #define GR_FONT_ATLAS_LCD_DELTA        "0.000162760417"
-
+#endif
 #endif
diff --git a/src/gpu/GrGlyph.h b/src/gpu/GrGlyph.h
index 108f2f0..2d3e945 100644
--- a/src/gpu/GrGlyph.h
+++ b/src/gpu/GrGlyph.h
@@ -8,6 +8,7 @@
 #ifndef GrGlyph_DEFINED
 #define GrGlyph_DEFINED
 
+#include "GrBatchAtlas.h"
 #include "GrRect.h"
 #include "GrTypes.h"
 
@@ -30,14 +31,17 @@
     
     typedef uint32_t PackedID;
 
-    GrPlot*      fPlot;
-    SkPath*      fPath;
-    PackedID     fPackedID;
-    GrMaskFormat fMaskFormat;
-    GrIRect16    fBounds;
-    SkIPoint16   fAtlasLocation;
+    // TODO either plot or AtlasID will be valid, not both
+    GrBatchAtlas::AtlasID fID;
+    GrPlot*               fPlot;
+    SkPath*               fPath;
+    PackedID              fPackedID;
+    GrMaskFormat          fMaskFormat;
+    GrIRect16             fBounds;
+    SkIPoint16            fAtlasLocation;
 
     void init(GrGlyph::PackedID packed, const SkIRect& bounds, GrMaskFormat format) {
+        fID = GrBatchAtlas::kInvalidAtlasID;
         fPlot = NULL;
         fPath = NULL;
         fPackedID = packed;
diff --git a/src/gpu/GrStencilAndCoverTextContext.cpp b/src/gpu/GrStencilAndCoverTextContext.cpp
index 172a856..bf47abd 100644
--- a/src/gpu/GrStencilAndCoverTextContext.cpp
+++ b/src/gpu/GrStencilAndCoverTextContext.cpp
@@ -34,7 +34,11 @@
                                      const SkDeviceProperties& props) {
     GrStencilAndCoverTextContext* textContext = SkNEW_ARGS(GrStencilAndCoverTextContext,
                                                            (context, gpuDevice, props));
+#ifdef USE_BITMAP_TEXTBLOBS
+    textContext->fFallbackTextContext = GrBitmapTextContextB::Create(context, gpuDevice, props);
+#else
     textContext->fFallbackTextContext = GrBitmapTextContext::Create(context, gpuDevice, props);
+#endif
 
     return textContext;
 }
diff --git a/src/gpu/GrTextContext.h b/src/gpu/GrTextContext.h
index 0a4beae..54b3efd 100644
--- a/src/gpu/GrTextContext.h
+++ b/src/gpu/GrTextContext.h
@@ -23,6 +23,9 @@
 class SkGpuDevice;
 class SkTextBlob;
 
+// For testing textblobs on GPU.
+//#define USE_BITMAP_TEXTBLOBS
+
 /*
  * This class wraps the state for a single text render
  */
@@ -38,9 +41,9 @@
                      const char text[], size_t byteLength,
                      const SkScalar pos[], int scalarsPerPosition,
                      const SkPoint& offset, const SkIRect& clipBounds);
-    void drawTextBlob(GrRenderTarget*, const GrClip&, const SkPaint&,
-                      const SkMatrix& viewMatrix, const SkTextBlob*, SkScalar x, SkScalar y,
-                      SkDrawFilter*, const SkIRect& clipBounds);
+    virtual void drawTextBlob(GrRenderTarget*, const GrClip&, const SkPaint&,
+                              const SkMatrix& viewMatrix, const SkTextBlob*, SkScalar x, SkScalar y,
+                              SkDrawFilter*, const SkIRect& clipBounds);
 
 protected:
     GrTextContext*                 fFallbackTextContext;
@@ -90,6 +93,8 @@
     // sets extent in stopVector and returns glyph count
     static int MeasureText(SkGlyphCache* cache, SkDrawCacheProc glyphCacheProc,
                            const char text[], size_t byteLength, SkVector* stopVector);
+
+    friend class BitmapTextBatch;
 };
 
 #endif
diff --git a/src/gpu/SkGpuDevice.h b/src/gpu/SkGpuDevice.h
index 522cba8..8c3414b 100644
--- a/src/gpu/SkGpuDevice.h
+++ b/src/gpu/SkGpuDevice.h
@@ -219,6 +219,7 @@
     static GrRenderTarget* CreateRenderTarget(GrContext*, SkSurface::Budgeted, const SkImageInfo&,
                                               int sampleCount);
 
+    friend class GrBitmapTextContextB;
     friend class GrTextContext;
     typedef SkBaseDevice INHERITED;
 };