Add support for additional textures in GrDrawOpAtlas

Step two in supporting growable/shrinkable atlases.

Bug: skia:3550
Change-Id: I0cdec2a9f59cc8ced071bfeec2f8ed5a228c4b7a
Reviewed-on: https://skia-review.googlesource.com/43260
Commit-Queue: Jim Van Verth <jvanverth@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/src/gpu/GrDrawOpAtlas.cpp b/src/gpu/GrDrawOpAtlas.cpp
index c962a61..bdd07e7 100644
--- a/src/gpu/GrDrawOpAtlas.cpp
+++ b/src/gpu/GrDrawOpAtlas.cpp
@@ -21,7 +21,7 @@
                                                    void* data) {
     std::unique_ptr<GrDrawOpAtlas> atlas(
             new GrDrawOpAtlas(ctx, config, width, height, numPlotsX, numPlotsY));
-    if (!atlas->getProxy()) {
+    if (!atlas->getProxies()[0]) {
         return nullptr;
     }
 
@@ -32,13 +32,14 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-GrDrawOpAtlas::Plot::Plot(int index, uint64_t genID, int offX, int offY, int width, int height,
-                          GrPixelConfig config)
+GrDrawOpAtlas::Plot::Plot(int pageIndex, int plotIndex, uint64_t genID, int offX, int offY,
+                          int width, int height, GrPixelConfig config)
         : fLastUpload(GrDrawOpUploadToken::AlreadyFlushedToken())
         , fLastUse(GrDrawOpUploadToken::AlreadyFlushedToken())
-        , fIndex(index)
+        , fPageIndex(pageIndex)
+        , fPlotIndex(plotIndex)
         , fGenID(genID)
-        , fID(CreateId(fIndex, fGenID))
+        , fID(CreateId(fPageIndex, fPlotIndex, fGenID))
         , fData(nullptr)
         , fWidth(width)
         , fHeight(height)
@@ -126,7 +127,7 @@
     }
 
     fGenID++;
-    fID = CreateId(fIndex, fGenID);
+    fID = CreateId(fPageIndex, fPlotIndex, fGenID);
 
     // zero out the plot
     if (fData) {
@@ -142,7 +143,6 @@
 GrDrawOpAtlas::GrDrawOpAtlas(GrContext* context, GrPixelConfig config, int width, int height,
                              int numPlotsX, int numPlotsY)
         : fContext(context)
-        , fProxy(nullptr)
         , fPixelConfig(config)
         , fTextureWidth(width)
         , fTextureHeight(height)
@@ -166,7 +166,8 @@
         // should receive special attention.
         // Note: When switching over to the deferred proxy, use the kExact flag to create
         // the atlas and assert that the width & height are powers of 2.
-        fProxy = GrSurfaceProxy::MakeWrapped(std::move(texture), kTopLeft_GrSurfaceOrigin);
+        fProxies[0] = GrSurfaceProxy::MakeWrapped(std::move(texture),
+                                                       kTopLeft_GrSurfaceOrigin);
     }
 
     fPlotWidth = fTextureWidth / numPlotsX;
@@ -176,19 +177,20 @@
     SkASSERT(fPlotHeight * numPlotsY == fTextureHeight);
 
     SkDEBUGCODE(fNumPlots = numPlotsX * numPlotsY;)
+    SkDEBUGCODE(fNumPages = 1;)
 
     // set up allocated plots
-    fPlotArray.reset(new sk_sp<Plot>[ numPlotsX * numPlotsY ]);
+    fPages[0].fPlotArray.reset(new sk_sp<Plot>[ numPlotsX * numPlotsY ]);
 
-    sk_sp<Plot>* currPlot = fPlotArray.get();
+    sk_sp<Plot>* currPlot = fPages[0].fPlotArray.get();
     for (int y = numPlotsY - 1, r = 0; y >= 0; --y, ++r) {
         for (int x = numPlotsX - 1, c = 0; x >= 0; --x, ++c) {
             uint32_t index = r * numPlotsX + c;
             currPlot->reset(
-                    new Plot(index, 1, x, y, fPlotWidth, fPlotHeight, fPixelConfig));
+                    new Plot(0, index, 1, x, y, fPlotWidth, fPlotHeight, fPixelConfig));
 
             // build LRU list
-            fPlotList.addToHead(currPlot->get());
+            fPages[0].fPlotList.addToHead(currPlot->get());
             ++currPlot;
         }
     }
