Make GrAtlasTextBlob return to caller when a flush is required during subrun tessellation.

The old code used a helper object in the tessellation code that called flush() on GrAtlasTextOp.
A confusing aspect of this was that the pre-flush vertex data generated for the sub run was copied
to the op's vertex buffer after flush() had already recorded the draw that read the data.

The new code adds a tessellator nested helper class to GrAtlasTextBlob. The helper exits early if
a flush is required, the op performs the flush, and then the helper is invoked again until
tessellation is complete.

This also changes the blob object to use char* instead of unsigned char* for its vertex pointers.

Change-Id: I31bed251435f13b2172e6f5829ba437b882dd44d
Reviewed-on: https://skia-review.googlesource.com/67856
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
diff --git a/src/gpu/ops/GrAtlasTextOp.cpp b/src/gpu/ops/GrAtlasTextOp.cpp
index 2383510..9e98466 100644
--- a/src/gpu/ops/GrAtlasTextOp.cpp
+++ b/src/gpu/ops/GrAtlasTextOp.cpp
@@ -76,12 +76,12 @@
     return analysis.requiresDstTexture() ? RequiresDstTexture::kYes : RequiresDstTexture::kNo;
 }
 
-static void clip_quads(const SkIRect& clipRect,
-                       unsigned char* currVertex, unsigned char* blobVertices,
+static void clip_quads(const SkIRect& clipRect, char* currVertex, const char* blobVertices,
                        size_t vertexStride, int glyphCount) {
     for (int i = 0; i < glyphCount; ++i) {
-        SkPoint* blobPositionLT = reinterpret_cast<SkPoint*>(blobVertices);
-        SkPoint* blobPositionRB = reinterpret_cast<SkPoint*>(blobVertices + 3*vertexStride);
+        const SkPoint* blobPositionLT = reinterpret_cast<const SkPoint*>(blobVertices);
+        const SkPoint* blobPositionRB =
+                reinterpret_cast<const SkPoint*>(blobVertices + 3 * vertexStride);
 
         // positions for bitmap glyphs are pixel boundary aligned
         SkIRect positionRect = SkIRect::MakeLTRB(SkScalarFloorToInt(blobPositionLT->fX),
@@ -95,11 +95,11 @@
             // Pull out some more data that we'll need.
             // In the LCD case the color will be garbage, but we'll overwrite it with the texcoords
             // and it avoids a lot of conditionals.
-            SkColor color = *reinterpret_cast<SkColor*>(blobVertices + sizeof(SkPoint));
+            auto color = *reinterpret_cast<const SkColor*>(blobVertices + sizeof(SkPoint));
             size_t coordOffset = vertexStride - 2*sizeof(uint16_t);
-            uint16_t* blobCoordsLT = reinterpret_cast<uint16_t*>(blobVertices + coordOffset);
-            uint16_t* blobCoordsRB = reinterpret_cast<uint16_t*>(blobVertices + 3*vertexStride +
-                                                                 coordOffset);
+            auto* blobCoordsLT = reinterpret_cast<const uint16_t*>(blobVertices + coordOffset);
+            auto* blobCoordsRB = reinterpret_cast<const uint16_t*>(blobVertices + 3 * vertexStride +
+                                                                   coordOffset);
             // Pull out the texel coordinates and texture index bits
             uint16_t coordsRectL = blobCoordsLT[0] >> 1;
             uint16_t coordsRectT = blobCoordsLT[1] >> 1;
@@ -224,32 +224,34 @@
         return;
     }
 
-    unsigned char* currVertex = reinterpret_cast<unsigned char*>(vertices);
+    char* currVertex = reinterpret_cast<char*>(vertices);
 
-    GrBlobRegenHelper helper(this, target, &flushInfo);
     SkAutoGlyphCache glyphCache;
     // each of these is a SubRun
     for (int i = 0; i < fGeoCount; i++) {
         const Geometry& args = fGeoData[i];
         Blob* blob = args.fBlob;
-        size_t byteCount;
-        void* blobVertices;
-        int subRunGlyphCount;
-        blob->regenInOp(target->deferredUploadTarget(), fFontCache, &helper, args.fRun,
-                        args.fSubRun, &glyphCache, vertexStride, args.fViewMatrix, args.fX, args.fY,
-                        args.fColor, &blobVertices, &byteCount, &subRunGlyphCount);
-
-        // now copy all vertices
-        if (args.fClipRect.isEmpty()) {
-            memcpy(currVertex, blobVertices, byteCount);
-        } else {
-            clip_quads(args.fClipRect, currVertex, reinterpret_cast<unsigned char*>(blobVertices),
-                       vertexStride, subRunGlyphCount);
-        }
-
-        currVertex += byteCount;
+        GrAtlasTextBlob::VertexRegenerator regenerator(
+                blob, args.fRun, args.fSubRun, args.fViewMatrix, args.fX, args.fY, args.fColor,
+                target->deferredUploadTarget(), fFontCache, &glyphCache, vertexStride);
+        GrAtlasTextBlob::VertexRegenerator::Result result;
+        do {
+            result = regenerator.regenerate();
+            // Copy regenerated vertices from the blob to our vertex buffer.
+            size_t vertexBytes = result.fGlyphsRegenerated * kVerticesPerGlyph * vertexStride;
+            if (args.fClipRect.isEmpty()) {
+                memcpy(currVertex, result.fFirstVertex, vertexBytes);
+            } else {
+                clip_quads(args.fClipRect, currVertex, result.fFirstVertex, vertexStride,
+                           result.fGlyphsRegenerated);
+            }
+            flushInfo.fGlyphsToFlush += result.fGlyphsRegenerated;
+            if (!result.fFinished) {
+                this->flush(target, &flushInfo);
+            }
+            currVertex += vertexBytes;
+        } while (!result.fFinished);
     }
-
     this->flush(target, &flushInfo);
 }
 
@@ -406,4 +408,3 @@
     }
 }
 
