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/GrAtlas.cpp b/src/gpu/GrAtlas.cpp
index c1d6d3d..f2daca1 100644
--- a/src/gpu/GrAtlas.cpp
+++ b/src/gpu/GrAtlas.cpp
@@ -44,9 +44,17 @@
     static int gCounter;
 #endif
 
+// for testing
+#define FONT_CACHE_STATS 0
+#if FONT_CACHE_STATS
+static int g_UploadCount = 0;
+#endif
+
 GrAtlas::GrAtlas(GrAtlasMgr* mgr, int plotX, int plotY, GrMaskFormat format) {
     fAtlasMgr = mgr;    // just a pointer, not an owner
     fNext = NULL;
+    fUsed = false;
+
     fTexture = mgr->getTexture(format); // we're not an owner, just a pointer
     fPlot.set(plotX, plotY);
 
@@ -62,7 +70,7 @@
 }
 
 GrAtlas::~GrAtlas() {
-    fAtlasMgr->freePlot(fPlot.fX, fPlot.fY);
+    fAtlasMgr->freePlot(fMaskFormat, fPlot.fX, fPlot.fY);
 
     delete fRects;
 
@@ -72,6 +80,27 @@
 #endif
 }
 
+bool GrAtlas::RemoveUnusedAtlases(GrAtlasMgr* atlasMgr, GrAtlas** startAtlas) {
+    // GrAtlas** is used so that a pointer to the head element can be passed in and
+    // modified when the first element is deleted
+    GrAtlas** atlasRef = startAtlas;
+    GrAtlas* atlas = *startAtlas;
+    bool removed = false;
+    while (NULL != atlas) {
+        if (!atlas->used()) {
+            *atlasRef = atlas->fNext;
+            atlasMgr->deleteAtlas(atlas);
+            atlas = *atlasRef;
+            removed = true;
+        } else {
+            atlasRef = &atlas->fNext;
+            atlas = atlas->fNext;
+        }
+    }
+
+    return removed;
+}
+
 static void adjustForPlot(GrIPoint16* loc, const GrIPoint16& plot) {
     loc->fX += plot.fX * GR_ATLAS_WIDTH;
     loc->fY += plot.fY * GR_ATLAS_HEIGHT;
@@ -122,6 +151,11 @@
     // now tell the caller to skip the top/left BORDER
     loc->fX += BORDER;
     loc->fY += BORDER;
+
+#if FONT_CACHE_STATS
+    ++g_UploadCount;
+#endif
+
     return true;
 }
 
@@ -139,7 +173,11 @@
         GrSafeUnref(fTexture[i]);
     }
     delete fPlotMgr;
+
     fGpu->unref();
+#if FONT_CACHE_STATS
+      GrPrintf("Num uploads: %d\n", g_UploadCount);
+#endif
 }
 
 static GrPixelConfig maskformat2pixelconfig(GrMaskFormat format) {
@@ -156,18 +194,23 @@
     return kUnknown_GrPixelConfig;
 }
 