@@ -201,7 +203,8 @@
 }
 
 inline bool GrDrawOpAtlas::updatePlot(GrDrawOp::Target* target, AtlasID* id, Plot* plot) {
-    this->makeMRU(plot);
+    int pageIdx = GetPageIndexFromID(plot->id());
+    this->makeMRU(plot, pageIdx);
 
     // If our most recent upload has already occurred then we have to insert a new
     // upload. Otherwise, we already have a scheduled upload that hasn't yet ocurred.
@@ -212,11 +215,11 @@
 
         // MDB TODO: this is currently fine since the atlas' proxy is always pre-instantiated.
         // Once it is deferred more care must be taken upon instantiation failure.
-        if (!fProxy->instantiate(fContext->resourceProvider())) {
+        if (!fProxies[pageIdx]->instantiate(fContext->resourceProvider())) {
             return false;
         }
 
-        GrTextureProxy* proxy = fProxy.get();
+        GrTextureProxy* proxy = fProxies[pageIdx].get();
 
         GrDrawOpUploadToken lastUploadToken = target->addAsapUpload(
             [plotsp, proxy] (GrDrawOp::WritePixelsFn& writePixels) {
@@ -231,18 +234,21 @@
 
 bool GrDrawOpAtlas::addToAtlas(AtlasID* id, GrDrawOp::Target* target, int width, int height,
                                const void* image, SkIPoint16* loc) {
+    // Eventually we will iterate through these, for now just use the one.
+    int pageIdx = 0;
+
     // We should already have a texture, TODO clean this up
-    SkASSERT(fProxy);
+    SkASSERT(fProxies[pageIdx]);
     if (width > fPlotWidth || height > fPlotHeight) {
         return false;
     }
 
     // now look through all allocated plots for one we can share, in Most Recently Refed order
     PlotList::Iter plotIter;
-    plotIter.init(fPlotList, PlotList::Iter::kHead_IterStart);
+    plotIter.init(fPages[pageIdx].fPlotList, PlotList::Iter::kHead_IterStart);
     Plot* plot;
     while ((plot = plotIter.get())) {
-        SkASSERT(GrBytesPerPixel(fProxy->config()) == plot->bpp());
+        SkASSERT(GrBytesPerPixel(fProxies[pageIdx]->config()) == plot->bpp());
         if (plot->addSubImage(width, height, image, loc)) {
             return this->updatePlot(target, id, plot);
         }
@@ -251,12 +257,12 @@
 
     // If the above fails, then see if the least recently refed plot has already been flushed to the
     // gpu
-    plot = fPlotList.tail();
+    plot = fPages[pageIdx].fPlotList.tail();
     SkASSERT(plot);
     if (target->hasDrawBeenFlushed(plot->lastUseToken())) {
         this->processEviction(plot->id());
         plot->resetRects();
-        SkASSERT(GrBytesPerPixel(fProxy->config()) == plot->bpp());
+        SkASSERT(GrBytesPerPixel(fProxies[pageIdx]->config()) == plot->bpp());
         SkDEBUGCODE(bool verify = )plot->addSubImage(width, height, image, loc);
         SkASSERT(verify);
         if (!this->updatePlot(target, id, plot)) {
@@ -267,6 +273,8 @@
         return true;
     }
 
+    // TODO: at this point try to create a new page and add to it before evicting
+
     // If this plot has been used in a draw that is currently being prepared by an op, then we have
     // to fail. This gives the op a chance to enqueue the draw, and call back into this function.
     // When that draw is enqueued, the draw token advances, and the subsequent call will continue
@@ -277,12 +285,12 @@
     }
 
     this->processEviction(plot->id());
-    fPlotList.remove(plot);
-    sk_sp<Plot>& newPlot = fPlotArray[plot->index()];
+    fPages[pageIdx].fPlotList.remove(plot);
+    sk_sp<Plot>& newPlot = fPages[pageIdx].fPlotArray[plot->index()];
     newPlot.reset(plot->clone());
 
-    fPlotList.addToHead(newPlot.get());
-    SkASSERT(GrBytesPerPixel(fProxy->config()) == newPlot->bpp());
+    fPages[pageIdx].fPlotList.addToHead(newPlot.get());
+    SkASSERT(GrBytesPerPixel(fProxies[pageIdx]->config()) == newPlot->bpp());
     SkDEBUGCODE(bool verify = )newPlot->addSubImage(width, height, image, loc);
     SkASSERT(verify);
 
@@ -292,10 +300,10 @@
     sk_sp<Plot> plotsp(SkRef(newPlot.get()));
     // MDB TODO: this is currently fine since the atlas' proxy is always pre-instantiated.
     // Once it is deferred more care must be taken upon instantiation failure.
-    if (!fProxy->instantiate(fContext->resourceProvider())) {
+    if (!fProxies[pageIdx]->instantiate(fContext->resourceProvider())) {
         return false;
     }
-    GrTextureProxy* proxy = fProxy.get();
+    GrTextureProxy* proxy = fProxies[pageIdx].get();
 
     GrDrawOpUploadToken lastUploadToken = target->addInlineUpload(
         [plotsp, proxy] (GrDrawOp::WritePixelsFn& writePixels) {
diff --git a/src/gpu/GrDrawOpAtlas.h b/src/gpu/GrDrawOpAtlas.h
index 03eb808..6d12973 100644
--- a/src/gpu/GrDrawOpAtlas.h
+++ b/src/gpu/GrDrawOpAtlas.h
@@ -89,23 +89,28 @@
                     SkIPoint16* loc);
 
     GrContext* context() const { return fContext; }
-    sk_sp<GrTextureProxy> getProxy() const { return fProxy; }
+    const sk_sp<GrTextureProxy>* getProxies() const { return fProxies; }
 
     uint64_t atlasGeneration() const { return fAtlasGeneration; }
 
     inline bool hasID(AtlasID id) {
-        uint32_t index = GetIndexFromID(id);
-        SkASSERT(index < fNumPlots);
-        return fPlotArray[index]->genID() == GetGenerationFromID(id);
+        uint32_t plot = GetPlotIndexFromID(id);
+        SkASSERT(plot < fNumPlots);
+        uint32_t page = GetPageIndexFromID(id);
+        SkASSERT(page < fNumPages);
+        return fPages[page].fPlotArray[plot]->genID() == GetGenerationFromID(id);
     }
 
     /** To ensure the atlas does not evict a given entry, the client must set the last use token. */
     inline void setLastUseToken(AtlasID id, GrDrawOpUploadToken token) {
         SkASSERT(this->hasID(id));
-        uint32_t index = GetIndexFromID(id);
-        SkASSERT(index < fNumPlots);
-        this->makeMRU(fPlotArray[index].get());
-        fPlotArray[index]->setLastUseToken(token);
+        uint32_t plotIdx = GetPlotIndexFromID(id);
+        SkASSERT(plotIdx < fNumPlots);
+        uint32_t pageIdx = GetPageIndexFromID(id);
+        SkASSERT(pageIdx < fNumPages);
+        Plot* plot = fPages[pageIdx].fPlotArray[plotIdx].get();
+        this->makeMRU(plot, pageIdx);
+        plot->setLastUseToken(token);
     }
 
     inline void registerEvictionCallback(EvictionFunc func, void* userData) {
@@ -114,47 +119,58 @@
         data->fData = userData;
     }
 
+    static constexpr auto kMaxPages = 4;
+
     /**
      * A class which can be handed back to GrDrawOpAtlas for updating last use tokens in bulk.  The
-     * current max number of plots the GrDrawOpAtlas can handle is 32. If in the future this is
-     * insufficient then we can move to a 64 bit int.
+     * current max number of plots per page the GrDrawOpAtlas can handle is 32. If in the future
+     * this is insufficient then we can move to a 64 bit int.
      */
     class BulkUseTokenUpdater {
     public:
-        BulkUseTokenUpdater() : fPlotAlreadyUpdated(0) {}
+        BulkUseTokenUpdater() {
+            memset(fPlotAlreadyUpdated, 0, sizeof(fPlotAlreadyUpdated));
+        }
         BulkUseTokenUpdater(const BulkUseTokenUpdater& that)
-            : fPlotsToUpdate(that.fPlotsToUpdate)
-            , fPlotAlreadyUpdated(that.fPlotAlreadyUpdated) {
+            : fPlotsToUpdate(that.fPlotsToUpdate) {
+            memcpy(fPlotAlreadyUpdated, that.fPlotAlreadyUpdated, sizeof(fPlotAlreadyUpdated));
         }
 
         void add(AtlasID id) {
-            int index = GrDrawOpAtlas::GetIndexFromID(id);
-            if (!this->find(index)) {
-                this->set(index);
+            int index = GrDrawOpAtlas::GetPlotIndexFromID(id);
+            int pageIdx = GrDrawOpAtlas::GetPageIndexFromID(id);
+            if (!this->find(pageIdx, index)) {
+                this->set(pageIdx, index);
             }
         }
 
         void reset() {
             fPlotsToUpdate.reset();
-            fPlotAlreadyUpdated = 0;
+            memset(fPlotAlreadyUpdated, 0, sizeof(fPlotAlreadyUpdated));
         }
 
+        struct PlotData {
+            PlotData(int pageIdx, int plotIdx) : fPageIndex(pageIdx), fPlotIndex(plotIdx) {}
+            uint32_t fPageIndex;
+            uint32_t fPlotIndex;
+        };
+
     private:
-        bool find(int index) const {
+        bool find(int pageIdx, int index) const {
             SkASSERT(index < kMaxPlots);
-            return (fPlotAlreadyUpdated >> index) & 1;
+            return (fPlotAlreadyUpdated[pageIdx] >> index) & 1;
         }
 
-        void set(int index) {
-            SkASSERT(!this->find(index));
-            fPlotAlreadyUpdated = fPlotAlreadyUpdated | (1 << index);
-            fPlotsToUpdate.push_back(index);
+        void set(int pageIdx, int index) {
+            SkASSERT(!this->find(pageIdx, index));
+            fPlotAlreadyUpdated[pageIdx] |= (1 << index);
+            fPlotsToUpdate.push_back(PlotData(pageIdx, index));
         }
 
-        static const int kMinItems = 4;
-        static const int kMaxPlots = 32;
-        SkSTArray<kMinItems, int, true> fPlotsToUpdate;
-        uint32_t fPlotAlreadyUpdated;
+        static constexpr int kMinItems = 4;
+        static constexpr int kMaxPlots = 32;
+        SkSTArray<kMinItems, PlotData, true> fPlotsToUpdate;
+        uint32_t fPlotAlreadyUpdated[kMaxPages];
 
         friend class GrDrawOpAtlas;
     };
@@ -162,13 +178,14 @@
     void setLastUseTokenBulk(const BulkUseTokenUpdater& updater, GrDrawOpUploadToken token) {
         int count = updater.fPlotsToUpdate.count();
         for (int i = 0; i < count; i++) {
-            Plot* plot = fPlotArray[updater.fPlotsToUpdate[i]].get();
-            this->makeMRU(plot);
+            const BulkUseTokenUpdater::PlotData& pd = updater.fPlotsToUpdate[i];
+            Plot* plot = fPages[pd.fPageIndex].fPlotArray[pd.fPlotIndex].get();
+            this->makeMRU(plot, pd.fPageIndex);
             plot->setLastUseToken(token);
         }
     }
 
-    static const int kGlyphMaxDim = 256;
+    static constexpr auto kGlyphMaxDim = 256;
     static bool GlyphTooLargeForAtlas(int width, int height) {
         return width > kGlyphMaxDim || height > kGlyphMaxDim;
     }
@@ -188,8 +205,8 @@
         SK_DECLARE_INTERNAL_LLIST_INTERFACE(Plot);
 
     public:
-        /** index() is a unique id for the plot relative to the owning GrAtlas. */
-        uint32_t index() const { return fIndex; }
+        /** index() is a unique id for the plot relative to the owning GrAtlas and page. */
+        uint32_t index() const { return fPlotIndex; }
         /**
          * genID() is incremented when the plot is evicted due to a atlas spill. It is used to know
          * if a particular subimage is still present in the atlas.
@@ -219,7 +236,7 @@
         void resetRects();
 
     private:
-        Plot(int index, uint64_t genID, int offX, int offY, int width, int height,
+        Plot(int pageIndex, int plotIndex, uint64_t genID, int offX, int offY, int width, int height,
              GrPixelConfig config);
 
         ~Plot() override;
@@ -229,19 +246,25 @@
          * the atlas
          */
         Plot* clone() const {
-            return new Plot(fIndex, fGenID + 1, fX, fY, fWidth, fHeight, fConfig);
+            return new Plot(fPageIndex, fPlotIndex, fGenID + 1, fX, fY, fWidth, fHeight, fConfig);
         }
 
-        static GrDrawOpAtlas::AtlasID CreateId(uint32_t index, uint64_t generation) {
-            SkASSERT(index < (1 << 16));
+        static GrDrawOpAtlas::AtlasID CreateId(uint32_t pageIdx, uint32_t plotIdx,
+                                               uint64_t generation) {
+            SkASSERT(pageIdx < (1 << 8));
+            SkASSERT(pageIdx == 0); // for now, we only support one page
+            SkASSERT(plotIdx < (1 << 8));
             SkASSERT(generation < ((uint64_t)1 << 48));
-            return generation << 16 | index;
+            return generation << 16 | plotIdx << 8 | pageIdx;
         }
 
         GrDrawOpUploadToken   fLastUpload;
         GrDrawOpUploadToken   fLastUse;
 
-        const uint32_t fIndex;
+        struct {
+            const uint32_t fPageIndex : 16;
+            const uint32_t fPlotIndex : 16;
+        };
         uint64_t fGenID;
         GrDrawOpAtlas::AtlasID fID;
         unsigned char* fData;
@@ -263,8 +286,12 @@
 
     typedef SkTInternalLList<Plot> PlotList;
 
-    static uint32_t GetIndexFromID(AtlasID id) {
-        return id & 0xffff;
+    static uint32_t GetPageIndexFromID(AtlasID id) {
+        return id & 0xff;
+    }
+
+    static uint32_t GetPlotIndexFromID(AtlasID id) {
+        return (id >> 8) & 0xff;
     }
 
     // top 48 bits are reserved for the generation ID
@@ -274,19 +301,20 @@
 
     inline bool updatePlot(GrDrawOp::Target*, AtlasID*, Plot*);
 
-    inline void makeMRU(Plot* plot) {
-        if (fPlotList.head() == plot) {
+    inline void makeMRU(Plot* plot, int pageIdx) {
+        if (fPages[pageIdx].fPlotList.head() == plot) {
             return;
         }
 
-        fPlotList.remove(plot);
-        fPlotList.addToHead(plot);
+        fPages[pageIdx].fPlotList.remove(plot);
+        fPages[pageIdx].fPlotList.addToHead(plot);
+
+        // TODO: make page MRU
     }
 
     inline void processEviction(AtlasID);
 
     GrContext*            fContext;
-    sk_sp<GrTextureProxy> fProxy;
     GrPixelConfig         fPixelConfig;
     int                   fTextureWidth;
     int                   fTextureHeight;
@@ -302,10 +330,17 @@
     };
 
     SkTDArray<EvictionData> fEvictionCallbacks;
-    // allocated array of Plots
-    std::unique_ptr<sk_sp<Plot>[]> fPlotArray;
-    // LRU list of Plots (MRU at head - LRU at tail)
-    PlotList fPlotList;
+
+    struct Page {
+        // allocated array of Plots
+        std::unique_ptr<sk_sp<Plot>[]> fPlotArray;
+        // LRU list of Plots (MRU at head - LRU at tail)
+        PlotList fPlotList;
+    };
+    // proxies kept separate to make it easier to pass them up to client
+    sk_sp<GrTextureProxy> fProxies[kMaxPages];
+    Page fPages[kMaxPages];
+    SkDEBUGCODE(uint32_t fNumPages;)
 };
 
 #endif
diff --git a/src/gpu/effects/GrBitmapTextGeoProc.cpp b/src/gpu/effects/GrBitmapTextGeoProc.cpp
index 8013d2e..2d665c5 100644
--- a/src/gpu/effects/GrBitmapTextGeoProc.cpp
+++ b/src/gpu/effects/GrBitmapTextGeoProc.cpp
@@ -120,13 +120,13 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-GrBitmapTextGeoProc::GrBitmapTextGeoProc(GrColor color, sk_sp<GrTextureProxy> proxy,
+GrBitmapTextGeoProc::GrBitmapTextGeoProc(GrColor color,
+                                         const sk_sp<GrTextureProxy> proxies[kMaxTextures],
                                          const GrSamplerState& params, GrMaskFormat format,
                                          const SkMatrix& localMatrix, bool usesLocalCoords)
         : fColor(color)
         , fLocalMatrix(localMatrix)
         , fUsesLocalCoords(usesLocalCoords)
-        , fTextureSampler(std::move(proxy), params)
         , fInColor(nullptr)
         , fMaskFormat(format) {
     this->initClassID<GrBitmapTextGeoProc>();
@@ -141,7 +141,12 @@
 
     fInTextureCoords = &this->addVertexAttrib("inTextureCoords", kVec2us_uint_GrVertexAttribType,
                                               kHigh_GrSLPrecision);
-    this->addTextureSampler(&fTextureSampler);
+    for (int i = 0; i < kMaxTextures; ++i) {
+        if (proxies[i]) {
+            fTextureSamplers[i].reset(std::move(proxies[i]), params);
+            this->addTextureSampler(&fTextureSamplers[i]);
+        }
+    }
 }
 
 void GrBitmapTextGeoProc::getGLSLProcessorKey(const GrShaderCaps& caps,
@@ -162,7 +167,12 @@
 sk_sp<GrGeometryProcessor> GrBitmapTextGeoProc::TestCreate(GrProcessorTestData* d) {
     int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx
                                         : GrProcessorUnitTest::kAlphaTextureIdx;
-    sk_sp<GrTextureProxy> proxy = d->textureProxy(texIdx);
+    sk_sp<GrTextureProxy> proxies[kMaxTextures] = {
+        d->textureProxy(texIdx),
+        nullptr,
+        nullptr,
+        nullptr
+    };
 
     GrSamplerState::WrapMode wrapModes[2];
     GrTest::TestWrapModes(d->fRandom, wrapModes);
@@ -183,7 +193,7 @@
             break;
     }
 
-    return GrBitmapTextGeoProc::Make(GrRandomColor(d->fRandom), std::move(proxy), samplerState,
+    return GrBitmapTextGeoProc::Make(GrRandomColor(d->fRandom), proxies, samplerState,
                                      format, GrTest::TestMatrix(d->fRandom),
                                      d->fRandom->nextBool());
 }
diff --git a/src/gpu/effects/GrBitmapTextGeoProc.h b/src/gpu/effects/GrBitmapTextGeoProc.h
index c919d1c..f8f4d8e 100644
--- a/src/gpu/effects/GrBitmapTextGeoProc.h
+++ b/src/gpu/effects/GrBitmapTextGeoProc.h
@@ -21,11 +21,14 @@
  */
 class GrBitmapTextGeoProc : public GrGeometryProcessor {
 public:
-    static sk_sp<GrGeometryProcessor> Make(GrColor color, sk_sp<GrTextureProxy> proxy,
+    static constexpr int kMaxTextures = 4;
+
+    static sk_sp<GrGeometryProcessor> Make(GrColor color,
+                                           const sk_sp<GrTextureProxy> proxies[kMaxTextures],
                                            const GrSamplerState& p, GrMaskFormat format,
                                            const SkMatrix& localMatrix, bool usesLocalCoords) {
         return sk_sp<GrGeometryProcessor>(
-            new GrBitmapTextGeoProc(color, std::move(proxy), p, format,
+            new GrBitmapTextGeoProc(color, proxies, p, format,
                                     localMatrix, usesLocalCoords));
     }
 
@@ -47,13 +50,14 @@
     GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps& caps) const override;
 
 private:
-    GrBitmapTextGeoProc(GrColor, sk_sp<GrTextureProxy>, const GrSamplerState& params,
-                        GrMaskFormat format, const SkMatrix& localMatrix, bool usesLocalCoords);
+    GrBitmapTextGeoProc(GrColor, const sk_sp<GrTextureProxy> proxies[kMaxTextures],
+                        const GrSamplerState& params, GrMaskFormat format,
+                        const SkMatrix& localMatrix, bool usesLocalCoords);
 
     GrColor          fColor;
     SkMatrix         fLocalMatrix;
     bool             fUsesLocalCoords;
-    TextureSampler   fTextureSampler;
+    TextureSampler   fTextureSamplers[kMaxTextures];
     const Attribute* fInPosition;
     const Attribute* fInColor;
     const Attribute* fInTextureCoords;
diff --git a/src/gpu/effects/GrDistanceFieldGeoProc.cpp b/src/gpu/effects/GrDistanceFieldGeoProc.cpp
index 953d4cc..66e9619 100644
--- a/src/gpu/effects/GrDistanceFieldGeoProc.cpp
+++ b/src/gpu/effects/GrDistanceFieldGeoProc.cpp
@@ -228,18 +228,18 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-GrDistanceFieldA8TextGeoProc::GrDistanceFieldA8TextGeoProc(GrColor color,
-                                                           const SkMatrix& viewMatrix,
-                                                           sk_sp<GrTextureProxy> proxy,
-                                                           const GrSamplerState& params,
+GrDistanceFieldA8TextGeoProc::GrDistanceFieldA8TextGeoProc(
+                                                 GrColor color,
+                                                 const SkMatrix& viewMatrix,
+                                                 const sk_sp<GrTextureProxy> proxies[kMaxTextures],
+                                                 const GrSamplerState& params,
 #ifdef SK_GAMMA_APPLY_TO_A8
-                                                           float distanceAdjust,
+                                                 float distanceAdjust,
 #endif
-                                                           uint32_t flags,
-                                                           bool usesLocalCoords)
+                                                 uint32_t flags,
+                                                 bool usesLocalCoords)
         : fColor(color)
         , fViewMatrix(viewMatrix)
-        , fTextureSampler(std::move(proxy), params)
 #ifdef SK_GAMMA_APPLY_TO_A8
         , fDistanceAdjust(distanceAdjust)
 #endif
@@ -253,7 +253,12 @@
     fInColor = &this->addVertexAttrib("inColor", kVec4ub_GrVertexAttribType);
     fInTextureCoords = &this->addVertexAttrib("inTextureCoords", kVec2us_uint_GrVertexAttribType,
                                               kHigh_GrSLPrecision);
-    this->addTextureSampler(&fTextureSampler);
+    for (int i = 0; i < kMaxTextures; ++i) {
+        if (proxies[i]) {
+            fTextureSamplers[i].reset(std::move(proxies[i]), params);
+            this->addTextureSampler(&fTextureSamplers[i]);
+        }
+    }
 }
 
 void GrDistanceFieldA8TextGeoProc::getGLSLProcessorKey(const GrShaderCaps& caps,
@@ -274,7 +279,12 @@
 sk_sp<GrGeometryProcessor> GrDistanceFieldA8TextGeoProc::TestCreate(GrProcessorTestData* d) {
     int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx
                                         : GrProcessorUnitTest::kAlphaTextureIdx;
-    sk_sp<GrTextureProxy> proxy = d->textureProxy(texIdx);
+    sk_sp<GrTextureProxy> proxies[kMaxTextures] = {
+        d->textureProxy(texIdx),
+        nullptr,
+        nullptr,
+        nullptr
+    };
 
     GrSamplerState::WrapMode wrapModes[2];
     GrTest::TestWrapModes(d->fRandom, wrapModes);
@@ -289,7 +299,7 @@
     }
 
     return GrDistanceFieldA8TextGeoProc::Make(GrRandomColor(d->fRandom),
-                                              GrTest::TestMatrix(d->fRandom), std::move(proxy),
+                                              GrTest::TestMatrix(d->fRandom), proxies,
                                               samplerState,
 #ifdef SK_GAMMA_APPLY_TO_A8
                                               d->fRandom->nextF(),
@@ -474,15 +484,15 @@
 };
 
 ///////////////////////////////////////////////////////////////////////////////
-GrDistanceFieldPathGeoProc::GrDistanceFieldPathGeoProc(GrColor color,
-                                                       const SkMatrix& viewMatrix,
-                                                       sk_sp<GrTextureProxy> proxy,
-                                                       const GrSamplerState& params,
-                                                       uint32_t flags,
-                                                       bool usesLocalCoords)
+GrDistanceFieldPathGeoProc::GrDistanceFieldPathGeoProc(
+                                                 GrColor color,
+                                                 const SkMatrix& viewMatrix,
+                                                 const sk_sp<GrTextureProxy> proxies[kMaxTextures],
+                                                 const GrSamplerState& params,
+                                                 uint32_t flags,
+                                                 bool usesLocalCoords)
         : fColor(color)
         , fViewMatrix(viewMatrix)
-        , fTextureSampler(std::move(proxy), params)
         , fFlags(flags & kNonLCD_DistanceFieldEffectMask)
         , fInColor(nullptr)
         , fUsesLocalCoords(usesLocalCoords) {
@@ -493,7 +503,12 @@
     fInColor = &this->addVertexAttrib("inColor", kVec4ub_GrVertexAttribType);
     fInTextureCoords = &this->addVertexAttrib("inTextureCoords", kVec2us_uint_GrVertexAttribType,
                                               kHigh_GrSLPrecision);
-    this->addTextureSampler(&fTextureSampler);
+    for (int i = 0; i < kMaxTextures; ++i) {
+        if (proxies[i]) {
+            fTextureSamplers[i].reset(std::move(proxies[i]), params);
+            this->addTextureSampler(&fTextureSamplers[i]);
+        }
+    }
 }
 
 void GrDistanceFieldPathGeoProc::getGLSLProcessorKey(const GrShaderCaps& caps,
@@ -514,7 +529,12 @@
 sk_sp<GrGeometryProcessor> GrDistanceFieldPathGeoProc::TestCreate(GrProcessorTestData* d) {
     int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx
                                         : GrProcessorUnitTest::kAlphaTextureIdx;
-    sk_sp<GrTextureProxy> proxy = d->textureProxy(texIdx);
+    sk_sp<GrTextureProxy> proxies[kMaxTextures] = {
+        d->textureProxy(texIdx),
+        nullptr,
+        nullptr,
+        nullptr
+    };
 
     GrSamplerState::WrapMode wrapModes[2];
     GrTest::TestWrapModes(d->fRandom, wrapModes);
@@ -530,7 +550,7 @@
 
     return GrDistanceFieldPathGeoProc::Make(GrRandomColor(d->fRandom),
                                             GrTest::TestMatrix(d->fRandom),
-                                            std::move(proxy),
+                                            proxies,
                                             samplerState,
                                             flags,
                                             d->fRandom->nextBool());
@@ -776,15 +796,15 @@
 };
 
 ///////////////////////////////////////////////////////////////////////////////
-GrDistanceFieldLCDTextGeoProc::GrDistanceFieldLCDTextGeoProc(GrColor color,
-                                                             const SkMatrix& viewMatrix,
-                                                             sk_sp<GrTextureProxy> proxy,
-                                                             const GrSamplerState& params,
-                                                             DistanceAdjust distanceAdjust,
-                                                             uint32_t flags, bool usesLocalCoords)
+GrDistanceFieldLCDTextGeoProc::GrDistanceFieldLCDTextGeoProc(
+                                                 GrColor color,
+                                                 const SkMatrix& viewMatrix,
+                                                 const sk_sp<GrTextureProxy> proxies[kMaxTextures],
+                                                 const GrSamplerState& params,
+                                                 DistanceAdjust distanceAdjust,
+                                                 uint32_t flags, bool usesLocalCoords)
         : fColor(color)
         , fViewMatrix(viewMatrix)
-        , fTextureSampler(std::move(proxy), params)
         , fDistanceAdjust(distanceAdjust)
         , fFlags(flags & kLCD_DistanceFieldEffectMask)
         , fUsesLocalCoords(usesLocalCoords) {
@@ -795,7 +815,12 @@
     fInColor = &this->addVertexAttrib("inColor", kVec4ub_GrVertexAttribType);
     fInTextureCoords = &this->addVertexAttrib("inTextureCoords", kVec2us_uint_GrVertexAttribType,
                                               kHigh_GrSLPrecision);
-    this->addTextureSampler(&fTextureSampler);
+    for (int i = 0; i < kMaxTextures; ++i) {
+        if (proxies[i]) {
+            fTextureSamplers[i].reset(std::move(proxies[i]), params);
+            this->addTextureSampler(&fTextureSamplers[i]);
+        }
+    }
 }
 
 void GrDistanceFieldLCDTextGeoProc::getGLSLProcessorKey(const GrShaderCaps& caps,
@@ -815,7 +840,12 @@
 sk_sp<GrGeometryProcessor> GrDistanceFieldLCDTextGeoProc::TestCreate(GrProcessorTestData* d) {
     int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx :
                                           GrProcessorUnitTest::kAlphaTextureIdx;
-    sk_sp<GrTextureProxy> proxy = d->textureProxy(texIdx);
+    sk_sp<GrTextureProxy> proxies[kMaxTextures] = {
+        d->textureProxy(texIdx),
+        nullptr,
+        nullptr,
+        nullptr
+    };
 
     GrSamplerState::WrapMode wrapModes[2];
     GrTest::TestWrapModes(d->fRandom, wrapModes);
@@ -830,7 +860,7 @@
     }
     flags |= d->fRandom->nextBool() ? kBGR_DistanceFieldEffectFlag : 0;
     return GrDistanceFieldLCDTextGeoProc::Make(GrRandomColor(d->fRandom),
-                                               GrTest::TestMatrix(d->fRandom), std::move(proxy),
+                                               GrTest::TestMatrix(d->fRandom), proxies,
                                                samplerState, wa, flags, d->fRandom->nextBool());
 }
 #endif
diff --git a/src/gpu/effects/GrDistanceFieldGeoProc.h b/src/gpu/effects/GrDistanceFieldGeoProc.h
index b1836dd..b5c6f9e 100644
--- a/src/gpu/effects/GrDistanceFieldGeoProc.h
+++ b/src/gpu/effects/GrDistanceFieldGeoProc.h
@@ -50,22 +50,24 @@
  */
 class GrDistanceFieldA8TextGeoProc : public GrGeometryProcessor {
 public:
+    static constexpr int kMaxTextures = 4;
+
 #ifdef SK_GAMMA_APPLY_TO_A8
     static sk_sp<GrGeometryProcessor> Make(GrColor color, const SkMatrix& viewMatrix,
-                                           sk_sp<GrTextureProxy> proxy,
+                                           const sk_sp<GrTextureProxy> proxies[kMaxTextures],
                                            const GrSamplerState& params, float lum, uint32_t flags,
                                            bool usesLocalCoords) {
         return sk_sp<GrGeometryProcessor>(
-            new GrDistanceFieldA8TextGeoProc(color, viewMatrix, std::move(proxy),
+            new GrDistanceFieldA8TextGeoProc(color, viewMatrix, proxies,
                                              params, lum, flags, usesLocalCoords));
     }
 #else
     static sk_sp<GrGeometryProcessor> Make(GrColor color, const SkMatrix& viewMatrix,
-                                           sk_sp<GrTextureProxy> proxy,
+                                           const sk_sp<GrTextureProxy> proxies[kMaxTextures],
                                            const GrSamplerState& params, uint32_t flags,
                                            bool usesLocalCoords) {
         return sk_sp<GrGeometryProcessor>(
-            new GrDistanceFieldA8TextGeoProc(color, viewMatrix, std::move(proxy),
+            new GrDistanceFieldA8TextGeoProc(color, viewMatrix, proxies,
                                              params, flags, usesLocalCoords));
     }
 #endif
@@ -90,7 +92,8 @@
     GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override;
 
 private:
-    GrDistanceFieldA8TextGeoProc(GrColor, const SkMatrix& viewMatrix, sk_sp<GrTextureProxy> proxy,
+    GrDistanceFieldA8TextGeoProc(GrColor, const SkMatrix& viewMatrix,
+                                 const sk_sp<GrTextureProxy> proxies[kMaxTextures],
                                  const GrSamplerState& params,
 #ifdef SK_GAMMA_APPLY_TO_A8
                                  float distanceAdjust,
@@ -99,7 +102,7 @@
 
     GrColor          fColor;
     SkMatrix         fViewMatrix;
-    TextureSampler   fTextureSampler;
+    TextureSampler   fTextureSamplers[kMaxTextures];
 #ifdef SK_GAMMA_APPLY_TO_A8
     float            fDistanceAdjust;
 #endif
@@ -122,12 +125,14 @@
  */
 class GrDistanceFieldPathGeoProc : public GrGeometryProcessor {
 public:
+    static constexpr int kMaxTextures = 4;
+
     static sk_sp<GrGeometryProcessor> Make(GrColor color, const SkMatrix& viewMatrix,
-                                           sk_sp<GrTextureProxy> proxy,
+                                           const sk_sp<GrTextureProxy> proxies[kMaxTextures],
                                            const GrSamplerState& params, uint32_t flags,
                                            bool usesLocalCoords) {
         return sk_sp<GrGeometryProcessor>(
-            new GrDistanceFieldPathGeoProc(color, viewMatrix, std::move(proxy),
+            new GrDistanceFieldPathGeoProc(color, viewMatrix, proxies,
                                            params, flags, usesLocalCoords));
     }
 
@@ -148,12 +153,13 @@
     GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override;
 
 private:
-    GrDistanceFieldPathGeoProc(GrColor, const SkMatrix& viewMatrix, sk_sp<GrTextureProxy>,
+    GrDistanceFieldPathGeoProc(GrColor, const SkMatrix& viewMatrix,
+                               const sk_sp<GrTextureProxy> proxies[kMaxTextures],
                                const GrSamplerState&, uint32_t flags, bool usesLocalCoords);
 
     GrColor          fColor;
     SkMatrix         fViewMatrix;
-    TextureSampler   fTextureSampler;
+    TextureSampler   fTextureSamplers[kMaxTextures];
     uint32_t         fFlags;
     const Attribute* fInPosition;
     const Attribute* fInColor;
@@ -188,15 +194,17 @@
         }
     };
 
+    static constexpr int kMaxTextures = 4;
+
     static sk_sp<GrGeometryProcessor> Make(GrColor color,
                                            const SkMatrix& viewMatrix,
-                                           sk_sp<GrTextureProxy> proxy,
+                                           const sk_sp<GrTextureProxy> proxies[kMaxTextures],
                                            const GrSamplerState& params,
                                            DistanceAdjust distanceAdjust,
                                            uint32_t flags,
                                            bool usesLocalCoords) {
         return sk_sp<GrGeometryProcessor>(
-            new GrDistanceFieldLCDTextGeoProc(color, viewMatrix, std::move(proxy),
+            new GrDistanceFieldLCDTextGeoProc(color, viewMatrix, proxies,
                                               params, distanceAdjust,
                                               flags, usesLocalCoords));
     }
@@ -219,13 +227,14 @@
     GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override;
 
 private:
-    GrDistanceFieldLCDTextGeoProc(GrColor, const SkMatrix& viewMatrix, sk_sp<GrTextureProxy> proxy,
+    GrDistanceFieldLCDTextGeoProc(GrColor, const SkMatrix& viewMatrix,
+                                  const sk_sp<GrTextureProxy> proxies[kMaxTextures],
                                   const GrSamplerState& params, DistanceAdjust wa, uint32_t flags,
                                   bool usesLocalCoords);
 
     GrColor          fColor;
     SkMatrix         fViewMatrix;
-    TextureSampler   fTextureSampler;
+    TextureSampler   fTextureSamplers[kMaxTextures];
     DistanceAdjust   fDistanceAdjust;
     uint32_t         fFlags;
     const Attribute* fInPosition;
diff --git a/src/gpu/ops/GrAtlasTextOp.cpp b/src/gpu/ops/GrAtlasTextOp.cpp
index bfdbfdb..c6ceaac 100644
--- a/src/gpu/ops/GrAtlasTextOp.cpp
+++ b/src/gpu/ops/GrAtlasTextOp.cpp
@@ -84,8 +84,8 @@
         return;
     }
 
-    sk_sp<GrTextureProxy> proxy = fFontCache->getProxy(this->maskFormat());
-    if (!proxy) {
+    const sk_sp<GrTextureProxy>* proxies = fFontCache->getProxies(this->maskFormat());
+    if (!proxies[0]) {
         SkDebugf("Could not allocate backing texture for atlas\n");
         return;
     }
@@ -98,10 +98,10 @@
     if (this->usesDistanceFields()) {
         flushInfo.fGeometryProcessor =
                 this->setupDfProcessor(this->viewMatrix(),
-                                       fLuminanceColor, this->color(), std::move(proxy));
+                                       fLuminanceColor, this->color(), proxies);
     } else {
         flushInfo.fGeometryProcessor = GrBitmapTextGeoProc::Make(
-                this->color(), std::move(proxy), GrSamplerState::ClampNearest(), maskFormat,
+                this->color(), proxies, GrSamplerState::ClampNearest(), maskFormat,
                 localMatrix, this->usesLocalCoords());
     }
 
@@ -218,10 +218,11 @@
 
 // TODO just use class params
 // TODO trying to figure out why lcd is so whack
-sk_sp<GrGeometryProcessor> GrAtlasTextOp::setupDfProcessor(const SkMatrix& viewMatrix,
-                                                           SkColor luminanceColor,
-                                                           GrColor color,
-                                                           sk_sp<GrTextureProxy> proxy) const {
+sk_sp<GrGeometryProcessor> GrAtlasTextOp::setupDfProcessor(
+                                                const SkMatrix& viewMatrix,
+                                                SkColor luminanceColor,
+                                                GrColor color,
+                                                const sk_sp<GrTextureProxy> p[kMaxTextures]) const {
     bool isLCD = this->isLCD();
     // set up any flags
     uint32_t flags = viewMatrix.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
@@ -247,7 +248,7 @@
                 GrDistanceFieldLCDTextGeoProc::DistanceAdjust::Make(
                         redCorrection, greenCorrection, blueCorrection);
 
-        return GrDistanceFieldLCDTextGeoProc::Make(color, viewMatrix, std::move(proxy),
+        return GrDistanceFieldLCDTextGeoProc::Make(color, viewMatrix, p,
                                                    GrSamplerState::ClampBilerp(), widthAdjust,
                                                    flags, this->usesLocalCoords());
     } else {
@@ -258,11 +259,11 @@
             correction = fDistanceAdjustTable->getAdjustment(lum >> kDistanceAdjustLumShift,
                                                              fUseGammaCorrectDistanceTable);
         }
-        return GrDistanceFieldA8TextGeoProc::Make(color, viewMatrix, std::move(proxy),
+        return GrDistanceFieldA8TextGeoProc::Make(color, viewMatrix, p,
                                                   GrSamplerState::ClampBilerp(), correction, flags,
                                                   this->usesLocalCoords());
 #else
-        return GrDistanceFieldA8TextGeoProc::Make(color, viewMatrix, std::move(proxy),
+        return GrDistanceFieldA8TextGeoProc::Make(color, viewMatrix, p,
                                                   GrSamplerState::ClampBilerp(), flags,
                                                   this->usesLocalCoords());
 #endif
diff --git a/src/gpu/ops/GrAtlasTextOp.h b/src/gpu/ops/GrAtlasTextOp.h
index 5c37d45..58e0515 100644
--- a/src/gpu/ops/GrAtlasTextOp.h
+++ b/src/gpu/ops/GrAtlasTextOp.h
@@ -159,9 +159,12 @@
 
     bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override;
 
+    static constexpr auto kMaxTextures = 4;
+    
     // TODO just use class params
     sk_sp<GrGeometryProcessor> setupDfProcessor(const SkMatrix& viewMatrix, SkColor luminanceColor,
-                                                GrColor color, sk_sp<GrTextureProxy> proxy) const;
+                                                GrColor color,
+                                                const sk_sp<GrTextureProxy> [kMaxTextures]) const;
 
 
     // The minimum number of Geometry we will try to allocate.
diff --git a/src/gpu/ops/GrSmallPathRenderer.cpp b/src/gpu/ops/GrSmallPathRenderer.cpp
index ec63e9e..1c3cbe9 100644
--- a/src/gpu/ops/GrSmallPathRenderer.cpp
+++ b/src/gpu/ops/GrSmallPathRenderer.cpp
@@ -234,7 +234,7 @@
             flags |= fGammaCorrect ? kGammaCorrect_DistanceFieldEffectFlag : 0;
 
             flushInfo.fGeometryProcessor = GrDistanceFieldPathGeoProc::Make(
-                    this->color(), this->viewMatrix(), atlas->getProxy(),
+                    this->color(), this->viewMatrix(), atlas->getProxies(),
                     GrSamplerState::ClampBilerp(), flags, fHelper.usesLocalCoords());
         } else {
             SkMatrix invert;
@@ -249,7 +249,7 @@
             }
 
             flushInfo.fGeometryProcessor = GrBitmapTextGeoProc::Make(
-                    this->color(), atlas->getProxy(), GrSamplerState::ClampNearest(),
+                    this->color(), atlas->getProxies(), GrSamplerState::ClampNearest(),
                     kA8_GrMaskFormat, invert, fHelper.usesLocalCoords());
         }
 
diff --git a/src/gpu/text/GrAtlasGlyphCache.cpp b/src/gpu/text/GrAtlasGlyphCache.cpp
index 7456a8e..1cf704a 100644
--- a/src/gpu/text/GrAtlasGlyphCache.cpp
+++ b/src/gpu/text/GrAtlasGlyphCache.cpp
@@ -176,16 +176,18 @@
     static int gDumpCount = 0;
     for (int i = 0; i < kMaskFormatCount; ++i) {
         if (fAtlases[i]) {
-            sk_sp<GrTextureProxy> proxy = fAtlases[i]->getProxy();
-            if (proxy) {
-                SkString filename;
+            const sk_sp<GrTextureProxy>* proxies = fAtlases[i]->getProxies();
+            for (int pageIdx = 0; pageIdx < GrDrawOpAtlas::kMaxPages; ++pageIdx) {
+                if (proxies[pageIdx]) {
+                    SkString filename;
 #ifdef SK_BUILD_FOR_ANDROID
-                filename.printf("/sdcard/fontcache_%d%d.png", gDumpCount, i);
+                    filename.printf("/sdcard/fontcache_%d%d%d.png", gDumpCount, i, pageIdx);
 #else
-                filename.printf("fontcache_%d%d.png", gDumpCount, i);
+                    filename.printf("fontcache_%d%d%d.png", gDumpCount, i, pageIdx);
 #endif
 
-                save_pixels(fContext, proxy.get(), filename.c_str());
+                    save_pixels(fContext, proxies[pageIdx].get(), filename.c_str());
+                }
             }
         }
     }
diff --git a/src/gpu/text/GrAtlasGlyphCache.h b/src/gpu/text/GrAtlasGlyphCache.h
index 167830f..81876a0 100644
--- a/src/gpu/text/GrAtlasGlyphCache.h
+++ b/src/gpu/text/GrAtlasGlyphCache.h
@@ -129,9 +129,9 @@
     // if getProxy returns nullptr, the client must not try to use other functions on the
     // GrAtlasGlyphCache which use the atlas.  This function *must* be called first, before other
     // functions which use the atlas.
-    sk_sp<GrTextureProxy> getProxy(GrMaskFormat format) {
+    const sk_sp<GrTextureProxy>* getProxies(GrMaskFormat format) {
         if (this->initAtlas(format)) {
-            return this->getAtlas(format)->getProxy();
+            return this->getAtlas(format)->getProxies();
         }
         return nullptr;
     }