Roll external/skia 61ffd53a9..b49d7b011 (5 commits)

https://skia.googlesource.com/skia.git/+log/61ffd53a9..b49d7b011

2017-11-07 liyuqian@google.com This is a reland of 67340. This CL fixes the broken layout tests by preserving the containedInClip boolean. We will eventually remove it and rebaseline the layout tests.
2017-11-07 csmartdalton@google.com Fix dangling pointers when Ganesh culls CCPR Ops early
2017-11-07 bsalomon@google.com Make GrAtlasTextBlob return to caller when a flush is required during subrun tessellation.
2017-11-07 rmistry@google.com Add section for how to connect to Skia swarming bots
2017-11-07 csmartdalton@google.com Add clipping options to path text bench and samples

The AutoRoll server is located here: https://android-roll.skia.org

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+/master/autoroll/README.md

If the roll is causing failures, please contact the current sheriff, who should
be CC'd on the roll, and stop the roller if necessary.

Test: Presubmit checks will test this change.
Change-Id: Ia5fc3396761cfe7f508a55d08cddf3decf1d9c89
Exempt-From-Owner-Approval: The autoroll bot does not require owner approval.
diff --git a/Android.bp b/Android.bp
index 1b70283..e668fb5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -601,7 +601,7 @@
         "src/gpu/ops/GrTextureOp.cpp",
         "src/gpu/text/GrAtlasGlyphCache.cpp",
         "src/gpu/text/GrAtlasTextBlob.cpp",
-        "src/gpu/text/GrAtlasTextBlob_regenInOp.cpp",
+        "src/gpu/text/GrAtlasTextBlobVertexRegenerator.cpp",
         "src/gpu/text/GrAtlasTextContext.cpp",
         "src/gpu/text/GrDistanceFieldAdjustTable.cpp",
         "src/gpu/text/GrStencilAndCoverTextContext.cpp",
diff --git a/bench/PathTextBench.cpp b/bench/PathTextBench.cpp
index 6551fa5..8920223 100644
--- a/bench/PathTextBench.cpp
+++ b/bench/PathTextBench.cpp
@@ -11,6 +11,7 @@
 #include "SkPaint.h"
 #include "SkPath.h"
 #include "SkRandom.h"
+#include "sk_tool_utils.h"
 
 static constexpr int kScreenWidth = 1500;
 static constexpr int kScreenHeight = 1500;
@@ -27,12 +28,19 @@
  */
 class PathTextBench : public Benchmark {
 public:
-    PathTextBench(bool cached) : fCached(cached) {}
+    PathTextBench(bool clipped, bool uncached) : fClipped(clipped), fUncached(uncached) {}
     bool isVisual() override { return true; }
 
 private:
     const char* onGetName() override {
-        return fCached ? "path_text" : "path_text_uncached";
+        fName = "path_text";
+        if (fClipped) {
+            fName.append("_clipped");
+        }
+        if (fUncached) {
+            fName.append("_uncached");
+        }
+        return fName.c_str();
     }
     SkIPoint onGetSize() override { return SkIPoint::Make(kScreenWidth, kScreenHeight); }
 
@@ -43,7 +51,7 @@
         for (int i = 0; i < kNumGlyphs; ++i) {
             SkGlyphID id = cache->unicharToGlyph(kGlyphs[i]);
             cache->getScalerContext()->getPath(SkPackedGlyphID(id), &fGlyphs[i]);
-            fGlyphs[i].setIsVolatile(!fCached);
+            fGlyphs[i].setIsVolatile(fUncached);
         }
 
         SkRandom rand;
@@ -67,10 +75,18 @@
             fPaints[i].setAntiAlias(true);
             fPaints[i].setColor(rand.nextU() | 0x80808080);
         }
+
+        if (fClipped) {
+            fClipPath = sk_tool_utils::make_star(SkRect::MakeIWH(kScreenWidth,kScreenHeight), 11,3);
+            fClipPath.setIsVolatile(fUncached);
+        }
     }
 
     void onDraw(int loops, SkCanvas* canvas) override {
         SkAutoCanvasRestore acr(canvas, true);
+        if (fClipped) {
+            canvas->clipPath(fClipPath, SkClipOp::kIntersect, true);
+        }
         for (int i = 0; i < kNumDraws; ++i) {
             const SkPath& glyph = fGlyphs[i % kNumGlyphs];
             canvas->setMatrix(fXforms[i]);
@@ -78,13 +94,17 @@
         }
     }
 
-    const bool fCached;
+    const bool fClipped;
+    const bool fUncached;
+    SkString fName;
     SkPath fGlyphs[kNumGlyphs];
     SkPaint fPaints[kNumDraws];
     SkMatrix fXforms[kNumDraws];
+    SkPath fClipPath;
 
     typedef Benchmark INHERITED;
 };
 
-DEF_BENCH(return new PathTextBench(false);)
-DEF_BENCH(return new PathTextBench(true);)
+DEF_BENCH(return new PathTextBench(false, false);)
+DEF_BENCH(return new PathTextBench(false, true);)
+DEF_BENCH(return new PathTextBench(true, true);)
diff --git a/gn/gpu.gni b/gn/gpu.gni
index ef60312..e67b2ac 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -387,7 +387,7 @@
   "$_src/gpu/text/GrAtlasGlyphCache.cpp",
   "$_src/gpu/text/GrAtlasGlyphCache.h",
   "$_src/gpu/text/GrAtlasTextBlob.cpp",
-  "$_src/gpu/text/GrAtlasTextBlob_regenInOp.cpp",
+  "$_src/gpu/text/GrAtlasTextBlobVertexRegenerator.cpp",
   "$_src/gpu/text/GrAtlasTextBlob.h",
   "$_src/gpu/text/GrAtlasTextContext.cpp",
   "$_src/gpu/text/GrAtlasTextContext.h",
diff --git a/samplecode/SamplePathText.cpp b/samplecode/SamplePathText.cpp
index 5df223b..12bb282 100644
--- a/samplecode/SamplePathText.cpp
+++ b/samplecode/SamplePathText.cpp
@@ -13,6 +13,7 @@
 #include "SkPath.h"
 #include "SkRandom.h"
 #include "SkTaskGroup.h"
+#include "sk_tool_utils.h"
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // Static text from paths.
@@ -21,7 +22,7 @@
     constexpr static int kNumPaths = 1500;
     virtual const char* getName() const { return "PathText"; }
 