-void GrBlobRegenHelper::flush() { fOp->flush(fTarget, fFlushInfo); }
diff --git a/src/gpu/ops/GrAtlasTextOp.h b/src/gpu/ops/GrAtlasTextOp.h
index 1814676..240b98b 100644
--- a/src/gpu/ops/GrAtlasTextOp.h
+++ b/src/gpu/ops/GrAtlasTextOp.h
@@ -206,29 +206,7 @@
     SkColor fLuminanceColor;
     bool fUseGammaCorrectDistanceTable;
 
-    friend class GrBlobRegenHelper;  // Needs to trigger flushes
-
     typedef GrMeshDrawOp INHERITED;
 };
 
-/*
- * A simple helper class to abstract the interface GrAtlasTextBlob needs to regenerate itself.
- * It'd be nicer if this was nested, but we need to forward declare it in GrAtlasTextBlob.h
- */
-class GrBlobRegenHelper {
-public:
-    GrBlobRegenHelper(const GrAtlasTextOp* op, GrMeshDrawOp::Target* target,
-                      GrAtlasTextOp::FlushInfo* flushInfo)
-            : fOp(op), fTarget(target), fFlushInfo(flushInfo) {}
-
-    void flush();
-
-    void incGlyphCount(int glyphCount = 1) { fFlushInfo->fGlyphsToFlush += glyphCount; }
-
-private:
-    const GrAtlasTextOp* fOp;
-    GrMeshDrawOp::Target* fTarget;
-    GrAtlasTextOp::FlushInfo* fFlushInfo;
-};
-
 #endif
diff --git a/src/gpu/text/GrAtlasTextBlob.cpp b/src/gpu/text/GrAtlasTextBlob.cpp
index 17f81a2..0b25a34 100644
--- a/src/gpu/text/GrAtlasTextBlob.cpp
+++ b/src/gpu/text/GrAtlasTextBlob.cpp
@@ -34,8 +34,7 @@
     cacheBlob->fSize = size;
 
     // setup offsets for vertices / glyphs
-    cacheBlob->fVertices = sizeof(GrAtlasTextBlob) +
-                           reinterpret_cast<unsigned char*>(cacheBlob.get());
+    cacheBlob->fVertices = sizeof(GrAtlasTextBlob) + reinterpret_cast<char*>(cacheBlob.get());
     cacheBlob->fGlyphs = reinterpret_cast<GrGlyph**>(cacheBlob->fVertices + verticesCount);
     cacheBlob->fRuns = reinterpret_cast<GrAtlasTextBlob::Run*>(cacheBlob->fGlyphs + glyphCount);
 
diff --git a/src/gpu/text/GrAtlasTextBlob.h b/src/gpu/text/GrAtlasTextBlob.h
index 495e72a..403cbbf 100644
--- a/src/gpu/text/GrAtlasTextBlob.h
+++ b/src/gpu/text/GrAtlasTextBlob.h
@@ -21,7 +21,6 @@
 #include "SkSurfaceProps.h"
 #include "SkTInternalLList.h"
 
-class GrBlobRegenHelper;
 struct GrDistanceFieldAdjustTable;
 class GrMemoryPool;
 class SkDrawFilter;
@@ -50,6 +49,8 @@
 public:
     SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrAtlasTextBlob);
 
+    class VertexRegenerator;
+
     static sk_sp<GrAtlasTextBlob> Make(GrMemoryPool* pool, int glyphCount, int runCount);
 
     struct Key {
@@ -250,16 +251,6 @@
         this->setupViewMatrix(viewMatrix, x, y);
     }
 
-    /**
-     * Consecutive calls to regenInOp often use the same SkGlyphCache. If the same instance of
-     * SkAutoGlyphCache is passed to multiple calls of regenInOp then it can save the cost of
-     * multiple detach/attach operations of SkGlyphCache.
-     */
-    void regenInOp(GrDeferredUploadTarget*, GrAtlasGlyphCache* fontCache, GrBlobRegenHelper* helper,
-                   int run, int subRun, SkAutoGlyphCache*, size_t vertexStride,
-                   const SkMatrix& viewMatrix, SkScalar x, SkScalar y, GrColor color,
-                   void** vertices, size_t* byteCount, int* glyphCount);
-
     const Key& key() const { return fKey; }
 
     ~GrAtlasTextBlob() {
@@ -492,11 +483,6 @@
         bool fDrawAsPaths;
     };  // Run
 
