Add CPU backing store for GrAtlas to reduce texture uploads.

This change creates a temporary copy of each GrPlot on the CPU
side. As we add glyphs to a plot, a dirty rectangle is tracked,
and just before we render text we upload all of the dirty areas
to the atlas texture. Once a plot is nearly full, we assume that
we may only be adding one or two glyphs before rendering, so we
delete the CPU backing memory to save space, and upload directly.

BUG=366225
R=robertphillips@google.com

Author: jvanverth@google.com

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

git-svn-id: http://skia.googlecode.com/svn/trunk@14729 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/gpu/GrAtlas.cpp b/src/gpu/GrAtlas.cpp
index 008bae2..c4e1a3b 100644
--- a/src/gpu/GrAtlas.cpp
+++ b/src/gpu/GrAtlas.cpp
@@ -24,19 +24,28 @@
                  , fRects(NULL)
                  , fAtlasMgr(NULL)
                  , fBytesPerPixel(1)
+                 , fDirty(false)
+                 , fBatchUploads(false)
 {
     fOffset.set(0, 0);
 }
 
 GrPlot::~GrPlot() {
+    SkDELETE_ARRAY(fPlotData);
+    fPlotData = NULL;
     delete fRects;
 }
 
-void GrPlot::init(GrAtlasMgr* mgr, int offX, int offY, int width, int height, size_t bpp) {
+void GrPlot::init(GrAtlasMgr* mgr, int offX, int offY, int width, int height, size_t bpp,
+                  bool batchUploads) {
     fRects = GrRectanizer::Factory(width, height);
     fAtlasMgr = mgr;
     fOffset.set(offX * width, offY * height);
     fBytesPerPixel = bpp;
+    fPlotData = NULL;
+    fDirtyRect.setEmpty();
+    fDirty = false;
+    fBatchUploads = batchUploads;
 }
 
 static inline void adjust_for_offset(GrIPoint16* loc, const GrIPoint16& offset) {
@@ -46,20 +55,46 @@
 
 bool GrPlot::addSubImage(int width, int height, const void* image,
                           GrIPoint16* loc) {
+    float percentFull = fRects->percentFull();
     if (!fRects->addRect(width, height, loc)) {
         return false;
     }
 
-    SkAutoSMalloc<1024> storage;
-    adjust_for_offset(loc, fOffset);
-    GrContext* context = fTexture->getContext();
-    // We pass the flag that does not force a flush. We assume our caller is
-    // smart and hasn't referenced the part of the texture we're about to update
-    // since the last flush.
-    context->writeTexturePixels(fTexture,
-                                loc->fX, loc->fY, width, height,
-                                fTexture->config(), image, 0,
-                                GrContext::kDontFlush_PixelOpsFlag);
+    // if batching uploads, create backing memory on first use
+    // once the plot is nearly full we will revert to uploading each subimage individually
+    int plotWidth = fRects->width();
+    int plotHeight = fRects->height();
+    if (fBatchUploads && NULL == fPlotData && 0.0f == percentFull) {
+        fPlotData = SkNEW_ARRAY(unsigned char, fBytesPerPixel*plotWidth*plotHeight);
+        memset(fPlotData, 0, fBytesPerPixel*plotWidth*plotHeight);
+    }
+
+    // if we have backing memory, copy to the memory and set for future upload
+    if (NULL != fPlotData) {
+        const unsigned char* imagePtr = (const unsigned char*) image;
+        // point ourselves at the right starting spot
+        unsigned char* dataPtr = fPlotData;
+        dataPtr += fBytesPerPixel*plotWidth*loc->fY;
+        dataPtr += fBytesPerPixel*loc->fX;
+        // copy into the data buffer
+        for (int i = 0; i < height; ++i) {
+            memcpy(dataPtr, imagePtr, fBytesPerPixel*width);
+            dataPtr += fBytesPerPixel*plotWidth;
+            imagePtr += fBytesPerPixel*width;
+        }
+
+        fDirtyRect.join(loc->fX, loc->fY, loc->fX + width, loc->fY + height);
+        adjust_for_offset(loc, fOffset);
+        fDirty = true;
+    // otherwise, just upload the image directly
+    } else {
+        adjust_for_offset(loc, fOffset);
+        GrContext* context = fTexture->getContext();
+        context->writeTexturePixels(fTexture,
+                                    loc->fX, loc->fY, width, height,
+                                    fTexture->config(), image, 0,
+                                    GrContext::kDontFlush_PixelOpsFlag);
+    }
 
 #if FONT_CACHE_STATS
     ++g_UploadCount;
@@ -68,6 +103,39 @@
     return true;
 }
 
+void GrPlot::uploadToTexture() {
+    static const float kNearlyFullTolerance = 0.85f;
+
+    // should only do this if batching is enabled
+    SkASSERT(fBatchUploads);
+
+    if (fDirty) {
+        SkASSERT(NULL != fTexture);
+        GrContext* context = fTexture->getContext();
+        // We pass the flag that does not force a flush. We assume our caller is
+        // smart and hasn't referenced the part of the texture we're about to update
+        // since the last flush.
+        int rowBytes = fBytesPerPixel*fRects->width();
+        const unsigned char* dataPtr = fPlotData;
+        dataPtr += rowBytes*fDirtyRect.fTop;
+        dataPtr += fBytesPerPixel*fDirtyRect.fLeft;
+        context->writeTexturePixels(fTexture,
+                                    fOffset.fX + fDirtyRect.fLeft, fOffset.fY + fDirtyRect.fTop, 
+                                    fDirtyRect.width(), fDirtyRect.height(),
+                                    fTexture->config(), dataPtr, 
+                                    rowBytes,
+                                    GrContext::kDontFlush_PixelOpsFlag);
+        fDirtyRect.setEmpty();
+        fDirty = false;
+        // If the Plot is nearly full, anything else we add will probably be small and one
+        // at a time, so free up the memory and after this upload any new images directly.
+        if (fRects->percentFull() > kNearlyFullTolerance) {
+            SkDELETE_ARRAY(fPlotData);
+            fPlotData = NULL;
+        }
+    }
+}
+
 void GrPlot::resetRects() {
     SkASSERT(NULL != fRects);
     fRects->reset();
@@ -77,19 +145,23 @@
 
 GrAtlasMgr::GrAtlasMgr(GrGpu* gpu, GrPixelConfig config,
                        const SkISize& backingTextureSize,
-                       int numPlotsX, int numPlotsY) {
+                       int numPlotsX, int numPlotsY, bool batchUploads) {
     fGpu = SkRef(gpu);
     fPixelConfig = config;
     fBackingTextureSize = backingTextureSize;
     fNumPlotsX = numPlotsX;
     fNumPlotsY = numPlotsY;
+    fBatchUploads = batchUploads;
     fTexture = NULL;
 
-    int plotWidth = fBackingTextureSize.width() / fNumPlotsX;
-    int plotHeight = fBackingTextureSize.height() / fNumPlotsY;
+    int textureWidth = fBackingTextureSize.width();
+    int textureHeight = fBackingTextureSize.height();
 
-    SkASSERT(plotWidth * fNumPlotsX == fBackingTextureSize.width());
-    SkASSERT(plotHeight * fNumPlotsY == fBackingTextureSize.height());
+    int plotWidth = textureWidth / fNumPlotsX;
+    int plotHeight = textureHeight / fNumPlotsY;
+
+    SkASSERT(plotWidth * fNumPlotsX == textureWidth);
+    SkASSERT(plotHeight * fNumPlotsY == textureHeight);
 
     // set up allocated plots
     size_t bpp = GrBytesPerPixel(fPixelConfig);
@@ -98,7 +170,7 @@
     GrPlot* currPlot = fPlotArray;
     for (int y = numPlotsY-1; y >= 0; --y) {
         for (int x = numPlotsX-1; x >= 0; --x) {
-            currPlot->init(this, x, y, plotWidth, plotHeight, bpp);
+            currPlot->init(this, x, y, plotWidth, plotHeight, bpp, batchUploads);
 
             // build LRU list
             fPlotList.addToHead(currPlot);
@@ -201,3 +273,15 @@
 
     return NULL;
 }
+
+void GrAtlasMgr::uploadPlotsToTexture() {
+    if (fBatchUploads) {
+        GrPlotList::Iter plotIter;
+        plotIter.init(fPlotList, GrPlotList::Iter::kHead_IterStart);
+        GrPlot* plot;
+        while (NULL != (plot = plotIter.get())) {
+            plot->uploadToTexture();
+            plotIter.next();
+        }
+    }
+}
diff --git a/src/gpu/GrAtlas.h b/src/gpu/GrAtlas.h
index d7f5334..b3affe2 100644
--- a/src/gpu/GrAtlas.h
+++ b/src/gpu/GrAtlas.h
@@ -40,21 +40,28 @@
     GrDrawTarget::DrawToken drawToken() const { return fDrawToken; }
     void setDrawToken(GrDrawTarget::DrawToken draw) { fDrawToken = draw; }
 
+    void uploadToTexture();
+
     void resetRects();
 
 private:
     GrPlot();
     ~GrPlot(); // does not try to delete the fNext field
-    void init(GrAtlasMgr* mgr, int offX, int offY, int width, int height, size_t bpp);
+    void init(GrAtlasMgr* mgr, int offX, int offY, int width, int height, size_t bpp,
+              bool batchUploads);
 
     // for recycling
     GrDrawTarget::DrawToken fDrawToken;
 
+    unsigned char*          fPlotData;
     GrTexture*              fTexture;
     GrRectanizer*           fRects;
     GrAtlasMgr*             fAtlasMgr;
     GrIPoint16              fOffset;        // the offset of the plot in the backing texture
     size_t                  fBytesPerPixel;
+    SkIRect                 fDirtyRect;
+    bool                    fDirty;
+    bool                    fBatchUploads;
 
     friend class GrAtlasMgr;
 };
@@ -64,7 +71,7 @@
 class GrAtlasMgr {
 public:
     GrAtlasMgr(GrGpu*, GrPixelConfig, const SkISize& backingTextureSize,
-               int numPlotsX, int numPlotsY);
+               int numPlotsX, int numPlotsY, bool batchUploads);
     ~GrAtlasMgr();
 
     // add subimage of width, height dimensions to atlas
@@ -82,6 +89,8 @@
         return fTexture;
     }
 
+    void uploadPlotsToTexture();
+
 private:
     void moveToHead(GrPlot* plot);
 
@@ -91,6 +100,7 @@
     SkISize       fBackingTextureSize;
     int           fNumPlotsX;
     int           fNumPlotsY;
+    bool          fBatchUploads;
 
     // allocated array of GrPlots
     GrPlot*       fPlotArray;
diff --git a/src/gpu/GrBitmapTextContext.cpp b/src/gpu/GrBitmapTextContext.cpp
index 3b5e27a..744d97f 100755
--- a/src/gpu/GrBitmapTextContext.cpp
+++ b/src/gpu/GrBitmapTextContext.cpp
@@ -68,6 +68,8 @@
     drawState->setFromPaint(fPaint, SkMatrix::I(), fContext->getRenderTarget());
 
     if (fCurrVertex > 0) {
+        fContext->getFontCache()->updateTextures();
+
         // setup our sampler state for our text texture/atlas
         SkASSERT(SkIsAlign4(fCurrVertex));
         SkASSERT(fCurrTexture);
diff --git a/src/gpu/GrBitmapTextContext.h b/src/gpu/GrBitmapTextContext.h
index 18f5cc0..23248eb 100644
--- a/src/gpu/GrBitmapTextContext.h
+++ b/src/gpu/GrBitmapTextContext.h
@@ -11,6 +11,7 @@
 #include "GrTextContext.h"
 
 class GrTextStrike;
+class GrAtlasMgr;
 
 /*
  * This class implements GrTextContext using standard bitmap fonts
diff --git a/src/gpu/GrDistanceFieldTextContext.cpp b/src/gpu/GrDistanceFieldTextContext.cpp
index 512420e..afc2a47 100755
--- a/src/gpu/GrDistanceFieldTextContext.cpp
+++ b/src/gpu/GrDistanceFieldTextContext.cpp
@@ -101,6 +101,8 @@
     drawState->setFromPaint(fPaint, fContext->getMatrix(), fContext->getRenderTarget());
 
     if (fCurrVertex > 0) {
+        fContext->getFontCache()->updateTextures();
+
         // setup our sampler state for our text texture/atlas
         SkASSERT(SkIsAlign4(fCurrVertex));
         SkASSERT(fCurrTexture);
diff --git a/src/gpu/GrLayerCache.cpp b/src/gpu/GrLayerCache.cpp
index d1346c5..0621aac 100644
--- a/src/gpu/GrLayerCache.cpp
+++ b/src/gpu/GrLayerCache.cpp
@@ -58,7 +58,7 @@
     // The layer cache only gets 1 plot
     SkISize textureSize = SkISize::Make(kAtlasTextureWidth, kAtlasTextureHeight);
     fAtlasMgr.reset(SkNEW_ARGS(GrAtlasMgr, (fGpu, kSkia8888_GrPixelConfig,
-                                            textureSize, 1, 1)));
+                                            textureSize, 1, 1, false)));
 }
 
 void GrLayerCache::freeAll() {
diff --git a/src/gpu/GrTextStrike.cpp b/src/gpu/GrTextStrike.cpp
index 57263d4..b03d14f 100644
--- a/src/gpu/GrTextStrike.cpp
+++ b/src/gpu/GrTextStrike.cpp
@@ -5,7 +5,6 @@
  * found in the LICENSE file.
  */
 
-#include "GrAtlas.h"
 #include "GrGpu.h"
 #include "GrRectanizer.h"
 #include "GrTextStrike.h"
@@ -86,7 +85,8 @@
         fAtlasMgr[atlasIndex] = SkNEW_ARGS(GrAtlasMgr, (fGpu, config,
                                                         textureSize,
                                                         GR_NUM_PLOTS_X,
-                                                        GR_NUM_PLOTS_Y));
+                                                        GR_NUM_PLOTS_Y,
+                                                        true));
     }
     GrTextStrike* strike = SkNEW_ARGS(GrTextStrike,
                                       (this, scaler->getKey(), format, fAtlasMgr[atlasIndex]));
diff --git a/src/gpu/GrTextStrike.h b/src/gpu/GrTextStrike.h
index 0355405..f71b82c 100644
--- a/src/gpu/GrTextStrike.h
+++ b/src/gpu/GrTextStrike.h
@@ -90,6 +90,14 @@
     }
     GrTextStrike* getHeadStrike() const { return fHead; }
 
+    void updateTextures() {
+        for (int i = 0; i < kAtlasCount; ++i) {
+            if (fAtlasMgr[i]) {
+                fAtlasMgr[i]->uploadPlotsToTexture();
+            }
+        }
+    }
+
 #ifdef SK_DEBUG
     void validate() const;
 #else