-    PathText() : fRand(25) {
+    PathText() {
         SkPaint defaultPaint;
         SkAutoGlyphCache agc(defaultPaint, nullptr, &SkMatrix::I());
         SkGlyphCache* cache = agc.getCache();
@@ -53,10 +54,32 @@
             SampleCode::TitleR(evt, this->getName());
             return true;
         }
+        SkUnichar unichar;
+        if (SampleCode::CharQ(*evt, &unichar)) {
+            if (unichar == 'X') {
+                fDoClip = !fDoClip;
+                this->inval(nullptr);
+                return true;
+            }
+        }
         return this->INHERITED::onQuery(evt);
     }
 
     void onDrawContent(SkCanvas* canvas) override {
+        if (fDoClip) {
+            SkMatrix oldMatrix = canvas->getTotalMatrix();
+            canvas->setMatrix(SkMatrix::MakeScale(this->width(), this->height()));
+            canvas->save();
+            canvas->clipPath(fClipPath, SkClipOp::kDifference, true);
+            canvas->clear(SK_ColorBLACK);
+            canvas->restore();
+            canvas->clipPath(fClipPath, SkClipOp::kIntersect, true);
+            canvas->setMatrix(oldMatrix);
+        }
+        this->drawGlyphs(canvas);
+    }
+
+    virtual void drawGlyphs(SkCanvas* canvas) {
         for (Glyph& glyph : fGlyphs) {
             SkAutoCanvasRestore acr(canvas, true);
             canvas->translate(glyph.fPosition.x(), glyph.fPosition.y());
@@ -81,7 +104,9 @@
     };
 
     Glyph      fGlyphs[kNumPaths];
-    SkRandom   fRand;
+    SkRandom   fRand{25};
+    SkPath     fClipPath = sk_tool_utils::make_star(SkRect{0,0,1,1}, 11, 3);
+    bool       fDoClip = false;
 
     typedef SampleView INHERITED;
 };
@@ -193,7 +218,7 @@
         std::swap(fFrontMatrices, fBackMatrices);
     }
 