-GrAtlas* GrAtlasMgr::addToAtlas(GrAtlas* atlas,
+GrAtlas* GrAtlasMgr::addToAtlas(GrAtlas** atlas,
                                 int width, int height, const void* image,
                                 GrMaskFormat format,
                                 GrIPoint16* loc) {
-    GrAssert(NULL == atlas || atlas->getMaskFormat() == format);
+    GrAssert(NULL == *atlas || (*atlas)->getMaskFormat() == format);
 
-    if (atlas && atlas->addSubImage(width, height, image, loc)) {
-        return atlas;
+    // iterate through entire atlas list, see if we can find a hole
+    GrAtlas* atlasIter = *atlas;
+    while (atlasIter) {
+        if (atlasIter->addSubImage(width, height, image, loc)) {
+            return atlasIter;
+        }
+        atlasIter = atlasIter->fNext;
     }
 
     // If the above fails, then either we have no starting atlas, or the current
-    // one is full. Either way we need to allocate a new atlas
+    // atlas list is full. Either way we need to allocate a new atlas
 
     GrIPoint16 plot;
     if (!fPlotMgr->newPlot(&plot)) {
@@ -196,11 +239,14 @@
         return NULL;
     }
 
-    newAtlas->fNext = atlas;
+    // new atlas, put at head
+    newAtlas->fNext = *atlas;
+    *atlas = newAtlas;
+
     return newAtlas;
 }
 
-void GrAtlasMgr::freePlot(int x, int y) {
+void GrAtlasMgr::freePlot(GrMaskFormat format, int x, int y) {
     GrAssert(fPlotMgr->isBusy(x, y));
     fPlotMgr->freePlot(x, y);
 }
diff --git a/src/gpu/GrAtlas.h b/src/gpu/GrAtlas.h
index 40a2154..b6f25c2 100644
--- a/src/gpu/GrAtlas.h
+++ b/src/gpu/GrAtlas.h
@@ -20,8 +20,6 @@
 
 class GrAtlas {
 public:
-    GrAtlas(GrAtlasMgr*, int plotX, int plotY, GrMaskFormat);
-
     int getPlotX() const { return fPlot.fX; }
     int getPlotY() const { return fPlot.fY; }
     GrMaskFormat getMaskFormat() const { return fMaskFormat; }
@@ -31,20 +29,34 @@
     bool addSubImage(int width, int height, const void*, GrIPoint16*);
 
     static void FreeLList(GrAtlas* atlas) {
-        while (atlas) {
+        while (NULL != atlas) {
             GrAtlas* next = atlas->fNext;
             delete atlas;
             atlas = next;
         }
     }
 
-    // testing
-    GrAtlas* nextAtlas() const { return fNext; }
+    static void MarkAllUnused(GrAtlas* atlas) {
+        while (NULL != atlas) {
+            atlas->fUsed = false;
+            atlas = atlas->fNext;
+        }
+    }
+
+    static bool RemoveUnusedAtlases(GrAtlasMgr* atlasMgr, GrAtlas** startAtlas);
+
+    bool used() const { return fUsed; }
+    void setUsed(bool used) { fUsed = used; }
 
 private:
+    GrAtlas(GrAtlasMgr*, int plotX, int plotY, GrMaskFormat format);
     ~GrAtlas(); // does not try to delete the fNext field
 
     GrAtlas*        fNext;
+
+    // for recycling
+    bool            fUsed;
+
     GrTexture*      fTexture;
     GrRectanizer*   fRects;
     GrAtlasMgr*     fAtlasMgr;
@@ -61,8 +73,9 @@
     GrAtlasMgr(GrGpu*);
     ~GrAtlasMgr();
 
-    GrAtlas* addToAtlas(GrAtlas*, int width, int height, const void*,
+    GrAtlas* addToAtlas(GrAtlas**, int width, int height, const void*,
                         GrMaskFormat, GrIPoint16*);
+    void deleteAtlas(GrAtlas* atlas) { delete atlas; }
 
     GrTexture* getTexture(GrMaskFormat format) const {
         GrAssert((unsigned)format < kCount_GrMaskFormats);
@@ -70,7 +83,7 @@
     }
 
     // to be called by ~GrAtlas()
-    void freePlot(int x, int y);
+    void freePlot(GrMaskFormat format, int x, int y);
 
 private:
     GrGpu*      fGpu;
diff --git a/src/gpu/GrTextContext.cpp b/src/gpu/GrTextContext.cpp
index 5af0851..8f0f1cf 100644
--- a/src/gpu/GrTextContext.cpp
+++ b/src/gpu/GrTextContext.cpp
@@ -153,6 +153,12 @@
             goto HAS_ATLAS;
         }
 
+        // try to clear out an unused atlas before we flush
+        fContext->getFontCache()->freeAtlasExceptFor(fStrike);
+        if (fStrike->getGlyphAtlas(glyph, scaler)) {
+            goto HAS_ATLAS;
+        }
+
         // before we purge the cache, we must flush any accumulated draws
         this->flushGlyphs();
         fContext->flush();
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;
 }
diff --git a/src/gpu/GrTextStrike.h b/src/gpu/GrTextStrike.h
index 25ced6d..e359e26 100644
--- a/src/gpu/GrTextStrike.h
+++ b/src/gpu/GrTextStrike.h
@@ -46,6 +46,9 @@
     }
     GrAtlas* getAtlas() const { return fAtlas; }
 
+    // returns true if an atlas was removed
+    bool removeUnusedAtlases();
+
 public:
     // for LRU
     GrTextStrike*   fPrev;
@@ -81,6 +84,9 @@
 
     void purgeExceptFor(GrTextStrike*);
 
+    // remove an unused atlas and its strike (if necessary)
+    void freeAtlasExceptFor(GrTextStrike*);
+
     // testing
     int countStrikes() const { return fCache.getArray().count(); }
     const GrTextStrike* strikeAt(int index) const {