-    template <bool regenPos, bool regenCol, bool regenTexCoords, bool regenGlyphs>
-    void regenInOp(GrDeferredUploadTarget*, GrAtlasGlyphCache* fontCache, GrBlobRegenHelper* helper,
-                   Run* run, Run::SubRunInfo* info, SkAutoGlyphCache*, int glyphCount,
-                   size_t vertexStride, GrColor color, SkScalar transX, SkScalar transY) const;
-
     inline std::unique_ptr<GrAtlasTextOp> makeOp(
             const Run::SubRunInfo& info, int glyphCount, uint16_t run, uint16_t subRun,
             const SkMatrix& viewMatrix, SkScalar x, SkScalar y, const SkIRect& clipRect,
@@ -530,7 +516,7 @@
     };
 
     // all glyph / vertex offsets are into these pools.
-    unsigned char* fVertices;
+    char* fVertices;
     GrGlyph** fGlyphs;
     Run* fRuns;
     GrMemoryPool* fPool;
@@ -554,4 +540,65 @@
     uint8_t fTextType;
 };
 
+/**
+ * Used to produce vertices for a subrun of a blob. The vertices are cached in the blob itself.
+ * This is invoked each time a sub run is drawn. It regenerates the vertex data as required either
+ * because of changes to the atlas or because of different draw parameters (e.g. color change). In
+ * rare cases the draw may have to interrupted and flushed in the middle of the sub run in order to
+ * free up atlas space. Thus, this generator is stateful and should be invoked in a loop until the
+ * entire sub run has been completed.
+ */
+class GrAtlasTextBlob::VertexRegenerator {
+public:
+    /**
+     * Consecutive VertexRegenerators often use the same SkGlyphCache. If the same instance of
+     * SkAutoGlyphCache is reused then it can save the cost of multiple detach/attach operations of
+     * SkGlyphCache.
+     */
+    VertexRegenerator(GrAtlasTextBlob* blob, int runIdx, int subRunIdx, const SkMatrix& viewMatrix,
+                      SkScalar x, SkScalar y, GrColor color, GrDeferredUploadTarget*,
+                      GrAtlasGlyphCache*, SkAutoGlyphCache*, size_t vertexStride);
+
+    struct Result {
+        /**
+         * Was regenerate() able to draw all the glyphs from the sub run? If not flush all glyph
+         * draws and call regenerate() again.
+         */
+        bool fFinished = true;
+
+        /**
+         * How many glyphs were regenerated. Will be equal to the sub run's glyph count if
+         * fType is kFinished.
+         */
+        int fGlyphsRegenerated = 0;
+
+        /**
+         * Pointer where the caller finds the first regenerated vertex.
+         */
+        const char* fFirstVertex;
+    };
+
+    Result regenerate();
+
+private:
+    template <bool regenPos, bool regenCol, bool regenTexCoords, bool regenGlyphs>
+    Result doRegen();
+
+    const SkMatrix& fViewMatrix;
+    GrAtlasTextBlob* fBlob;
+    GrDeferredUploadTarget* fUploadTarget;
+    GrAtlasGlyphCache* fGlyphCache;
+    SkAutoGlyphCache* fLazyCache;
+    Run* fRun;
+    Run::SubRunInfo* fSubRun;
+    size_t fVertexStride;
+    GrColor fColor;
+    SkScalar fTransX;
+    SkScalar fTransY;
+
+    uint32_t fRegenFlags = 0;
+    int fCurrGlyph = 0;
+    bool fBrokenRun = false;
+};
+
 #endif