-    void onDrawContent(SkCanvas* canvas) override {
+    void drawGlyphs(SkCanvas* canvas) override {
         for (int i = 0; i < kNumPaths; ++i) {
             SkAutoCanvasRestore acr(canvas, true);
             canvas->concat(fFrontMatrices[i]);
@@ -299,7 +324,7 @@
         fFrontPaths.swap(fBackPaths);
     }
 
-    void onDrawContent(SkCanvas* canvas) override {
+    void drawGlyphs(SkCanvas* canvas) override {
         for (int i = 0; i < kNumPaths; ++i) {
             canvas->drawPath(fFrontPaths[i], fGlyphs[i].fPaint);
         }
diff --git a/site/dev/sheriffing/trooper.md b/site/dev/sheriffing/trooper.md
index e49d7cf..c21af03 100644
--- a/site/dev/sheriffing/trooper.md
+++ b/site/dev/sheriffing/trooper.md
@@ -49,31 +49,12 @@
 
 - Install the Skia trooper Chrome extension (available [here](https://chrome.google.com/webstore/a/google.com/detail/alerts-for-skia-troopers/fpljhfiomnfioecagooiekldeolcpief)) to be able to see alerts quickly in the browser.
 
-- Where machines are located:
-  - Machine name like "skia-gce-NNN", "skia-i-gce-NNN", "ct-gce-NNN", "skia-ct-gce-NNN", "ct-xxx-builder-NNN" -> GCE
-  - Machine name ends with "a9", "m3" -> Chrome Golo/Labs
-  - Machine name ends with "m5" -> CT bare-metal bots in Chrome Golo
-  - Machine name starts with "skia-e-", "skia-i-" (other than "skia-i-gce-NNN"), "skia-rpi-" -> Chapel Hill lab
+- See [this section](../testing/swarmingbots#connecting-to-swarming-bots) for details on how to connect to the different Skia bots.
 
 - The [chrome-infra hangout](https://goto.google.com/cit-hangout) is useful for
   questions regarding bots managed by the Chrome Infra team and to get
   visibility into upstream failures that cause problems for us.
 
-- To log in to a Linux bot in GCE, use `gcloud compute ssh default@<machine
-  name>`. Choose the zone listed for the
-  [GCE VM](https://console.cloud.google.com/project/31977622648/compute/instances)
-  (or specify it using the `--zone` command-line flag).
-
-- To log in to a Windows bot in GCE, use
-  [Chrome RDP Extension](https://chrome.google.com/webstore/detail/chrome-rdp/cbkkbcmdlboombapidmoeolnmdacpkch?hl=en-US)
-  with the
-  [IP address of the GCE VM](https://console.cloud.google.com/project/31977622648/compute/instances)
-  shown on the [host info page](https://status.skia.org/hosts) for that bot. The
-  username is chrome-bot and the password can be found on
-  [Valentine](https://valentine.corp.google.com/) as "chrome-bot (Win GCE)".
-
-- To log in to other bots, see the [Skolo maintenance doc](https://docs.google.com/document/d/1zTR1YtrIFBo-fRWgbUgvJNVJ-s_4_sNjTrHIoX2vulo/edit#heading=h.2nq3yd1axg0n) remote access section.
-
 - If there is a problem with a bot in the Chrome Golo or Chrome infra GCE, the
   best course of action is to
   [file a bug](https://code.google.com/p/chromium/issues/entry?template=Build%20Infrastructure)
diff --git a/site/dev/testing/swarmingbots.md b/site/dev/testing/swarmingbots.md
index 4f28ddd..4aa9115 100644
--- a/site/dev/testing/swarmingbots.md
+++ b/site/dev/testing/swarmingbots.md
@@ -20,6 +20,24 @@
 [go/skbl](https://goto.google.com/skbl) lists all Skia Swarming bots.
 
 
+<a name="connecting-to-swarming-bots"></a>
+Connecting to Swarming Bots
+---------------------------
+
+- Machine name like “skia-gce-NNN”, “skia-i-gce-NNN”, “ct-gce-NNN”, “skia-ct-gce-NNN”, “ct-xxx-builder-NNN” -> GCE
+  * To log in to a Linux bot in GCE, use `gcloud compute ssh default@<machine name>`. Choose the zone listed for the [GCE VM][gce instances link] (or specify it using the `--zone` command-line flag).
+  * To log in to a Windows bot in GCE, use [Chrome RDP Extension][chrome rdp extension] with the [IP address of the GCE VM][gce instances link]. The username is chrome-bot and the password can be found on [Valentine](https://goto.google.com/valentine) as "chrome-bot (Win GCE)".
+
+- Machine name ends with “a9”, “m3”, "m5" -> Chrome Golo/Labs
+  * To log in to Golo bots, see [go/swarming-ssh](https://goto.google.com/swarming-ssh).
+
+- Machine name starts with “skia-e-”, “skia-i-” (other than “skia-i-gce-NNN”), “skia-rpi-” -> Chapel Hill lab (aka Skolo)<br/>
+  To log in to Skolo bots, see the [Skolo maintenance doc][remote access] remote access section. See the following for OS specific instructions:<br/>
+  * [Remotely debug an Android device in Skolo][remotely debug android]
+  * [VNC to Skolo Windows bots][vnc to skolo windows]
+  * [ChromeOS Debugging][chromeos debugging]
+
+
 Debugging
 ---------
 
@@ -27,11 +45,9 @@
 run tryjobs (after adding debugging output to the relevant code). In some cases you may also need to
 [create or modify tryjobs](automated_testing#adding-new-jobs).
 
-For Googlers: If you need more control (e.g. to run GDB), the [current Trooper][current trooper] can
-loan a machine/device from the Skolo. All bots are accessible via either SSH or VNC -- see the
-[Skolo maintenance doc remote access section][remote access] and/or get help from the Trooper. You
-can also bring the device back to your desk and connect it to GoogleGuest WiFi or the [Google Test
-Network](http://go/gtn-criteria).
+For Googlers: If you need more control (e.g. to run GDB) and need run to directly on a swarming bot then you can use [leasing.skia.org](https://leasing.skia.org).<br/>
+If that does not work then the [current trooper][current trooper] can help you bring the device back to your desk and connect
+it to GoogleGuest Wifi or the [Google Test Network](http://go/gtn-criteria).
 
 If you need to make changes on a Skolo device, please check with an Infra team member. Most can be
 flashed/imaged back to a clean state, but others can not.
@@ -39,10 +55,18 @@
 If a permanent change needs to be made on the machine (such as an OS or driver update), please [file
 a bug][infra bug] and assign to jcgregorio for reassignment.
 
+[chrome rdp extension]:
+    https://chrome.google.com/webstore/detail/chrome-rdp/cbkkbcmdlboombapidmoeolnmdacpkch?hl=en-US
 [current trooper]: http://skia-tree-status.appspot.com/trooper
 [remote access]:
     https://docs.google.com/document/d/1zTR1YtrIFBo-fRWgbUgvJNVJ-s_4_sNjTrHIoX2vulo/edit#heading=h.2nq3yd1axg0n
 [infra bug]: https://bugs.chromium.org/p/skia/issues/entry?template=Infrastructure+Bug
+[gce instances link]: https://console.cloud.google.com/project/31977622648/compute/instances
+[remotely debug android]: https://docs.google.com/document/d/1nxn7TobfaLNNfhSTiwstOnjV0jCxYUI1uwW0T_V7BYg/
+[vnc to skolo windows]:
+    https://docs.google.com/document/d/1zTR1YtrIFBo-fRWgbUgvJNVJ-s_4_sNjTrHIoX2vulo/edit#heading=h.7cqd856ft0s
+[chromeos debugging]:
+    https://docs.google.com/document/d/1yJ2LLfLzV6pXKjiameid1LHEz1mj71Ob4wySIYxlBdw/edit#heading=h.9arg79l59xrf
 
 Maintenance Tasks
 -----------------
diff --git a/src/core/SkScan.h b/src/core/SkScan.h
index 8bb9d1f..2212d0d 100644
--- a/src/core/SkScan.h
+++ b/src/core/SkScan.h
@@ -89,10 +89,12 @@
                               const SkRegion*, SkBlitter*);
     static void HairLineRgn(const SkPoint[], int count, const SkRegion*, SkBlitter*);
     static void AntiHairLineRgn(const SkPoint[], int count, const SkRegion*, SkBlitter*);
-    static void AAAFillPath(const SkPath& path, const SkRegion& origClip, SkBlitter* blitter,
-                            bool forceRLE = false); // SkAAClip uses forceRLE
-    static void DAAFillPath(const SkPath& path, const SkRegion& origClip, SkBlitter* blitter,
-                            bool forceRLE = false);
+    static void AAAFillPath(const SkPath& path, SkBlitter* blitter, const SkIRect& pathIR,
+                            const SkIRect& clipBounds, bool containedInClip, bool forceRLE);
+    static void DAAFillPath(const SkPath& path, SkBlitter* blitter, const SkIRect& pathIR,
+                            const SkIRect& clipBounds, bool containedInClip, bool forceRLE);
+    static void SAAFillPath(const SkPath& path, SkBlitter* blitter, const SkIRect& pathIR,
+                            const SkIRect& clipBounds, bool containedInClip, bool forceRLE);
 };
 
 /** Assign an SkXRect from a SkIRect, by promoting the src rect's coordinates
diff --git a/src/core/SkScanPriv.h b/src/core/SkScanPriv.h
index 96ee695..544f5f5 100644
--- a/src/core/SkScanPriv.h
+++ b/src/core/SkScanPriv.h
@@ -78,45 +78,6 @@
     return prev;
 }
 
-static bool fitsInsideLimit(const SkRect& r, SkScalar max) {
-    const SkScalar min = -max;
-    return  r.fLeft > min && r.fTop > min &&
-            r.fRight < max && r.fBottom < max;
-}
-
-static int overflows_short_shift(int value, int shift) {
-    const int s = 16 + shift;
-    return (SkLeftShift(value, s) >> s) - value;
-}
-
-/**
-  Would any of the coordinates of this rectangle not fit in a short,
-  when left-shifted by shift?
-*/
-static int rect_overflows_short_shift(SkIRect rect, int shift) {
-    SkASSERT(!overflows_short_shift(8191, shift));
-    SkASSERT(overflows_short_shift(8192, shift));
-    SkASSERT(!overflows_short_shift(32767, 0));
-    SkASSERT(overflows_short_shift(32768, 0));
-
-    // Since we expect these to succeed, we bit-or together
-    // for a tiny extra bit of speed.
-    return overflows_short_shift(rect.fLeft, shift) |
-           overflows_short_shift(rect.fRight, shift) |
-           overflows_short_shift(rect.fTop, shift) |
-           overflows_short_shift(rect.fBottom, shift);
-}
-
-static bool safeRoundOut(const SkRect& src, SkIRect* dst, int32_t maxInt) {
-    const SkScalar maxScalar = SkIntToScalar(maxInt);
-
-    if (fitsInsideLimit(src, maxScalar)) {
-        src.roundOut(dst);
-        return true;
-    }
-    return false;
-}
-
 // Check if the path is a rect and fat enough after clipping; if so, blit it.
 static inline bool TryBlitFatAntiRect(SkBlitter* blitter, const SkPath& path, const SkIRect& clip) {
     SkRect rect;
@@ -134,93 +95,4 @@
     return true;
 }
 
-using FillPathFunc = std::function<void(const SkPath& path, SkBlitter* blitter, bool isInverse,
-        const SkIRect& ir, const SkIRect& clipBounds, bool containedInClip, bool forceRLE)>;
-
-static inline void do_fill_path(const SkPath& path, const SkRegion& origClip, SkBlitter* blitter,
-        bool forceRLE, const int SHIFT, FillPathFunc fillPathFunc) {
-    if (origClip.isEmpty()) {
-        return;
-    }
-
-    const bool isInverse = path.isInverseFillType();
-    SkIRect ir;
-
-    if (!safeRoundOut(path.getBounds(), &ir, SK_MaxS32 >> SHIFT)) {
-        // Bounds can't fit in SkIRect; we'll return without drawing
-        return;
-    }
-    if (ir.isEmpty()) {
-        if (isInverse) {
-            blitter->blitRegion(origClip);
-        }
-        return;
-    }
-
-    // If the intersection of the path bounds and the clip bounds
-    // will overflow 32767 when << by SHIFT, we can't supersample,
-    // so draw without antialiasing.
-    SkIRect clippedIR;
-    if (isInverse) {
-       // If the path is an inverse fill, it's going to fill the entire
-       // clip, and we care whether the entire clip exceeds our limits.
-       clippedIR = origClip.getBounds();
-    } else {
-       if (!clippedIR.intersect(ir, origClip.getBounds())) {
-           return;
-       }
-    }
-    if (rect_overflows_short_shift(clippedIR, SHIFT)) {
-        SkScan::FillPath(path, origClip, blitter);
-        return;
-    }
-
-    // Our antialiasing can't handle a clip larger than 32767, so we restrict
-    // the clip to that limit here. (the runs[] uses int16_t for its index).
-    //
-    // A more general solution (one that could also eliminate the need to
-    // disable aa based on ir bounds (see overflows_short_shift) would be
-    // to tile the clip/target...
-    SkRegion tmpClipStorage;
-    const SkRegion* clipRgn = &origClip;
-    {
-        static const int32_t kMaxClipCoord = 32767;
-        const SkIRect& bounds = origClip.getBounds();
-        if (bounds.fRight > kMaxClipCoord || bounds.fBottom > kMaxClipCoord) {
-            SkIRect limit = { 0, 0, kMaxClipCoord, kMaxClipCoord };
-            tmpClipStorage.op(origClip, limit, SkRegion::kIntersect_Op);
-            clipRgn = &tmpClipStorage;
-        }
-    }
-    // for here down, use clipRgn, not origClip
-
-    SkScanClipper   clipper(blitter, clipRgn, ir);
-    const SkIRect*  clipRect = clipper.getClipRect();
-
-    if (clipper.getBlitter() == nullptr) { // clipped out
-        if (isInverse) {
-            blitter->blitRegion(*clipRgn);
-        }
-        return;
-    }
-
-    SkASSERT(clipper.getClipRect() == nullptr ||
-            *clipper.getClipRect() == clipRgn->getBounds());
-
-    // now use the (possibly wrapped) blitter
-    blitter = clipper.getBlitter();
-
-    if (isInverse) {
-        sk_blit_above(blitter, ir, *clipRgn);
-    }
-
-    SkASSERT(SkIntToScalar(ir.fTop) <= path.getBounds().fTop);
-
-    fillPathFunc(path, blitter, isInverse, ir, clipRgn->getBounds(), clipRect == nullptr, forceRLE);
-
-    if (isInverse) {
-        sk_blit_below(blitter, ir, *clipRgn);
-    }
-}
-
 #endif
diff --git a/src/core/SkScan_AAAPath.cpp b/src/core/SkScan_AAAPath.cpp
index d62b151..f9445d2 100644
--- a/src/core/SkScan_AAAPath.cpp
+++ b/src/core/SkScan_AAAPath.cpp
@@ -1672,48 +1672,45 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-void SkScan::AAAFillPath(const SkPath& path, const SkRegion& origClip, SkBlitter* blitter,
-                         bool forceRLE) {
-    FillPathFunc fillPathFunc = [](const SkPath& path, SkBlitter* blitter, bool isInverse,
-            const SkIRect& ir, const SkIRect& clipBounds, bool containedInClip, bool forceRLE){
-        // The mask blitter (where we store intermediate alpha values directly in a mask, and then
-        // call the real blitter once in the end to blit the whole mask) is faster than the RLE
-        // blitter when the blit region is small enough (i.e., canHandleRect(ir)).
-        // When isInverse is true, the blit region is no longer ir so we won't use the mask blitter.
-        // The caller may also use the forceRLE flag to force not using the mask blitter.
-        // Also, when the path is a simple rect, preparing a mask and blitting it might have too
-        // much overhead. Hence we'll use blitFatAntiRect to avoid the mask and its overhead.
-        if (MaskAdditiveBlitter::canHandleRect(ir) && !isInverse && !forceRLE) {
+void SkScan::AAAFillPath(const SkPath& path, SkBlitter* blitter, const SkIRect& ir,
+                         const SkIRect& clipBounds, bool containedInClip, bool forceRLE) {
+    bool isInverse = path.isInverseFillType();
+
+    // The mask blitter (where we store intermediate alpha values directly in a mask, and then call
+    // the real blitter once in the end to blit the whole mask) is faster than the RLE blitter when
+    // the blit region is small enough (i.e., canHandleRect(ir)). When isInverse is true, the blit
+    // region is no longer the rectangle ir so we won't use the mask blitter. The caller may also
+    // use the forceRLE flag to force not using the mask blitter. Also, when the path is a simple
+    // rect, preparing a mask and blitting it might have too much overhead. Hence we'll use
+    // blitFatAntiRect to avoid the mask and its overhead.
+    if (MaskAdditiveBlitter::canHandleRect(ir) && !isInverse && !forceRLE) {
 #ifdef SK_SUPPORT_LEGACY_SMALLRECT_AA
+        MaskAdditiveBlitter additiveBlitter(blitter, ir, clipBounds, isInverse);
+        aaa_fill_path(path, clipBounds, &additiveBlitter, ir.fTop, ir.fBottom,
+                containedInClip, true, forceRLE);
+#else
+        // blitFatAntiRect is slower than the normal AAA flow without MaskAdditiveBlitter.
+        // Hence only tryBlitFatAntiRect when MaskAdditiveBlitter would have been used.
+        if (!TryBlitFatAntiRect(blitter, path, clipBounds)) {
             MaskAdditiveBlitter additiveBlitter(blitter, ir, clipBounds, isInverse);
             aaa_fill_path(path, clipBounds, &additiveBlitter, ir.fTop, ir.fBottom,
                     containedInClip, true, forceRLE);
-#else
-            // blitFatAntiRect is slower than the normal AAA flow without MaskAdditiveBlitter.
-            // Hence only tryBlitFatAntiRect when MaskAdditiveBlitter would have been used.
-            if (!TryBlitFatAntiRect(blitter, path, clipBounds)) {
-                MaskAdditiveBlitter additiveBlitter(blitter, ir, clipBounds, isInverse);
-                aaa_fill_path(path, clipBounds, &additiveBlitter, ir.fTop, ir.fBottom,
-                        containedInClip, true, forceRLE);
-            }
-#endif
-        } else if (!isInverse && path.isConvex()) {
-            // If the filling area is convex (i.e., path.isConvex && !isInverse), our simpler
-            // aaa_walk_convex_edges won't generate alphas above 255. Hence we don't need
-            // SafeRLEAdditiveBlitter (which is slow due to clamping). The basic RLE blitter
-            // RunBasedAdditiveBlitter would suffice.
-            RunBasedAdditiveBlitter additiveBlitter(blitter, ir, clipBounds, isInverse);
-            aaa_fill_path(path, clipBounds, &additiveBlitter, ir.fTop, ir.fBottom,
-                    containedInClip, false, forceRLE);
-        } else {
-            // If the filling area might not be convex, the more involved aaa_walk_edges would
-            // be called and we have to clamp the alpha downto 255. The SafeRLEAdditiveBlitter
-            // does that at a cost of performance.
-            SafeRLEAdditiveBlitter additiveBlitter(blitter, ir, clipBounds, isInverse);
-            aaa_fill_path(path, clipBounds, &additiveBlitter, ir.fTop, ir.fBottom,
-                    containedInClip, false, forceRLE);
         }
-    };
-
-    do_fill_path(path, origClip, blitter, forceRLE, 2, std::move(fillPathFunc));
+#endif
+    } else if (!isInverse && path.isConvex()) {
+        // If the filling area is convex (i.e., path.isConvex && !isInverse), our simpler
+        // aaa_walk_convex_edges won't generate alphas above 255. Hence we don't need
+        // SafeRLEAdditiveBlitter (which is slow due to clamping). The basic RLE blitter
+        // RunBasedAdditiveBlitter would suffice.
+        RunBasedAdditiveBlitter additiveBlitter(blitter, ir, clipBounds, isInverse);
+        aaa_fill_path(path, clipBounds, &additiveBlitter, ir.fTop, ir.fBottom,
+                containedInClip, false, forceRLE);
+    } else {
+        // If the filling area might not be convex, the more involved aaa_walk_edges would
+        // be called and we have to clamp the alpha downto 255. The SafeRLEAdditiveBlitter
+        // does that at a cost of performance.
+        SafeRLEAdditiveBlitter additiveBlitter(blitter, ir, clipBounds, isInverse);
+        aaa_fill_path(path, clipBounds, &additiveBlitter, ir.fTop, ir.fBottom,
+                containedInClip, false, forceRLE);
+    }
 }
diff --git a/src/core/SkScan_AntiPath.cpp b/src/core/SkScan_AntiPath.cpp
index ba42a72..1178c9c 100644
--- a/src/core/SkScan_AntiPath.cpp
+++ b/src/core/SkScan_AntiPath.cpp
@@ -613,33 +613,155 @@
     return path.countPoints() < SkTMax(bounds.width(), bounds.height()) / 2 - 10;
 }
 
+void SkScan::SAAFillPath(const SkPath& path, SkBlitter* blitter, const SkIRect& ir,
+                  const SkIRect& clipBounds, bool containedInClip, bool forceRLE) {
+    bool isInverse = path.isInverseFillType();
+
+    // MaskSuperBlitter can't handle drawing outside of ir, so we can't use it
+    // if we're an inverse filltype
+    if (!isInverse && MaskSuperBlitter::CanHandleRect(ir) && !forceRLE) {
+        MaskSuperBlitter superBlit(blitter, ir, clipBounds, isInverse);
+        SkASSERT(SkIntToScalar(ir.fTop) <= path.getBounds().fTop);
+        sk_fill_path(path, clipBounds, &superBlit, ir.fTop, ir.fBottom, SHIFT, containedInClip);
+    } else {
+        SuperBlitter superBlit(blitter, ir, clipBounds, isInverse);
+        sk_fill_path(path, clipBounds, &superBlit, ir.fTop, ir.fBottom, SHIFT, containedInClip);
+    }
+}
+
+static bool fitsInsideLimit(const SkRect& r, SkScalar max) {
+    const SkScalar min = -max;
+    return  r.fLeft > min && r.fTop > min &&
+            r.fRight < max && r.fBottom < max;
+}
+
+static int overflows_short_shift(int value, int shift) {
+    const int s = 16 + shift;
+    return (SkLeftShift(value, s) >> s) - value;
+}
+
+/**
+  Would any of the coordinates of this rectangle not fit in a short,
+  when left-shifted by shift?
+*/
+static int rect_overflows_short_shift(SkIRect rect, int shift) {
+    SkASSERT(!overflows_short_shift(8191, shift));
+    SkASSERT(overflows_short_shift(8192, shift));
+    SkASSERT(!overflows_short_shift(32767, 0));
+    SkASSERT(overflows_short_shift(32768, 0));
+
+    // Since we expect these to succeed, we bit-or together
+    // for a tiny extra bit of speed.
+    return overflows_short_shift(rect.fLeft, shift) |
+           overflows_short_shift(rect.fRight, shift) |
+           overflows_short_shift(rect.fTop, shift) |
+           overflows_short_shift(rect.fBottom, shift);
+}
+
+static bool safeRoundOut(const SkRect& src, SkIRect* dst, int32_t maxInt) {
+    const SkScalar maxScalar = SkIntToScalar(maxInt);
+
+    if (fitsInsideLimit(src, maxScalar)) {
+        src.roundOut(dst);
+        return true;
+    }
+    return false;
+}
+
 void SkScan::AntiFillPath(const SkPath& path, const SkRegion& origClip,
                           SkBlitter* blitter, bool forceRLE) {
-    if (ShouldUseDAA(path)) {
-        SkScan::DAAFillPath(path, origClip, blitter, forceRLE);
-        return;
-    } else if (ShouldUseAAA(path)) {
-        // Do not use AAA if path is too complicated:
-        // there won't be any speedup or significant visual improvement.
-        SkScan::AAAFillPath(path, origClip, blitter, forceRLE);
+    if (origClip.isEmpty()) {
         return;
     }
 
-    FillPathFunc fillPathFunc = [](const SkPath& path, SkBlitter* blitter, bool isInverse,
-            const SkIRect& ir, const SkIRect& clipBounds, bool containedInClip, bool forceRLE){
-        // MaskSuperBlitter can't handle drawing outside of ir, so we can't use it
-        // if we're an inverse filltype
-        if (!isInverse && MaskSuperBlitter::CanHandleRect(ir) && !forceRLE) {
-            MaskSuperBlitter    superBlit(blitter, ir, clipBounds, isInverse);
-            SkASSERT(SkIntToScalar(ir.fTop) <= path.getBounds().fTop);
-            sk_fill_path(path, clipBounds, &superBlit, ir.fTop, ir.fBottom, SHIFT, containedInClip);
-        } else {
-            SuperBlitter    superBlit(blitter, ir, clipBounds, isInverse);
-            sk_fill_path(path, clipBounds, &superBlit, ir.fTop, ir.fBottom, SHIFT, containedInClip);
-        }
-    };
+    const bool isInverse = path.isInverseFillType();
+    SkIRect ir;
 
-    do_fill_path(path, origClip, blitter, forceRLE, SHIFT, std::move(fillPathFunc));
+    if (!safeRoundOut(path.getBounds(), &ir, SK_MaxS32 >> SHIFT)) {
+        // Bounds can't fit in SkIRect; we'll return without drawing
+        return;
+    }
+    if (ir.isEmpty()) {
+        if (isInverse) {
+            blitter->blitRegion(origClip);
+        }
+        return;
+    }
+
+    // If the intersection of the path bounds and the clip bounds
+    // will overflow 32767 when << by SHIFT, we can't supersample,
+    // so draw without antialiasing.
+    SkIRect clippedIR;
+    if (isInverse) {
+       // If the path is an inverse fill, it's going to fill the entire
+       // clip, and we care whether the entire clip exceeds our limits.
+       clippedIR = origClip.getBounds();
+    } else {
+       if (!clippedIR.intersect(ir, origClip.getBounds())) {
+           return;
+       }
+    }
+    if (rect_overflows_short_shift(clippedIR, SHIFT)) {
+        SkScan::FillPath(path, origClip, blitter);
+        return;
+    }
+
+    // Our antialiasing can't handle a clip larger than 32767, so we restrict
+    // the clip to that limit here. (the runs[] uses int16_t for its index).
+    //
+    // A more general solution (one that could also eliminate the need to
+    // disable aa based on ir bounds (see overflows_short_shift) would be
+    // to tile the clip/target...
+    SkRegion tmpClipStorage;
+    const SkRegion* clipRgn = &origClip;
+    {
+        static const int32_t kMaxClipCoord = 32767;
+        const SkIRect& bounds = origClip.getBounds();
+        if (bounds.fRight > kMaxClipCoord || bounds.fBottom > kMaxClipCoord) {
+            SkIRect limit = { 0, 0, kMaxClipCoord, kMaxClipCoord };
+            tmpClipStorage.op(origClip, limit, SkRegion::kIntersect_Op);
+            clipRgn = &tmpClipStorage;
+        }
+    }
+    // for here down, use clipRgn, not origClip
+
+    SkScanClipper   clipper(blitter, clipRgn, ir);
+
+    if (clipper.getBlitter() == nullptr) { // clipped out
+        if (isInverse) {
+            blitter->blitRegion(*clipRgn);
+        }
+        return;
+    }
+
+    SkASSERT(clipper.getClipRect() == nullptr ||
+            *clipper.getClipRect() == clipRgn->getBounds());
+
+    // now use the (possibly wrapped) blitter
+    blitter = clipper.getBlitter();
+
+    if (isInverse) {
+        sk_blit_above(blitter, ir, *clipRgn);
+    }
+
+    SkASSERT(SkIntToScalar(ir.fTop) <= path.getBounds().fTop);
+
+    if (ShouldUseDAA(path)) {
+        SkScan::DAAFillPath(path, blitter, ir, clipRgn->getBounds(),
+                            clipper.getClipRect() == nullptr, forceRLE);
+    } else if (ShouldUseAAA(path)) {
+        // Do not use AAA if path is too complicated:
+        // there won't be any speedup or significant visual improvement.
+        SkScan::AAAFillPath(path, blitter, ir, clipRgn->getBounds(),
+                            clipper.getClipRect() == nullptr, forceRLE);
+    } else {
+        SkScan::SAAFillPath(path, blitter, ir, clipRgn->getBounds(),
+                            clipper.getClipRect() == nullptr, forceRLE);
+    }
+
+    if (isInverse) {
+        sk_blit_below(blitter, ir, *clipRgn);
+    }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/SkScan_DAAPath.cpp b/src/core/SkScan_DAAPath.cpp
index e213e34..cd1717f 100644
--- a/src/core/SkScan_DAAPath.cpp
+++ b/src/core/SkScan_DAAPath.cpp
@@ -315,48 +315,42 @@
     }
 }
 
-void SkScan::DAAFillPath(const SkPath& path, const SkRegion& origClip, SkBlitter* blitter,
-                         bool forceRLE) {
+// For threaded backend with out-of-order init-once, we probably have to take care of the
+// blitRegion, sk_blit_above, sk_blit_below in SkScan::AntiFillPath to maintain the draw order. If
+// we do that, be caureful that blitRect may throw exception if the rect is empty.
+void SkScan::DAAFillPath(const SkPath& path, SkBlitter* blitter, const SkIRect& ir,
+                         const SkIRect& clipBounds, bool containedInClip, bool forceRLE) {
+    bool isEvenOdd  = path.getFillType() & 1;
+    bool isConvex   = path.isConvex();
+    bool isInverse  = path.isInverseFillType();
+    bool skipRect   = isConvex && !isInverse;
 
-    FillPathFunc fillPathFunc = [](const SkPath& path, SkBlitter* blitter, bool isInverse,
-            const SkIRect& ir, const SkIRect& clipBounds, bool containedInClip, bool forceRLE){
-        bool isEvenOdd  = path.getFillType() & 1;
-        bool isConvex   = path.isConvex();
-        bool skipRect   = isConvex && !isInverse;
+    SkIRect clippedIR = ir;
+    clippedIR.intersect(clipBounds);
 
-        SkIRect clippedIR = ir;
-        clippedIR.intersect(clipBounds);
-
-        // The overhead of even constructing SkCoverageDeltaList/Mask is too big.
-        // So TryBlitFatAntiRect and return if it's successful.
-        if (!isInverse && TryBlitFatAntiRect(blitter, path, clipBounds)) {
-            return;
-        }
+    // The overhead of even constructing SkCoverageDeltaList/Mask is too big.
+    // So TryBlitFatAntiRect and return if it's successful.
+    if (!isInverse && TryBlitFatAntiRect(blitter, path, clipBounds)) {
+        return;
+    }
 
 #ifdef GOOGLE3
-        constexpr int STACK_SIZE = 12 << 10; // 12K stack size alloc; Google3 has 16K limit.
+    constexpr int STACK_SIZE = 12 << 10; // 12K stack size alloc; Google3 has 16K limit.
 #else
-        constexpr int STACK_SIZE = 64 << 10; // 64k stack size to avoid heap allocation
+    constexpr int STACK_SIZE = 64 << 10; // 64k stack size to avoid heap allocation
 #endif
-        SkSTArenaAlloc<STACK_SIZE> alloc; // avoid heap allocation with SkSTArenaAlloc
+    SkSTArenaAlloc<STACK_SIZE> alloc; // avoid heap allocation with SkSTArenaAlloc
 
-        // Only blitter->blitXXX need to be done in order in the threaded backend.
-        // Everything before can be done out of order in the threaded backend.
-        if (!forceRLE && !isInverse && SkCoverageDeltaMask::Suitable(clippedIR)) {
-            SkCoverageDeltaMask deltaMask(&alloc, clippedIR);
-            gen_alpha_deltas(path, clipBounds, deltaMask, blitter, skipRect, containedInClip);
-            deltaMask.convertCoverageToAlpha(isEvenOdd, isInverse, isConvex);
-            blitter->blitMask(deltaMask.prepareSkMask(), clippedIR);
-        } else {
-            SkCoverageDeltaList deltaList(&alloc, clippedIR.fTop, clippedIR.fBottom, forceRLE);
-            gen_alpha_deltas(path, clipBounds, deltaList, blitter, skipRect, containedInClip);
-            blitter->blitCoverageDeltas(&deltaList, clipBounds, isEvenOdd, isInverse, isConvex);
-        }
-    };
-
-    // For threaded backend with out-of-order init-once (and therefore out-of-order do_fill_path),
-    // we probably have to take care of the blitRegion, sk_blit_above, sk_blit_below in do_fill_path
-    // to maintain the draw order. If we do that, be caureful that blitRect may throw exception is
-    // the rect is empty.
-    do_fill_path(path, origClip, blitter, forceRLE, 2, std::move(fillPathFunc));
+    // Only blitter->blitXXX needs to be done in order in the threaded backend.
+    // Everything before can be done out of order in the threaded backend.
+    if (!forceRLE && !isInverse && SkCoverageDeltaMask::Suitable(clippedIR)) {
+        SkCoverageDeltaMask deltaMask(&alloc, clippedIR);
+        gen_alpha_deltas(path, clipBounds, deltaMask, blitter, skipRect, containedInClip);
+        deltaMask.convertCoverageToAlpha(isEvenOdd, isInverse, isConvex);
+        blitter->blitMask(deltaMask.prepareSkMask(), clippedIR);
+    } else {
+        SkCoverageDeltaList deltaList(&alloc, clippedIR.fTop, clippedIR.fBottom, forceRLE);
+        gen_alpha_deltas(path, clipBounds, deltaList, blitter, skipRect, containedInClip);
+        blitter->blitCoverageDeltas(&deltaList, clipBounds, isEvenOdd, isInverse, isConvex);
+    }
 }
diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
index c6e5a0e..b4eb89a 100644
--- a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
+++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
@@ -104,6 +104,7 @@
         , fProcessors(std::move(args.fPaint))
         , fTailDraw(&fHeadDraw)
         , fOwningRTPendingOps(nullptr) {
+    SkDEBUGCODE(++fCCPR->fPendingDrawOpsCount);
     SkDEBUGCODE(fBaseInstance = -1);
     SkDEBUGCODE(fDebugInstanceCount = 1;)
     SkDEBUGCODE(fDebugSkippedInstances = 0;)
@@ -139,20 +140,34 @@
     this->setBounds(devBounds, GrOp::HasAABloat::kYes, GrOp::IsZeroArea::kNo);
 }
 
+GrCoverageCountingPathRenderer::DrawPathsOp::~DrawPathsOp() {
+    if (fOwningRTPendingOps) {
+        // Remove CCPR's dangling pointer to this Op before deleting it.
+        SkASSERT(!fCCPR->fFlushing);
+        fOwningRTPendingOps->fOpList.remove(this);
+    }
+    SkDEBUGCODE(--fCCPR->fPendingDrawOpsCount);
+}
+
 GrDrawOp::RequiresDstTexture DrawPathsOp::finalize(const GrCaps& caps, const GrAppliedClip* clip,
                                                    GrPixelConfigIsClamped dstIsClamped) {
-    SingleDraw& onlyDraw = this->getOnlyPathDraw();
+    SkASSERT(!fCCPR->fFlushing);
+    // There should only be one single path draw in this Op right now.
+    SkASSERT(1 == fDebugInstanceCount);
+    SkASSERT(&fHeadDraw == fTailDraw);
     GrProcessorSet::Analysis analysis = fProcessors.finalize(
-            onlyDraw.fColor, GrProcessorAnalysisCoverage::kSingleChannel, clip, false, caps,
-            dstIsClamped, &onlyDraw.fColor);
+            fHeadDraw.fColor, GrProcessorAnalysisCoverage::kSingleChannel, clip, false, caps,
+            dstIsClamped, &fHeadDraw.fColor);
     return analysis.requiresDstTexture() ? RequiresDstTexture::kYes : RequiresDstTexture::kNo;
 }
 
 bool DrawPathsOp::onCombineIfPossible(GrOp* op, const GrCaps& caps) {
     DrawPathsOp* that = op->cast<DrawPathsOp>();
     SkASSERT(fCCPR == that->fCCPR);
+    SkASSERT(!fCCPR->fFlushing);
     SkASSERT(fOwningRTPendingOps);
     SkASSERT(fDebugInstanceCount);
+    SkASSERT(!that->fOwningRTPendingOps || that->fOwningRTPendingOps == fOwningRTPendingOps);
     SkASSERT(that->fDebugInstanceCount);
 
     if (this->getFillType() != that->getFillType() ||
@@ -161,20 +176,8 @@
         return false;
     }
 
-    if (RTPendingOps* owningRTPendingOps = that->fOwningRTPendingOps) {
-        SkASSERT(owningRTPendingOps == fOwningRTPendingOps);
-        owningRTPendingOps->fOpList.remove(that);
-    } else {
-        // The Op is being combined immediately after creation, before a call to wasRecorded. In
-        // this case wasRecorded will not be called. So we count its path here instead.
-        const SingleDraw& onlyDraw = that->getOnlyPathDraw();
-        ++fOwningRTPendingOps->fNumTotalPaths;
-        fOwningRTPendingOps->fNumSkPoints += onlyDraw.fPath.countPoints();
-        fOwningRTPendingOps->fNumSkVerbs += onlyDraw.fPath.countVerbs();
-    }
-
     fTailDraw->fNext = &fOwningRTPendingOps->fDrawsAllocator.push_back(that->fHeadDraw);
-    fTailDraw = that->fTailDraw == &that->fHeadDraw ? fTailDraw->fNext : that->fTailDraw;
+    fTailDraw = (that->fTailDraw == &that->fHeadDraw) ? fTailDraw->fNext : that->fTailDraw;
 
     this->joinBounds(*that);
 
@@ -184,12 +187,9 @@
 }
 
 void DrawPathsOp::wasRecorded(GrRenderTargetOpList* opList) {
+    SkASSERT(!fCCPR->fFlushing);
     SkASSERT(!fOwningRTPendingOps);
-    const SingleDraw& onlyDraw = this->getOnlyPathDraw();
     fOwningRTPendingOps = &fCCPR->fRTPendingOpsMap[opList->uniqueID()];
-    ++fOwningRTPendingOps->fNumTotalPaths;
-    fOwningRTPendingOps->fNumSkPoints += onlyDraw.fPath.countPoints();
-    fOwningRTPendingOps->fNumSkVerbs += onlyDraw.fPath.countVerbs();
     fOwningRTPendingOps->fOpList.addToTail(this);
 }
 
@@ -224,22 +224,29 @@
 
     fPerFlushResourcesAreValid = false;
 
-    SkTInternalLList<DrawPathsOp> flushingOps;
+    // Gather the Ops that are being flushed.
     int maxTotalPaths = 0, numSkPoints = 0, numSkVerbs = 0;
-
+    SkTInternalLList<DrawPathsOp> flushingOps;
     for (int i = 0; i < numOpListIDs; ++i) {
         auto it = fRTPendingOpsMap.find(opListIDs[i]);
-        if (fRTPendingOpsMap.end() != it) {
-            RTPendingOps& rtPendingOps = it->second;
-            SkASSERT(!rtPendingOps.fOpList.isEmpty());
-            flushingOps.concat(std::move(rtPendingOps.fOpList));
-            maxTotalPaths += rtPendingOps.fNumTotalPaths;
-            numSkPoints += rtPendingOps.fNumSkPoints;
-            numSkVerbs += rtPendingOps.fNumSkVerbs;
+        if (fRTPendingOpsMap.end() == it) {
+            continue;
         }
+        SkTInternalLList<DrawPathsOp>::Iter iter;
+        SkTInternalLList<DrawPathsOp>& rtFlushingOps = it->second.fOpList;
+        iter.init(rtFlushingOps, SkTInternalLList<DrawPathsOp>::Iter::kHead_IterStart);
+        while (DrawPathsOp* flushingOp = iter.get()) {
+            for (const auto* draw = &flushingOp->fHeadDraw; draw; draw = draw->fNext) {
+                ++maxTotalPaths;
+                numSkPoints += draw->fPath.countPoints();
+                numSkVerbs += draw->fPath.countVerbs();
+            }
+            flushingOp->fOwningRTPendingOps = nullptr; // Owner is about to change to 'flushingOps'.
+            iter.next();
+        }
+        flushingOps.concat(std::move(rtFlushingOps));
     }
 
-    SkASSERT(flushingOps.isEmpty() == !maxTotalPaths);
     if (flushingOps.isEmpty()) {
         return; // Nothing to draw.
     }
@@ -376,6 +383,8 @@
         return; // Setup failed.
     }
 
+    SkASSERT(fBaseInstance >= 0); // Make sure setupPerFlushResources has set us up.
+
     GrPipeline::InitArgs initArgs;
     initArgs.fFlags = fSRGBFlags;
     initArgs.fProxy = flushState->drawOpArgs().fProxy;
diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.h b/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
index 7d613f9..a6295a5 100644
--- a/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
+++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
@@ -35,6 +35,12 @@
     static sk_sp<GrCoverageCountingPathRenderer> CreateIfSupported(const GrCaps&,
                                                                    bool drawCachablePaths);
 
+    ~GrCoverageCountingPathRenderer() override {
+        // Ensure nothing exists that could have a dangling pointer back into this class.
+        SkASSERT(fRTPendingOpsMap.empty());
+        SkASSERT(0 == fPendingDrawOpsCount);
+    }
+
     // GrPathRenderer overrides.
     StencilSupport onGetStencilSupport(const GrShape&) const override {
         return GrPathRenderer::kNoSupport_StencilSupport;
@@ -55,6 +61,7 @@
         SK_DECLARE_INTERNAL_LLIST_INTERFACE(DrawPathsOp);
 
         DrawPathsOp(GrCoverageCountingPathRenderer*, const DrawPathArgs&, GrColor);
+        ~DrawPathsOp() override;
 
         const char* name() const override { return "GrCoverageCountingPathRenderer::DrawPathsOp"; }
 
@@ -87,12 +94,6 @@
             SingleDraw*   fNext = nullptr;
         };
 
-        SingleDraw& getOnlyPathDraw() {
-            SkASSERT(&fHeadDraw == fTailDraw);
-            SkASSERT(1 == fDebugInstanceCount);
-            return fHeadDraw;
-        }
-
         struct AtlasBatch {
             const GrCCPRAtlas*   fAtlas;
             int                  fEndInstanceIdx;
@@ -130,14 +131,12 @@
 
     struct RTPendingOps {
         SkTInternalLList<DrawPathsOp>                 fOpList;
-        int                                           fNumTotalPaths = 0;
-        int                                           fNumSkPoints = 0;
-        int                                           fNumSkVerbs = 0;
         GrSTAllocator<256, DrawPathsOp::SingleDraw>   fDrawsAllocator;
     };
 
     // Map from render target ID to the individual render target's pending path ops.
     std::map<uint32_t, RTPendingOps>   fRTPendingOpsMap;
+    SkDEBUGCODE(int                    fPendingDrawOpsCount = 0;)
 
     sk_sp<GrBuffer>                    fPerFlushIndexBuffer;
     sk_sp<GrBuffer>                    fPerFlushVertexBuffer;
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();
-}
diff --git a/tests/GrCCPRTest.cpp b/tests/GrCCPRTest.cpp
index f56298b..b8e3db9 100644
--- a/tests/GrCCPRTest.cpp
+++ b/tests/GrCCPRTest.cpp
@@ -154,6 +154,28 @@
 };
 DEF_CCPR_TEST(GrCCPRTest_cleanup)
 
+class GrCCPRTest_unregisterCulledOps : public CCPRTest {
+    void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) override {
+        REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
+
+        // Ensure Ops get unregistered from CCPR when culled early.
+        ccpr.drawPath(fPath);
+        REPORTER_ASSERT(reporter, !SkPathPriv::TestingOnly_unique(fPath));
+        ccpr.clear(); // Clear should delete the CCPR Op.
+        REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
+        ccpr.flush(); // Should not crash (DrawPathsOp should have unregistered itself).
+
+        // Ensure Op unregisters work when we delete the context without flushing.
+        ccpr.drawPath(fPath);
+        REPORTER_ASSERT(reporter, !SkPathPriv::TestingOnly_unique(fPath));
+        ccpr.clear(); // Clear should delete the CCPR DrawPathsOp.
+        REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
+        ccpr.abandonGrContext();
+        fMockContext.reset(); // Should not crash (DrawPathsOp should have unregistered itself).
+    }
+};
+DEF_CCPR_TEST(GrCCPRTest_unregisterCulledOps)
+
 class CCPRRenderingTest {
 public:
     void run(skiatest::Reporter* reporter, GrContext* ctx) const {
diff --git a/tools/sk_tool_utils.cpp b/tools/sk_tool_utils.cpp
index 0a93db4..56c1f48 100644
--- a/tools/sk_tool_utils.cpp
+++ b/tools/sk_tool_utils.cpp
@@ -263,6 +263,21 @@
     add_to_text_blob_w_len(builder, text, strlen(text), origPaint, x, y);
 }
 
+SkPath make_star(const SkRect& bounds, int numPts, int step) {
+    SkPath path;
+    path.setFillType(SkPath::kEvenOdd_FillType);
+    path.moveTo(0,-1);
+    for (int i = 1; i < numPts; ++i) {
+        int idx = i*step;
+        SkScalar theta = idx * 2*SK_ScalarPI/numPts + SK_ScalarPI/2;
+        SkScalar x = SkScalarCos(theta);
+        SkScalar y = -SkScalarSin(theta);
+        path.lineTo(x, y);
+    }
+    path.transform(SkMatrix::MakeRectToRect(path.getBounds(), bounds, SkMatrix::kFill_ScaleToFit));
+    return path;
+}
+
 #if !defined(__clang__) && defined(_MSC_VER)
     // MSVC takes ~2 minutes to compile this function with optimization.
     // We don't really care to wait that long for this function.
diff --git a/tools/sk_tool_utils.h b/tools/sk_tool_utils.h
index b1bde77..2931243 100644
--- a/tools/sk_tool_utils.h
+++ b/tools/sk_tool_utils.h
@@ -135,6 +135,18 @@
     void add_to_text_blob(SkTextBlobBuilder* builder, const char* text,
                           const SkPaint& origPaint, SkScalar x, SkScalar y);
 
+    // Constructs a star by walking a 'numPts'-sided regular polygon with even/odd fill:
+    //
+    //   moveTo(pts[0]);
+    //   lineTo(pts[step % numPts]);
+    //   ...
+    //   lineTo(pts[(step * (N - 1)) % numPts]);
+    //
+    // numPts=5, step=2 will produce a classic five-point star.
+    //
+    // numPts and step must be co-prime.
+    SkPath make_star(const SkRect& bounds, int numPts = 5, int step = 2);
+
     void make_big_path(SkPath& path);
 
     // Return a blurred version of 'src'. This doesn't use a separable filter