GPU Font Cache improvements:
- If a strike has multiple atlases, check all for room for a new glyph
- Mark remaining atlases unused after a purge, then check for an unused
atlas before purging (reduces TextContext flushes and ghosting)
- Hide Atlas management a little better inside AtlasMgr

R=robertphillips@google.com, bsalomon@google.com

Author: jvanverth@google.com

Review URL: https://chromiumcodereview.appspot.com/21594005

git-svn-id: http://skia.googlecode.com/svn/trunk@10544 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/gpu/GrTextStrike.cpp b/src/gpu/GrTextStrike.cpp
index 071c5d2..9373351 100644
--- a/src/gpu/GrTextStrike.cpp
+++ b/src/gpu/GrTextStrike.cpp
@@ -16,6 +16,11 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
+#define FONT_CACHE_STATS 0
+#if FONT_CACHE_STATS
+static int g_PurgeCount = 0;
+#endif
+
 GrFontCache::GrFontCache(GrGpu* gpu) : fGpu(gpu) {
     gpu->ref();
     fAtlasMgr = NULL;
@@ -27,6 +32,9 @@
     fCache.deleteAll();
     delete fAtlasMgr;
     fGpu->unref();
+#if FONT_CACHE_STATS
+      GrPrintf("Num purges: %d\n", g_PurgeCount);
+#endif
 }
 
 GrTextStrike* GrFontCache::generateStrike(GrFontScaler* scaler,
@@ -62,19 +70,51 @@
 
 void GrFontCache::purgeExceptFor(GrTextStrike* preserveStrike) {
     GrTextStrike* strike = fTail;
+    bool purge = true;
     while (strike) {
         if (strike == preserveStrike) {
             strike = strike->fPrev;
             continue;
         }
         GrTextStrike* strikeToPurge = strike;
-        // keep going if we won't free up any atlases with this strike.
-        strike = (NULL == strikeToPurge->fAtlas) ? strikeToPurge->fPrev : NULL;
-        int index = fCache.slowFindIndex(strikeToPurge);
-        GrAssert(index >= 0);
-        fCache.removeAt(index, strikeToPurge->fFontScalerKey->getHash());
-        this->detachStrikeFromList(strikeToPurge);
-        delete strikeToPurge;
+        strike = strikeToPurge->fPrev;
+        if (purge) {
+            // keep purging if we won't free up any atlases with this strike.
+            purge = (NULL == strikeToPurge->fAtlas);
+            int index = fCache.slowFindIndex(strikeToPurge);
+            GrAssert(index >= 0);
+            fCache.removeAt(index, strikeToPurge->fFontScalerKey->getHash());
+            this->detachStrikeFromList(strikeToPurge);
+            delete strikeToPurge;
+        } else {
+            // for the remaining strikes, we just mark them unused
+            GrAtlas::MarkAllUnused(strikeToPurge->fAtlas);
+        }
+    }
+#if FONT_CACHE_STATS
+    ++g_PurgeCount;
+#endif
+}
+
+void GrFontCache::freeAtlasExceptFor(GrTextStrike* preserveStrike) {
+    GrTextStrike* strike = fTail;
+    while (strike) {
+        if (strike == preserveStrike) {
+            strike = strike->fPrev;
+            continue;
+        }
+        GrTextStrike* strikeToPurge = strike;
+        strike = strikeToPurge->fPrev;
+        if (strikeToPurge->removeUnusedAtlases()) {
+            if (NULL == strikeToPurge->fAtlas) {
+                int index = fCache.slowFindIndex(strikeToPurge);
+                GrAssert(index >= 0);
+                fCache.removeAt(index, strikeToPurge->fFontScalerKey->getHash());
+                this->detachStrikeFromList(strikeToPurge);
+                delete strikeToPurge;
+            }
+            break;
+        }
     }
 }
 
@@ -140,12 +180,20 @@
 #endif
 }
 
-static void FreeGlyph(GrGlyph*& glyph) { glyph->free(); }
+// these signatures are needed because they're used with
+// SkTDArray::visitAll() (see destructor & removeUnusedAtlases())
+static void free_glyph(GrGlyph*& glyph) { glyph->free(); }
+
+static void invalidate_glyph(GrGlyph*& glyph) {
+    if (glyph->fAtlas && !glyph->fAtlas->used()) {
+        glyph->fAtlas = NULL;
+    }
+}
 
 GrTextStrike::~GrTextStrike() {
     GrAtlas::FreeLList(fAtlas);
     fFontScalerKey->unref();
-    fCache.getArray().visitAll(FreeGlyph);
+    fCache.getArray().visitAll(free_glyph);
 
 #if GR_DEBUG
     gCounter -= 1;
@@ -166,6 +214,13 @@
     return glyph;
 }
 
+bool GrTextStrike::removeUnusedAtlases() {
+    fCache.getArray().visitAll(invalidate_glyph);
+    return GrAtlas::RemoveUnusedAtlases(fAtlasMgr, &fAtlas);
+
+    return false;
+}
+
 bool GrTextStrike::getGlyphAtlas(GrGlyph* glyph, GrFontScaler* scaler) {
 #if 0   // testing hack to force us to flush our cache often
     static int gCounter;
@@ -176,6 +231,7 @@
     GrAssert(scaler);
     GrAssert(fCache.contains(glyph));
     if (glyph->fAtlas) {
+        glyph->fAtlas->setUsed(true);
         return true;
     }
 
@@ -191,7 +247,7 @@
         return false;
     }
 
-    GrAtlas* atlas = fAtlasMgr->addToAtlas(fAtlas, glyph->width(),
+    GrAtlas* atlas = fAtlasMgr->addToAtlas(&fAtlas, glyph->width(),
                                            glyph->height(), storage.get(),
                                            fMaskFormat,
                                            &glyph->fAtlasLocation);
@@ -199,7 +255,7 @@
         return false;
     }
 
-    // update fAtlas as well, since they may be chained in a linklist
-    glyph->fAtlas = fAtlas = atlas;
+    glyph->fAtlas = atlas;
+    atlas->setUsed(true);
     return true;
 }