diff --git a/src/gpu/text/GrAtlasTextBlobVertexRegenerator.cpp b/src/gpu/text/GrAtlasTextBlobVertexRegenerator.cpp
new file mode 100644
index 0000000..6b242ad
--- /dev/null
+++ b/src/gpu/text/GrAtlasTextBlobVertexRegenerator.cpp
@@ -0,0 +1,317 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrAtlasTextBlob.h"
+#include "GrTextUtils.h"
+#include "SkDistanceFieldGen.h"
+#include "SkGlyphCache.h"
+#include "ops/GrAtlasTextOp.h"
+
+using Regenerator = GrAtlasTextBlob::VertexRegenerator;
+
+enum RegenMask {
+    kNoRegen    = 0x0,
+    kRegenPos   = 0x1,
+    kRegenCol   = 0x2,
+    kRegenTex   = 0x4,
+    kRegenGlyph = 0x8 | kRegenTex,  // we have to regenerate the texture coords when we regen glyphs
+
+    // combinations
+    kRegenPosCol = kRegenPos | kRegenCol,
+    kRegenPosTex = kRegenPos | kRegenTex,
+    kRegenPosTexGlyph = kRegenPos | kRegenGlyph,
+    kRegenPosColTex = kRegenPos | kRegenCol | kRegenTex,
+    kRegenPosColTexGlyph = kRegenPos | kRegenCol | kRegenGlyph,
+    kRegenColTex = kRegenCol | kRegenTex,
+    kRegenColTexGlyph = kRegenCol | kRegenGlyph,
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// A large template to handle regenerating the vertices of a textblob with as few branches as
+// possible
+template <bool regenPos, bool regenCol, bool regenTexCoords>
+inline void regen_vertices(char* vertex, const GrGlyph* glyph, size_t vertexStride,
+                           bool useDistanceFields, SkScalar transX, SkScalar transY,
+                           GrColor color) {
+    uint16_t u0, v0, u1, v1;
+    if (regenTexCoords) {
+        SkASSERT(glyph);
+        int width = glyph->fBounds.width();
+        int height = glyph->fBounds.height();
+
+        if (useDistanceFields) {
+            u0 = glyph->fAtlasLocation.fX + SK_DistanceFieldInset;
+            v0 = glyph->fAtlasLocation.fY + SK_DistanceFieldInset;
+            u1 = u0 + width - 2 * SK_DistanceFieldInset;
+            v1 = v0 + height - 2 * SK_DistanceFieldInset;
+        } else {
+            u0 = glyph->fAtlasLocation.fX;
+            v0 = glyph->fAtlasLocation.fY;
+            u1 = u0 + width;
+            v1 = v0 + height;
+        }
+        // We pack the 2bit page index in the low bit of the u and v texture coords
+        uint32_t pageIndex = glyph->pageIndex();
+        SkASSERT(pageIndex < 4);
+        uint16_t uBit = (pageIndex >> 1) & 0x1;
+        uint16_t vBit = pageIndex & 0x1;
+        u0 <<= 1;
+        u0 |= uBit;
+        v0 <<= 1;
+        v0 |= vBit;
+        u1 <<= 1;
+        u1 |= uBit;
+        v1 <<= 1;
+        v1 |= vBit;
+    }
+
+    // This is a bit wonky, but sometimes we have LCD text, in which case we won't have color
+    // vertices, hence vertexStride - sizeof(SkIPoint16)
+    intptr_t colorOffset = sizeof(SkPoint);
+    intptr_t texCoordOffset = vertexStride - sizeof(SkIPoint16);
+
+    // V0
+    if (regenPos) {
+        SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
+        point->fX += transX;
+        point->fY += transY;
+    }
+
+    if (regenCol) {
+        SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
+        *vcolor = color;
+    }
+
+    if (regenTexCoords) {
+        uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
+        textureCoords[0] = u0;
+        textureCoords[1] = v0;
+    }
+    vertex += vertexStride;
+
+    // V1
+    if (regenPos) {
+        SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
+        point->fX += transX;
+        point->fY += transY;
+    }
+
+    if (regenCol) {
+        SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
+        *vcolor = color;
+    }
+
+    if (regenTexCoords) {
+        uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
+        textureCoords[0] = u0;
+        textureCoords[1] = v1;
+    }
+    vertex += vertexStride;
+
+    // V2
+    if (regenPos) {
+        SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
+        point->fX += transX;
+        point->fY += transY;
+    }
+
+    if (regenCol) {
+        SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
+        *vcolor = color;
+    }
+
+    if (regenTexCoords) {
+        uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
+        textureCoords[0] = u1;
+        textureCoords[1] = v0;
+    }
+    vertex += vertexStride;
+
+    // V3
+    if (regenPos) {
+        SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
+        point->fX += transX;
+        point->fY += transY;
+    }
+
+    if (regenCol) {
+        SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
+        *vcolor = color;
+    }
+
+    if (regenTexCoords) {
+        uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
+        textureCoords[0] = u1;
+        textureCoords[1] = v1;
+    }
+}
+
+Regenerator::VertexRegenerator(GrAtlasTextBlob* blob, int runIdx, int subRunIdx,
+                               const SkMatrix& viewMatrix, SkScalar x, SkScalar y, GrColor color,
+                               GrDeferredUploadTarget* uploadTarget, GrAtlasGlyphCache* glyphCache,
+                               SkAutoGlyphCache* lazyCache, size_t vertexStride)
+        : fViewMatrix(viewMatrix)
+        , fBlob(blob)
+        , fUploadTarget(uploadTarget)
+        , fGlyphCache(glyphCache)
+        , fLazyCache(lazyCache)
+        , fRun(&blob->fRuns[runIdx])
+        , fSubRun(&blob->fRuns[runIdx].fSubRunInfo[subRunIdx])
+        , fVertexStride(vertexStride)
+        , fColor(color) {
+    // Compute translation if any
+    fSubRun->computeTranslation(fViewMatrix, x, y, &fTransX, &fTransY);
+
+    // Because the GrAtlasGlyphCache may evict the strike a blob depends on using for
+    // generating its texture coords, we have to track whether or not the strike has
+    // been abandoned.  If it hasn't been abandoned, then we can use the GrGlyph*s as is
+    // otherwise we have to get the new strike, and use that to get the correct glyphs.
+    // Because we do not have the packed ids, and thus can't look up our glyphs in the
+    // new strike, we instead keep our ref to the old strike and use the packed ids from
+    // it.  These ids will still be valid as long as we hold the ref.  When we are done
+    // updating our cache of the GrGlyph*s, we drop our ref on the old strike
+    if (fSubRun->strike()->isAbandoned()) {
+        fRegenFlags |= kRegenGlyph;
+        fRegenFlags |= kRegenTex;
+    }
+    if (kARGB_GrMaskFormat != fSubRun->maskFormat() && fSubRun->color() != color) {
+        fRegenFlags |= kRegenCol;
+    }
+    if (0.f != fTransX || 0.f != fTransY) {
+        fRegenFlags |= kRegenPos;
+    }
+}
+
+template <bool regenPos, bool regenCol, bool regenTexCoords, bool regenGlyphs>
+Regenerator::Result Regenerator::doRegen() {
+    static_assert(!regenGlyphs || regenTexCoords, "must regenTexCoords along regenGlyphs");
+    GrAtlasTextStrike* strike = nullptr;
+    if (regenTexCoords) {
+        fSubRun->resetBulkUseToken();
+
+        const SkDescriptor* desc = (fRun->fOverrideDescriptor && !fSubRun->drawAsDistanceFields())
+                                           ? fRun->fOverrideDescriptor->getDesc()
+                                           : fRun->fDescriptor.getDesc();
+
+        if (!*fLazyCache || (*fLazyCache)->getDescriptor() != *desc) {
+            SkScalerContextEffects effects;
+            effects.fPathEffect = fRun->fPathEffect.get();
+            effects.fRasterizer = fRun->fRasterizer.get();
+            effects.fMaskFilter = fRun->fMaskFilter.get();
+            fLazyCache->reset(SkGlyphCache::DetachCache(fRun->fTypeface.get(), effects, desc));
+        }
+
+        if (regenGlyphs) {
+            strike = fGlyphCache->getStrike(fLazyCache->get());
+        } else {
+            strike = fSubRun->strike();
+        }
+    }
+
+    Result result;
+    char* currVertex = fBlob->fVertices + fSubRun->vertexStartIndex() +
+                       fCurrGlyph * kVerticesPerGlyph * fVertexStride;
+    result.fFirstVertex = currVertex;
+
+    for (int glyphIdx = fCurrGlyph; glyphIdx < (int)fSubRun->glyphCount(); glyphIdx++) {
+        GrGlyph* glyph = nullptr;
+        if (regenTexCoords) {
+            size_t glyphOffset = glyphIdx + fSubRun->glyphStartIndex();
+
+            if (regenGlyphs) {
+                // Get the id from the old glyph, and use the new strike to lookup
+                // the glyph.
+                GrGlyph::PackedID id = fBlob->fGlyphs[glyphOffset]->fPackedID;
+                fBlob->fGlyphs[glyphOffset] =
+                        strike->getGlyph(id, fSubRun->maskFormat(), fLazyCache->get());
+                SkASSERT(id == fBlob->fGlyphs[glyphOffset]->fPackedID);
+            }
+            glyph = fBlob->fGlyphs[glyphOffset];
+            SkASSERT(glyph && glyph->fMaskFormat == fSubRun->maskFormat());
+
+            if (!fGlyphCache->hasGlyph(glyph) &&
+                !strike->addGlyphToAtlas(fUploadTarget, glyph, fLazyCache->get(),
+                                         fSubRun->maskFormat())) {
+                fBrokenRun = glyphIdx > 0;
+                result.fFinished = false;
+                return result;
+            }
+            fGlyphCache->addGlyphToBulkAndSetUseToken(fSubRun->bulkUseToken(), glyph,
+                                                      fUploadTarget->nextDrawToken());
+        }
+
+        regen_vertices<regenPos, regenCol, regenTexCoords>(currVertex, glyph, fVertexStride,
+                                                           fSubRun->drawAsDistanceFields(), fTransX,
+                                                           fTransY, fColor);
+        currVertex += fVertexStride * GrAtlasTextOp::kVerticesPerGlyph;
+        ++result.fGlyphsRegenerated;
+        ++fCurrGlyph;
+    }
+
+    // We may have changed the color so update it here
+    fSubRun->setColor(fColor);
+    if (regenTexCoords) {
+        if (regenGlyphs) {
+            fSubRun->setStrike(strike);
+        }
+        fSubRun->setAtlasGeneration(fBrokenRun
+                                            ? GrDrawOpAtlas::kInvalidAtlasGeneration
+                                            : fGlyphCache->atlasGeneration(fSubRun->maskFormat()));
+    }
+    return result;
+}
+
+Regenerator::Result Regenerator::regenerate() {
+    uint64_t currentAtlasGen = fGlyphCache->atlasGeneration(fSubRun->maskFormat());
+    // If regenerate() is called multiple times then the atlas gen may have changed. So we check
+    // this each time.
+    if (fSubRun->atlasGeneration() != currentAtlasGen) {
+        fRegenFlags |= kRegenTex;
+    }
+
+    switch (static_cast<RegenMask>(fRegenFlags)) {
+        case kRegenPos:
+            return this->doRegen<true, false, false, false>();
+        case kRegenCol:
+            return this->doRegen<false, true, false, false>();
+        case kRegenTex:
+            return this->doRegen<false, false, true, false>();
+        case kRegenGlyph:
+            return this->doRegen<false, false, true, true>();
+
+        // combinations
+        case kRegenPosCol:
+            return this->doRegen<true, true, false, false>();
+        case kRegenPosTex:
+            return this->doRegen<true, false, true, false>();
+        case kRegenPosTexGlyph:
+            return this->doRegen<true, false, true, true>();
+        case kRegenPosColTex:
+            return this->doRegen<true, true, true, false>();
+        case kRegenPosColTexGlyph:
+            return this->doRegen<true, true, true, true>();
+        case kRegenColTex:
+            return this->doRegen<false, true, true, false>();
+        case kRegenColTexGlyph:
+            return this->doRegen<false, true, true, true>();
+        case kNoRegen: {
+            Result result;
+            result.fGlyphsRegenerated = fSubRun->glyphCount() - fCurrGlyph;
+            result.fFirstVertex = fBlob->fVertices + fSubRun->vertexStartIndex() +
+                                  fCurrGlyph * kVerticesPerGlyph * fVertexStride;
+            fCurrGlyph = fSubRun->glyphCount();
+
+            // set use tokens for all of the glyphs in our subrun.  This is only valid if we
+            // have a valid atlas generation
+            fGlyphCache->setUseTokenBulk(*fSubRun->bulkUseToken(), fUploadTarget->nextDrawToken(),
+                                         fSubRun->maskFormat());
+            return result;
+        }
+    }
+    SK_ABORT("Should not get here");
+    return Result();
+}
diff --git a/src/gpu/text/GrAtlasTextBlob_regenInOp.cpp b/src/gpu/text/GrAtlasTextBlob_regenInOp.cpp
deleted file mode 100644
index e081158..0000000
--- a/src/gpu/text/GrAtlasTextBlob_regenInOp.cpp
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "GrAtlasTextBlob.h"
-
-#include "GrOpFlushState.h"
-#include "GrTextUtils.h"
-
-#include "SkDistanceFieldGen.h"
-#include "SkGlyphCache.h"
-
-#include "ops/GrAtlasTextOp.h"
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// A large template to handle regenerating the vertices of a textblob with as few branches as
-// possible
-template <bool regenPos, bool regenCol, bool regenTexCoords>
-inline void regen_vertices(intptr_t vertex, const GrGlyph* glyph, size_t vertexStride,
-                           bool useDistanceFields, SkScalar transX, SkScalar transY,
-                           GrColor color) {
-    uint16_t u0, v0, u1, v1;
-    if (regenTexCoords) {
-        SkASSERT(glyph);
-        int width = glyph->fBounds.width();
-        int height = glyph->fBounds.height();
-
-        if (useDistanceFields) {
-            u0 = glyph->fAtlasLocation.fX + SK_DistanceFieldInset;
-            v0 = glyph->fAtlasLocation.fY + SK_DistanceFieldInset;
-            u1 = u0 + width - 2 * SK_DistanceFieldInset;
-            v1 = v0 + height - 2 * SK_DistanceFieldInset;
-        } else {
-            u0 = glyph->fAtlasLocation.fX;
-            v0 = glyph->fAtlasLocation.fY;
-            u1 = u0 + width;
-            v1 = v0 + height;
-        }
-        // We pack the 2bit page index in the low bit of the u and v texture coords
-        uint32_t pageIndex = glyph->pageIndex();
-        SkASSERT(pageIndex < 4);
-        uint16_t uBit = (pageIndex >> 1) & 0x1;
-        uint16_t vBit = pageIndex & 0x1;
-        u0 <<= 1;
-        u0 |= uBit;
-        v0 <<= 1;
-        v0 |= vBit;
-        u1 <<= 1;
-        u1 |= uBit;
-        v1 <<= 1;
-        v1 |= vBit;
-    }
-
-    // This is a bit wonky, but sometimes we have LCD text, in which case we won't have color
-    // vertices, hence vertexStride - sizeof(SkIPoint16)
-    intptr_t colorOffset = sizeof(SkPoint);
-    intptr_t texCoordOffset = vertexStride - sizeof(SkIPoint16);
-
-    // V0
-    if (regenPos) {
-        SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
-        point->fX += transX;
-        point->fY += transY;
-    }
-
-    if (regenCol) {
-        SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
-        *vcolor = color;
-    }
-
-    if (regenTexCoords) {
-        uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
-        textureCoords[0] = u0;
-        textureCoords[1] = v0;
-    }
-    vertex += vertexStride;
-
-    // V1
-    if (regenPos) {
-        SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
-        point->fX += transX;
-        point->fY += transY;
-    }
-
-    if (regenCol) {
-        SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
-        *vcolor = color;
-    }
-
-    if (regenTexCoords) {
-        uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
-        textureCoords[0] = u0;
-        textureCoords[1] = v1;
-    }
-    vertex += vertexStride;
-
-    // V2
-    if (regenPos) {
-        SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
-        point->fX += transX;
-        point->fY += transY;
-    }
-
-    if (regenCol) {
-        SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
-        *vcolor = color;
-    }
-
-    if (regenTexCoords) {
-        uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
-        textureCoords[0] = u1;
-        textureCoords[1] = v0;
-    }
-    vertex += vertexStride;
-
-    // V3
-    if (regenPos) {
-        SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
-        point->fX += transX;
-        point->fY += transY;
-    }
-
-    if (regenCol) {
-        SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
-        *vcolor = color;
-    }
-
-    if (regenTexCoords) {
-        uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
-        textureCoords[0] = u1;
-        textureCoords[1] = v1;
-    }
-}
-
-template <bool regenPos, bool regenCol, bool regenTexCoords, bool regenGlyphs>
-void GrAtlasTextBlob::regenInOp(GrDeferredUploadTarget* target, GrAtlasGlyphCache* fontCache,
-                                GrBlobRegenHelper* helper, Run* run, Run::SubRunInfo* info,
-                                SkAutoGlyphCache* lazyCache, int glyphCount, size_t vertexStride,
-                                GrColor color, SkScalar transX, SkScalar transY) const {
-    SkASSERT(lazyCache);
-    static_assert(!regenGlyphs || regenTexCoords, "must regenTexCoords along regenGlyphs");
-    GrAtlasTextStrike* strike = nullptr;
-    if (regenTexCoords) {
-        info->resetBulkUseToken();
-
-        const SkDescriptor* desc = (run->fOverrideDescriptor && !info->drawAsDistanceFields())
-                                      ? run->fOverrideDescriptor->getDesc()
-                                      : run->fDescriptor.getDesc();
-
-        if (!*lazyCache || (*lazyCache)->getDescriptor() != *desc) {
-            SkScalerContextEffects effects;
-            effects.fPathEffect = run->fPathEffect.get();
-            effects.fRasterizer = run->fRasterizer.get();
-            effects.fMaskFilter = run->fMaskFilter.get();
-            lazyCache->reset(SkGlyphCache::DetachCache(run->fTypeface.get(), effects, desc));
-        }
-
-        if (regenGlyphs) {
-            strike = fontCache->getStrike(lazyCache->get());
-        } else {
-            strike = info->strike();
-        }
-    }
-
-    bool brokenRun = false;
-    intptr_t vertex = reinterpret_cast<intptr_t>(fVertices);
-    vertex += info->vertexStartIndex();
-    for (int glyphIdx = 0; glyphIdx < glyphCount; glyphIdx++) {
-        GrGlyph* glyph = nullptr;
-        if (regenTexCoords) {
-            size_t glyphOffset = glyphIdx + info->glyphStartIndex();
-
-            if (regenGlyphs) {
-                // Get the id from the old glyph, and use the new strike to lookup
-                // the glyph.
-                GrGlyph::PackedID id = fGlyphs[glyphOffset]->fPackedID;
-                fGlyphs[glyphOffset] = strike->getGlyph(id, info->maskFormat(), lazyCache->get());
-                SkASSERT(id == fGlyphs[glyphOffset]->fPackedID);
-            }
-            glyph = fGlyphs[glyphOffset];
-            SkASSERT(glyph && glyph->fMaskFormat == info->maskFormat());
-
-            if (!fontCache->hasGlyph(glyph) &&
-                !strike->addGlyphToAtlas(target, glyph, lazyCache->get(), info->maskFormat())) {
-                helper->flush();
-                brokenRun = glyphIdx > 0;
-
-                SkDEBUGCODE(bool success =) strike->addGlyphToAtlas(target,
-                                                                    glyph,
-                                                                    lazyCache->get(),
-                                                                    info->maskFormat());
-                SkASSERT(success);
-            }
-            fontCache->addGlyphToBulkAndSetUseToken(info->bulkUseToken(), glyph,
-                                                    target->nextDrawToken());
-        }
-
-        regen_vertices<regenPos, regenCol, regenTexCoords>(vertex, glyph, vertexStride,
-                                                           info->drawAsDistanceFields(),
-                                                           transX, transY, color);
-        vertex += vertexStride * GrAtlasTextOp::kVerticesPerGlyph;
-        helper->incGlyphCount();
-    }
-
-    // We may have changed the color so update it here
-    info->setColor(color);
-    if (regenTexCoords) {
-        if (regenGlyphs) {
-            info->setStrike(strike);
-        }
-        info->setAtlasGeneration(brokenRun ? GrDrawOpAtlas::kInvalidAtlasGeneration
-                                           : fontCache->atlasGeneration(info->maskFormat()));
-    }
-}
-
-enum RegenMask {
-    kNoRegen    = 0x0,
-    kRegenPos   = 0x1,
-    kRegenCol   = 0x2,
-    kRegenTex   = 0x4,
-    kRegenGlyph = 0x8 | kRegenTex, // we have to regenerate the texture coords when we regen glyphs
-
-    // combinations
-    kRegenPosCol = kRegenPos | kRegenCol,
-    kRegenPosTex = kRegenPos | kRegenTex,
-    kRegenPosTexGlyph = kRegenPos | kRegenGlyph,
-    kRegenPosColTex = kRegenPos | kRegenCol | kRegenTex,
-    kRegenPosColTexGlyph = kRegenPos | kRegenCol | kRegenGlyph,
-    kRegenColTex = kRegenCol | kRegenTex,
-    kRegenColTexGlyph = kRegenCol | kRegenGlyph,
-};
-
-#define REGEN_ARGS target, fontCache, helper, &run, &info, lazyCache, \
-                   *glyphCount, vertexStride, color, transX, transY
-
-void GrAtlasTextBlob::regenInOp(GrDeferredUploadTarget* target, GrAtlasGlyphCache* fontCache,
-                                GrBlobRegenHelper* helper, int runIndex, int subRunIndex,
-                                SkAutoGlyphCache* lazyCache, size_t vertexStride,
-                                const SkMatrix& viewMatrix, SkScalar x, SkScalar y, GrColor color,
-                                void** vertices, size_t* byteCount, int* glyphCount) {
-    Run& run = fRuns[runIndex];
-    Run::SubRunInfo& info = run.fSubRunInfo[subRunIndex];
-
-    uint64_t currentAtlasGen = fontCache->atlasGeneration(info.maskFormat());
-
-    // Compute translation if any
-    SkScalar transX, transY;
-    info.computeTranslation(viewMatrix, x, y, &transX, &transY);
-
-    // Because the GrAtlasGlyphCache may evict the strike a blob depends on using for
-    // generating its texture coords, we have to track whether or not the strike has
-    // been abandoned.  If it hasn't been abandoned, then we can use the GrGlyph*s as is
-    // otherwise we have to get the new strike, and use that to get the correct glyphs.
-    // Because we do not have the packed ids, and thus can't look up our glyphs in the
-    // new strike, we instead keep our ref to the old strike and use the packed ids from
-    // it.  These ids will still be valid as long as we hold the ref.  When we are done
-    // updating our cache of the GrGlyph*s, we drop our ref on the old strike
-    bool regenerateGlyphs = info.strike()->isAbandoned();
-    bool regenerateTextureCoords = info.atlasGeneration() != currentAtlasGen ||
-                                   regenerateGlyphs;
-    bool regenerateColors = kARGB_GrMaskFormat != info.maskFormat() &&
-                            info.color() != color;
-    bool regeneratePositions = transX != 0.f || transY != 0.f;
-    *glyphCount = info.glyphCount();
-
-    uint32_t regenMaskBits = kNoRegen;
-    regenMaskBits |= regeneratePositions ? kRegenPos : 0;
-    regenMaskBits |= regenerateColors ? kRegenCol : 0;
-    regenMaskBits |= regenerateTextureCoords ? kRegenTex : 0;
-    regenMaskBits |= regenerateGlyphs ? kRegenGlyph : 0;
-    RegenMask regenMask = (RegenMask)regenMaskBits;
-
-    switch (regenMask) {
-        case kRegenPos:
-            this->regenInOp<true, false, false, false>(REGEN_ARGS);
-            break;
-        case kRegenCol:
-            this->regenInOp<false, true, false, false>(REGEN_ARGS);
-            break;
-        case kRegenTex:
-            this->regenInOp<false, false, true, false>(REGEN_ARGS);
-            break;
-        case kRegenGlyph:
-            this->regenInOp<false, false, true, true>(REGEN_ARGS);
-            break;
-
-        // combinations
-        case kRegenPosCol:
-            this->regenInOp<true, true, false, false>(REGEN_ARGS);
-            break;
-        case kRegenPosTex:
-            this->regenInOp<true, false, true, false>(REGEN_ARGS);
-            break;
-        case kRegenPosTexGlyph:
-            this->regenInOp<true, false, true, true>(REGEN_ARGS);
-            break;
-        case kRegenPosColTex:
-            this->regenInOp<true, true, true, false>(REGEN_ARGS);
-            break;
-        case kRegenPosColTexGlyph:
-            this->regenInOp<true, true, true, true>(REGEN_ARGS);
-            break;
-        case kRegenColTex:
-            this->regenInOp<false, true, true, false>(REGEN_ARGS);
-            break;
-        case kRegenColTexGlyph:
-            this->regenInOp<false, true, true, true>(REGEN_ARGS);
-            break;
-        case kNoRegen:
-            helper->incGlyphCount(*glyphCount);
-
-            // set use tokens for all of the glyphs in our subrun.  This is only valid if we
-            // have a valid atlas generation
-            fontCache->setUseTokenBulk(*info.bulkUseToken(), target->nextDrawToken(),
-                                        info.maskFormat());
-            break;
-    }
-
-    *byteCount = info.byteCount();
-    *vertices = fVertices + info.vertexStartIndex();
-}