ccpr: Generalize GrCCAtlas to work for cached atlases as well

Converts atlas offsets to SkIVector, adds a GrCCAtlasStack class,
moves the Op that renders the atlases into GrCCPerFlushResources, etc.

Bug: skia:
Change-Id: I5110be8e74da709f3ce84bb6798ead572142d0fa
Reviewed-on: https://skia-review.googlesource.com/134701
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/src/gpu/GrRenderTargetContext.h b/src/gpu/GrRenderTargetContext.h
index d0b4a7d..783b351 100644
--- a/src/gpu/GrRenderTargetContext.h
+++ b/src/gpu/GrRenderTargetContext.h
@@ -404,7 +404,7 @@
     friend class GrDefaultPathRenderer;              // for access to add[Mesh]DrawOp
     friend class GrStencilAndCoverPathRenderer;      // for access to add[Mesh]DrawOp
     friend class GrTessellatingPathRenderer;         // for access to add[Mesh]DrawOp
-    friend class GrCCAtlas;                          // for access to addDrawOp
+    friend class GrCCPerFlushResources;              // for access to addDrawOp
     friend class GrCoverageCountingPathRenderer;     // for access to addDrawOp
     // for a unit test
     friend void test_draw_op(GrContext*,
diff --git a/src/gpu/ccpr/GrCCAtlas.cpp b/src/gpu/ccpr/GrCCAtlas.cpp
index 551b2a0..cbf6993 100644
--- a/src/gpu/ccpr/GrCCAtlas.cpp
+++ b/src/gpu/ccpr/GrCCAtlas.cpp
@@ -7,19 +7,13 @@
 
 #include "GrCCAtlas.h"
 
-#include "GrClip.h"
-#include "GrMemoryPool.h"
+#include "GrCaps.h"
 #include "GrOnFlushResourceProvider.h"
-#include "GrSurfaceContextPriv.h"
 #include "GrRectanizer_skyline.h"
 #include "GrRenderTargetContext.h"
-#include "GrSurfaceContextPriv.h"
 #include "GrTextureProxy.h"
 #include "SkMakeUnique.h"
 #include "SkMathPriv.h"
-#include "ccpr/GrCCCoverageProcessor.h"
-#include "ccpr/GrCCPathParser.h"
-#include "ops/GrDrawOp.h"
 
 class GrCCAtlas::Node {
 public:
@@ -50,50 +44,6 @@
     GrRectanizerSkyline fRectanizer;
 };
 
-class GrCCAtlas::DrawCoverageCountOp : public GrDrawOp {
-public:
-    DEFINE_OP_CLASS_ID
-
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
-                                          sk_sp<const GrCCPathParser> parser,
-                                          CoverageCountBatchID batchID,
-                                          const SkISize& drawBounds) {
-        return std::unique_ptr<GrDrawOp>(new DrawCoverageCountOp(std::move(parser),
-                                                                 batchID, drawBounds));
-    }
-
-    // GrDrawOp interface.
-    const char* name() const override { return "GrCCAtlas::DrawCoverageCountOp"; }
-    FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
-    RequiresDstTexture finalize(const GrCaps&, const GrAppliedClip*,
-                                GrPixelConfigIsClamped) override { return RequiresDstTexture::kNo; }
-    bool onCombineIfPossible(GrOp* other, const GrCaps& caps) override { return false; }
-    void onPrepare(GrOpFlushState*) override {}
-    void onExecute(GrOpFlushState* flushState) override {
-        fParser->drawCoverageCount(flushState, fBatchID,
-                                   SkIRect::MakeWH(fDrawBounds.width(), fDrawBounds.height()));
-    }
-
-private:
-    friend class GrOpMemoryPool; // for ctor
-
-    DrawCoverageCountOp(sk_sp<const GrCCPathParser> parser, CoverageCountBatchID batchID,
-                        const SkISize& drawBounds)
-            : INHERITED(ClassID())
-            , fParser(std::move(parser))
-            , fBatchID(batchID)
-            , fDrawBounds(drawBounds) {
-        this->setBounds(SkRect::MakeIWH(fDrawBounds.width(), fDrawBounds.height()),
-                        GrOp::HasAABloat::kNo, GrOp::IsZeroArea::kNo);
-    }
-
-    const sk_sp<const GrCCPathParser> fParser;
-    const CoverageCountBatchID fBatchID;
-    const SkISize fDrawBounds;
-
-    typedef GrDrawOp INHERITED;
-};
-
 GrCCAtlas::GrCCAtlas(const Specs& specs)
         : fMaxTextureSize(SkTMax(SkTMax(specs.fMinHeight, specs.fMinWidth),
                                  specs.fMaxPreferredTextureSize)) {
@@ -121,17 +71,18 @@
 GrCCAtlas::~GrCCAtlas() {
 }
 
-bool GrCCAtlas::addRect(int w, int h, SkIPoint16* loc) {
-    // This can't be called anymore once setCoverageCountBatchID() has been called.
-    SkASSERT(!fCoverageCountBatchID);
+bool GrCCAtlas::addRect(const SkIRect& devIBounds, SkIVector* offset) {
+    // This can't be called anymore once makeClearedTextureProxy() has been called.
     SkASSERT(!fTextureProxy);
 
-    if (!this->internalPlaceRect(w, h, loc)) {
+    SkIPoint16 location;
+    if (!this->internalPlaceRect(devIBounds.width(), devIBounds.height(), &location)) {
         return false;
     }
+    offset->set(location.x() - devIBounds.left(), location.y() - devIBounds.top());
 
-    fDrawBounds.fWidth = SkTMax(fDrawBounds.width(), loc->x() + w);
-    fDrawBounds.fHeight = SkTMax(fDrawBounds.height(), loc->y() + h);
+    fDrawBounds.fWidth = SkTMax(fDrawBounds.width(), location.x() + devIBounds.width());
+    fDrawBounds.fHeight = SkTMax(fDrawBounds.height(), location.y() + devIBounds.height());
     return true;
 }
 
@@ -161,21 +112,21 @@
     return true;
 }
 
-sk_sp<GrRenderTargetContext> GrCCAtlas::finalize(GrOnFlushResourceProvider* onFlushRP,
-                                                 sk_sp<const GrCCPathParser> parser) {
-    SkASSERT(fCoverageCountBatchID);
+sk_sp<GrRenderTargetContext> GrCCAtlas::initInternalTextureProxy(
+        GrOnFlushResourceProvider* onFlushRP, GrPixelConfig config) {
     SkASSERT(!fTextureProxy);
     // Caller should have cropped any paths to the destination render target instead of asking for
     // an atlas larger than maxRenderTargetSize.
+    SkASSERT(SkTMax(fHeight, fWidth) <= fMaxTextureSize);
     SkASSERT(fMaxTextureSize <= onFlushRP->caps()->maxRenderTargetSize());
 
     GrSurfaceDesc desc;
     desc.fFlags = kRenderTarget_GrSurfaceFlag;
     desc.fWidth = fWidth;
     desc.fHeight = fHeight;
-    desc.fConfig = kAlpha_half_GrPixelConfig;
+    desc.fConfig = config;
     sk_sp<GrRenderTargetContext> rtc =
-            onFlushRP->makeRenderTargetContext(desc, kTopLeft_GrSurfaceOrigin, nullptr, nullptr);
+            onFlushRP->makeRenderTargetContext(desc, kTextureOrigin, nullptr, nullptr);
     if (!rtc) {
         SkDebugf("WARNING: failed to allocate a %ix%i atlas. Some paths will not be drawn.\n",
                  fWidth, fHeight);
@@ -185,14 +136,19 @@
     SkIRect clearRect = SkIRect::MakeSize(fDrawBounds);
     rtc->clear(&clearRect, 0, GrRenderTargetContext::CanClearFullscreen::kYes);
 
-    GrContext* context = rtc->surfPriv().getContext();
-
-    std::unique_ptr<GrDrawOp> op = DrawCoverageCountOp::Make(context,
-                                                             std::move(parser),
-                                                             fCoverageCountBatchID,
-                                                             fDrawBounds);
-    rtc->addDrawOp(GrNoClip(), std::move(op));
-
     fTextureProxy = sk_ref_sp(rtc->asTextureProxy());
     return rtc;
 }
+
+GrCCAtlas* GrCCAtlasStack::addRect(const SkIRect& devIBounds, SkIVector* offset) {
+    GrCCAtlas* retiredAtlas = nullptr;
+    if (fAtlases.empty() || !fAtlases.back().addRect(devIBounds, offset)) {
+        // The retired atlas is out of room and can't grow any bigger.
+        retiredAtlas = !fAtlases.empty() ? &fAtlases.back() : nullptr;
+        fAtlases.emplace_back(fSpecs);
+        SkASSERT(devIBounds.width() <= fSpecs.fMinWidth);
+        SkASSERT(devIBounds.height() <= fSpecs.fMinHeight);
+        SkAssertResult(fAtlases.back().addRect(devIBounds, offset));
+    }
+    return retiredAtlas;
+}
diff --git a/src/gpu/ccpr/GrCCAtlas.h b/src/gpu/ccpr/GrCCAtlas.h
index b30bdbe..d4a07f5 100644
--- a/src/gpu/ccpr/GrCCAtlas.h
+++ b/src/gpu/ccpr/GrCCAtlas.h
@@ -8,12 +8,12 @@
 #ifndef GrCCAtlas_DEFINED
 #define GrCCAtlas_DEFINED
 
+#include "GrAllocator.h"
+#include "GrTypes.h"
+#include "GrTypesPriv.h"
 #include "SkRefCnt.h"
 #include "SkSize.h"
 
-class GrCaps;
-class GrCCPathParser;
-class GrDrawOp;
 class GrOnFlushResourceProvider;
 class GrRenderTargetContext;
 class GrTextureProxy;
@@ -26,7 +26,8 @@
  */
 class GrCCAtlas {
 public:
-    using CoverageCountBatchID = int;
+    // As long as GrSurfaceOrigin exists, we just have to decide on one for the atlas texture.
+    static constexpr GrSurfaceOrigin kTextureOrigin = kTopLeft_GrSurfaceOrigin;
     static constexpr int kPadding = 1;  // Amount of padding below and to the right of each path.
 
     // This struct encapsulates the minimum and desired requirements for an atlas, as well as an
@@ -45,23 +46,25 @@
     GrCCAtlas(const Specs&);
     ~GrCCAtlas();
 
-    bool addRect(int devWidth, int devHeight, SkIPoint16* loc);
+    // Attempts to add a rect to the atlas. If successful, returns the integer offset from
+    // device-space pixels where the path will be drawn, to atlas pixels where its mask resides.
+    bool addRect(const SkIRect& devIBounds, SkIVector* atlasOffset);
     const SkISize& drawBounds() { return fDrawBounds; }
 
-    void setCoverageCountBatchID(CoverageCountBatchID batchID) {
-        SkASSERT(!fCoverageCountBatchID);
-        SkASSERT(!fTextureProxy);
-        fCoverageCountBatchID = batchID;
-    }
+    // This is an optional space for the caller to jot down which user-defined batch to use when
+    // they render the content of this atlas.
+    void setUserBatchID(int id) { SkASSERT(!fTextureProxy); fUserBatchID = id; }
+    int getUserBatchID() const { return fUserBatchID; }
 
-    sk_sp<GrRenderTargetContext> SK_WARN_UNUSED_RESULT finalize(GrOnFlushResourceProvider*,
-                                                                sk_sp<const GrCCPathParser>);
-
+    // Creates our texture proxy for the atlas and returns a pre-cleared GrRenderTargetContext that
+    // the caller may use to render the content. After this call, it is no longer valid to call
+    // addRect() or setUserBatchID().
+    sk_sp<GrRenderTargetContext> initInternalTextureProxy(GrOnFlushResourceProvider*,
+                                                          GrPixelConfig);
     GrTextureProxy* textureProxy() const { return fTextureProxy.get(); }
 
 private:
     class Node;
-    class DrawCoverageCountOp;
 
     bool internalPlaceRect(int w, int h, SkIPoint16* loc);
 
@@ -70,10 +73,46 @@
     std::unique_ptr<Node> fTopNode;
     SkISize fDrawBounds = {0, 0};
 
-    CoverageCountBatchID fCoverageCountBatchID SkDEBUGCODE(= 0);
+    int fUserBatchID;
     sk_sp<GrTextureProxy> fTextureProxy;
 };
 
+/**
+ * This class implements an unbounded stack of atlases. When the current atlas reaches the
+ * implementation-dependent max texture size, a new one is pushed to the back and we continue on.
+ */
+class GrCCAtlasStack {
+public:
+    GrCCAtlasStack(const GrCCAtlas::Specs& specs) : fSpecs(specs) {}
+
+    bool empty() const { return fAtlases.empty(); }
+    const GrCCAtlas& front() const { SkASSERT(!this->empty()); return fAtlases.front(); }
+    GrCCAtlas& front() { SkASSERT(!this->empty()); return fAtlases.front(); }
+    GrCCAtlas& current() { SkASSERT(!this->empty()); return fAtlases.back(); }
+
+    class Iter {
+    public:
+        Iter(GrCCAtlasStack& stack) : fImpl(&stack.fAtlases) {}
+        bool next() { return fImpl.next(); }
+        GrCCAtlas* operator->() const { return fImpl.get(); }
+    private:
+        typename GrTAllocator<GrCCAtlas>::Iter fImpl;
+    };
+
+    // Adds a rect to the current atlas and returns the offset from device space to atlas space.
+    // Call current() to get the atlas it was added to.
+    //
+    // If the return value is non-null, it means the given rect did not fit in the then-current
+    // atlas, so it was retired and a new one was added to the stack. The return value is the
+    // newly-retired atlas. The caller should call setUserBatchID() on the retired atlas before
+    // moving on.
+    GrCCAtlas* addRect(const SkIRect& devIBounds, SkIVector* offset);
+
+private:
+    const GrCCAtlas::Specs fSpecs;
+    GrSTAllocator<4, GrCCAtlas> fAtlases;
+};
+
 inline void GrCCAtlas::Specs::accountForSpace(int width, int height) {
     fMinWidth = SkTMax(width, fMinWidth);
     fMinHeight = SkTMax(height, fMinHeight);
diff --git a/src/gpu/ccpr/GrCCClipPath.cpp b/src/gpu/ccpr/GrCCClipPath.cpp
index c988657..77674e4 100644
--- a/src/gpu/ccpr/GrCCClipPath.cpp
+++ b/src/gpu/ccpr/GrCCClipPath.cpp
@@ -35,8 +35,8 @@
                 SkASSERT(kTopLeft_GrSurfaceOrigin == textureProxy->origin());
 
                 fAtlasScale = {1.f / textureProxy->width(), 1.f / textureProxy->height()};
-                fAtlasTranslate = {fAtlasOffsetX * fAtlasScale.x(),
-                                   fAtlasOffsetY * fAtlasScale.y()};
+                fAtlasTranslate.set(fDevToAtlasOffset.fX * fAtlasScale.x(),
+                                    fDevToAtlasOffset.fY * fAtlasScale.y());
                 SkDEBUGCODE(fHasAtlasTransform = true);
 
                 return sk_ref_sp(textureProxy->priv().peekTexture());
@@ -65,6 +65,6 @@
     SkASSERT(this->isInitialized());
     SkASSERT(!fHasAtlas);
     fAtlas = resources->renderDeviceSpacePathInAtlas(fAccessRect, fDeviceSpacePath, fPathDevIBounds,
-                                                     &fAtlasOffsetX, &fAtlasOffsetY);
+                                                     &fDevToAtlasOffset);
     SkDEBUGCODE(fHasAtlas = true);
 }
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.cpp b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
index f29fca2..16a2c66 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.cpp
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
@@ -114,10 +114,10 @@
         // second one rotated an additional 45 degrees. The path vertex shader uses these two
         // bounding boxes to generate an octagon that circumscribes the path.
         SkRect devBounds, devBounds45;
-        int16_t atlasOffsetX, atlasOffsetY;
-        GrCCAtlas* atlas = resources->renderPathInAtlas(draw.fLooseClippedIBounds, draw.fMatrix,
-                                                        draw.fPath, &devBounds, &devBounds45,
-                                                        &atlasOffsetX, &atlasOffsetY);
+        SkIVector devToAtlasOffset;
+        const GrCCAtlas* atlas = resources->renderPathInAtlas(draw.fLooseClippedIBounds,
+                                                              draw.fMatrix, draw.fPath, &devBounds,
+                                                              &devBounds45, &devToAtlasOffset);
         if (!atlas) {
             SkDEBUGCODE(++fNumSkippedInstances);
             continue;
@@ -130,7 +130,7 @@
         }
 
         resources->appendDrawPathInstance().set(draw.fPath.getFillType(), devBounds, devBounds45,
-                                                atlasOffsetX, atlasOffsetY, draw.fColor);
+                                                devToAtlasOffset, draw.fColor);
     }
 
     SkASSERT(resources->nextPathInstanceIdx() == fBaseInstance + fNumDraws - fNumSkippedInstances);
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.h b/src/gpu/ccpr/GrCCDrawPathsOp.h
index b46039d..87cd50e 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.h
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.h
@@ -51,7 +51,7 @@
 private:
     friend class GrOpMemoryPool;
 
-    GrCCDrawPathsOp(const SkIRect& clippedDevIBounds, const SkMatrix&, const SkPath&,
+    GrCCDrawPathsOp(const SkIRect& looseClippedIBounds, const SkMatrix&, const SkPath&,
                     const SkRect& devBounds, GrPaint&&);
 
     struct AtlasBatch {
diff --git a/src/gpu/ccpr/GrCCPathParser.cpp b/src/gpu/ccpr/GrCCPathParser.cpp
index b29bec7..6f9b224 100644
--- a/src/gpu/ccpr/GrCCPathParser.cpp
+++ b/src/gpu/ccpr/GrCCPathParser.cpp
@@ -165,10 +165,10 @@
 }
 
 void GrCCPathParser::saveParsedPath(ScissorMode scissorMode, const SkIRect& clippedDevIBounds,
-                                    int16_t atlasOffsetX, int16_t atlasOffsetY) {
+                                    const SkIVector& devToAtlasOffset) {
     SkASSERT(fParsingPath);
 
-    fPathsInfo.emplace_back(scissorMode, atlasOffsetX, atlasOffsetY);
+    fPathsInfo.emplace_back(scissorMode, devToAtlasOffset);
 
     // Tessellate fans from very large and/or simple paths, in order to reduce overdraw.
     int numVerbs = fGeometry.verbs().count() - fCurrPathVerbsIdx - 1;
@@ -254,7 +254,8 @@
 
     if (ScissorMode::kScissored == scissorMode) {
         fScissorSubBatches.push_back() = {fTotalPrimitiveCounts[(int)ScissorMode::kScissored],
-                                          clippedDevIBounds.makeOffset(atlasOffsetX, atlasOffsetY)};
+                                          clippedDevIBounds.makeOffset(devToAtlasOffset.fX,
+                                                                       devToAtlasOffset.fY)};
     }
 
     SkDEBUGCODE(fParsingPath = false);
@@ -304,7 +305,7 @@
 // Returns the next triangle instance after the final one emitted.
 static TriPointInstance* emit_recursive_fan(const SkTArray<SkPoint, true>& pts,
                                             SkTArray<int32_t, true>& indices, int firstIndex,
-                                            int indexCount, const Sk2f& atlasOffset,
+                                            int indexCount, const Sk2f& devToAtlasOffset,
                                             TriPointInstance out[]) {
     if (indexCount < 3) {
         return out;
@@ -313,33 +314,34 @@
     int32_t oneThirdCount = indexCount / 3;
     int32_t twoThirdsCount = (2 * indexCount) / 3;
     out++->set(pts[indices[firstIndex]], pts[indices[firstIndex + oneThirdCount]],
-               pts[indices[firstIndex + twoThirdsCount]], atlasOffset);
+               pts[indices[firstIndex + twoThirdsCount]], devToAtlasOffset);
 
-    out = emit_recursive_fan(pts, indices, firstIndex, oneThirdCount + 1, atlasOffset, out);
+    out = emit_recursive_fan(pts, indices, firstIndex, oneThirdCount + 1, devToAtlasOffset, out);
     out = emit_recursive_fan(pts, indices, firstIndex + oneThirdCount,
-                             twoThirdsCount - oneThirdCount + 1, atlasOffset, out);
+                             twoThirdsCount - oneThirdCount + 1, devToAtlasOffset, out);
 
     int endIndex = firstIndex + indexCount;
     int32_t oldValue = indices[endIndex];
     indices[endIndex] = indices[firstIndex];
     out = emit_recursive_fan(pts, indices, firstIndex + twoThirdsCount,
-                             indexCount - twoThirdsCount + 1, atlasOffset, out);
+                             indexCount - twoThirdsCount + 1, devToAtlasOffset, out);
     indices[endIndex] = oldValue;
 
     return out;
 }
 
 static void emit_tessellated_fan(const GrTessellator::WindingVertex* vertices, int numVertices,
-                                 const Sk2f& atlasOffset, TriPointInstance* triPointInstanceData,
+                                 const Sk2f& devToAtlasOffset,
+                                 TriPointInstance* triPointInstanceData,
                                  QuadPointInstance* quadPointInstanceData,
                                  GrCCGeometry::PrimitiveTallies* indices) {
     for (int i = 0; i < numVertices; i += 3) {
         if (1 == abs(vertices[i].fWinding)) {
             triPointInstanceData[indices->fTriangles++].set(vertices[i].fPos, vertices[i + 1].fPos,
-                                                            vertices[i + 2].fPos, atlasOffset);
+                                                            vertices[i + 2].fPos, devToAtlasOffset);
         } else {
             quadPointInstanceData[indices->fWeightedTriangles++].setW(
-                    vertices[i].fPos, vertices[i+1].fPos, vertices[i + 2].fPos, atlasOffset,
+                    vertices[i].fPos, vertices[i+1].fPos, vertices[i + 2].fPos, devToAtlasOffset,
                     static_cast<float>(abs(vertices[i].fWinding)));
         }
     }
@@ -400,8 +402,7 @@
     SkASSERT(quadPointInstanceData);
 
     PathInfo* nextPathInfo = fPathsInfo.begin();
-    float atlasOffsetX = 0.0, atlasOffsetY = 0.0;
-    Sk2f atlasOffset;
+    Sk2f devToAtlasOffset;
     PrimitiveTallies instanceIndices[2] = {fBaseInstances[0], fBaseInstances[1]};
     PrimitiveTallies* currIndices = nullptr;
     SkSTArray<256, int32_t, true> currFan;
@@ -417,13 +418,12 @@
             case GrCCGeometry::Verb::kBeginPath:
                 SkASSERT(currFan.empty());
                 currIndices = &instanceIndices[(int)nextPathInfo->scissorMode()];
-                atlasOffsetX = static_cast<float>(nextPathInfo->atlasOffsetX());
-                atlasOffsetY = static_cast<float>(nextPathInfo->atlasOffsetY());
-                atlasOffset = {atlasOffsetX, atlasOffsetY};
+                devToAtlasOffset = Sk2f(static_cast<float>(nextPathInfo->devToAtlasOffset().fX),
+                                        static_cast<float>(nextPathInfo->devToAtlasOffset().fY));
                 currFanIsTessellated = nextPathInfo->hasFanTessellation();
                 if (currFanIsTessellated) {
                     emit_tessellated_fan(nextPathInfo->fanTessellation(),
-                                         nextPathInfo->fanTessellationCount(), atlasOffset,
+                                         nextPathInfo->fanTessellationCount(), devToAtlasOffset,
                                          triPointInstanceData, quadPointInstanceData, currIndices);
                 }
                 ++nextPathInfo;
@@ -446,7 +446,8 @@
                 continue;
 
             case GrCCGeometry::Verb::kMonotonicQuadraticTo:
-                triPointInstanceData[currIndices->fQuadratics++].set(&pts[ptsIdx], atlasOffset);
+                triPointInstanceData[currIndices->fQuadratics++].set(&pts[ptsIdx],
+                                                                     devToAtlasOffset);
                 ptsIdx += 2;
                 if (!currFanIsTessellated) {
                     SkASSERT(!currFan.empty());
@@ -455,8 +456,8 @@
                 continue;
 
             case GrCCGeometry::Verb::kMonotonicCubicTo:
-                quadPointInstanceData[currIndices->fCubics++].set(&pts[ptsIdx], atlasOffsetX,
-                                                                  atlasOffsetY);
+                quadPointInstanceData[currIndices->fCubics++].set(&pts[ptsIdx], devToAtlasOffset[0],
+                                                                  devToAtlasOffset[1]);
                 ptsIdx += 3;
                 if (!currFanIsTessellated) {
                     SkASSERT(!currFan.empty());
@@ -466,7 +467,8 @@
 
             case GrCCGeometry::Verb::kMonotonicConicTo:
                 quadPointInstanceData[currIndices->fConics++].setW(
-                        &pts[ptsIdx], atlasOffset, fGeometry.getConicWeight(nextConicWeightIdx));
+                        &pts[ptsIdx], devToAtlasOffset,
+                        fGeometry.getConicWeight(nextConicWeightIdx));
                 ptsIdx += 2;
                 ++nextConicWeightIdx;
                 if (!currFanIsTessellated) {
@@ -489,7 +491,7 @@
                     // fanSize + log3(fanSize), but we approximate with log2.
                     currFan.push_back_n(SkNextLog2(fanSize));
                     SkDEBUGCODE(TriPointInstance* end =)
-                            emit_recursive_fan(pts, currFan, 0, fanSize, atlasOffset,
+                            emit_recursive_fan(pts, currFan, 0, fanSize, devToAtlasOffset,
                                                triPointInstanceData + currIndices->fTriangles);
                     currIndices->fTriangles += fanSize - 2;
                     SkASSERT(triPointInstanceData + currIndices->fTriangles == end);
diff --git a/src/gpu/ccpr/GrCCPathParser.h b/src/gpu/ccpr/GrCCPathParser.h
index b48a0b8..1c09b98 100644
--- a/src/gpu/ccpr/GrCCPathParser.h
+++ b/src/gpu/ccpr/GrCCPathParser.h
@@ -9,7 +9,6 @@
 #define GrCCPathParser_DEFINED
 
 #include "GrMesh.h"
-#include "GrNonAtomicRef.h"
 #include "SkPath.h"
 #include "SkPathPriv.h"
 #include "SkRect.h"
@@ -27,7 +26,7 @@
  * This class parses SkPaths into CCPR primitives in GPU buffers, then issues calls to draw their
  * coverage counts.
  */
-class GrCCPathParser : public GrNonAtomicRef<GrCCPathParser> {
+class GrCCPathParser {
 public:
     // Indicates whether a path should enforce a scissor clip when rendering its mask. (Specified
     // as an int because these values get used directly as indices into arrays.)
@@ -68,8 +67,8 @@
     // Commits the currently-parsed path from staging to the current batch, and specifies whether
     // the mask should be rendered with a scissor in effect. Accepts an optional post-device-space
     // translate for placement in an atlas.
-    void saveParsedPath(ScissorMode, const SkIRect& clippedDevIBounds, int16_t atlasOffsetX,
-                        int16_t atlasOffsetY);
+    void saveParsedPath(ScissorMode, const SkIRect& clippedDevIBounds,
+                        const SkIVector& devToAtlasOffset);
     void discardParsedPath();
 
     // Compiles the outstanding saved paths into a batch, and returns an ID that can be used to draw
@@ -89,12 +88,11 @@
     // Every kBeginPath verb has a corresponding PathInfo entry.
     class PathInfo {
     public:
-        PathInfo(ScissorMode scissorMode, int16_t offsetX, int16_t offsetY)
-                : fScissorMode(scissorMode), fAtlasOffsetX(offsetX), fAtlasOffsetY(offsetY) {}
+        PathInfo(ScissorMode scissorMode, const SkIVector& devToAtlasOffset)
+                : fScissorMode(scissorMode), fDevToAtlasOffset(devToAtlasOffset) {}
 
         ScissorMode scissorMode() const { return fScissorMode; }
-        int16_t atlasOffsetX() const { return fAtlasOffsetX; }
-        int16_t atlasOffsetY() const { return fAtlasOffsetY; }
+        const SkIVector& devToAtlasOffset() const { return fDevToAtlasOffset; }
 
         // An empty tessellation fan is also valid; we use negative count to denote not tessellated.
         bool hasFanTessellation() const { return fFanTessellationCount >= 0; }
@@ -115,7 +113,7 @@
 
     private:
         ScissorMode fScissorMode;
-        int16_t fAtlasOffsetX, fAtlasOffsetY;
+        SkIVector fDevToAtlasOffset;  // Translation from device space to location in atlas.
         int fFanTessellationCount = -1;
         std::unique_ptr<const GrTessellator::WindingVertex[]> fFanTessellation;
     };
diff --git a/src/gpu/ccpr/GrCCPathProcessor.cpp b/src/gpu/ccpr/GrCCPathProcessor.cpp
index 5edc45f..39d5ef2 100644
--- a/src/gpu/ccpr/GrCCPathProcessor.cpp
+++ b/src/gpu/ccpr/GrCCPathProcessor.cpp
@@ -83,15 +83,15 @@
                        GrSamplerState::WrapMode::kClamp, kFragment_GrShaderFlag) {
     this->addInstanceAttrib("devbounds", kFloat4_GrVertexAttribType);
     this->addInstanceAttrib("devbounds45", kFloat4_GrVertexAttribType);
-    this->addInstanceAttrib("atlas_offset", kShort2_GrVertexAttribType);
+    this->addInstanceAttrib("dev_to_atlas_offset", kInt2_GrVertexAttribType);
     this->addInstanceAttrib("color", kUByte4_norm_GrVertexAttribType);
 
     SkASSERT(offsetof(Instance, fDevBounds) ==
              this->getInstanceAttrib(InstanceAttribs::kDevBounds).offsetInRecord());
     SkASSERT(offsetof(Instance, fDevBounds45) ==
              this->getInstanceAttrib(InstanceAttribs::kDevBounds45).offsetInRecord());
-    SkASSERT(offsetof(Instance, fAtlasOffset) ==
-             this->getInstanceAttrib(InstanceAttribs::kAtlasOffset).offsetInRecord());
+    SkASSERT(offsetof(Instance, fDevToAtlasOffset) ==
+             this->getInstanceAttrib(InstanceAttribs::kDevToAtlasOffset).offsetInRecord());
     SkASSERT(offsetof(Instance, fColor) ==
              this->getInstanceAttrib(InstanceAttribs::kColor).offsetInRecord());
     SkASSERT(sizeof(Instance) == this->getInstanceStride());
@@ -208,7 +208,7 @@
 
     // Convert to atlas coordinates in order to do our texture lookup.
     v->codeAppendf("float2 atlascoord = octocoord + float2(%s);",
-                   proc.getInstanceAttrib(InstanceAttribs::kAtlasOffset).name());
+                   proc.getInstanceAttrib(InstanceAttribs::kDevToAtlasOffset).name());
     if (kTopLeft_GrSurfaceOrigin == proc.atlasProxy()->origin()) {
         v->codeAppendf("%s.xy = atlascoord * %s;", texcoord.vsOut(), atlasAdjust);
     } else {
diff --git a/src/gpu/ccpr/GrCCPathProcessor.h b/src/gpu/ccpr/GrCCPathProcessor.h
index 9d97bad..9ea0a16 100644
--- a/src/gpu/ccpr/GrCCPathProcessor.h
+++ b/src/gpu/ccpr/GrCCPathProcessor.h
@@ -32,23 +32,23 @@
     enum class InstanceAttribs {
         kDevBounds,
         kDevBounds45,
-        kAtlasOffset,
+        kDevToAtlasOffset,
         kColor
     };
     static constexpr int kNumInstanceAttribs = 1 + (int)InstanceAttribs::kColor;
 
     struct Instance {
-        SkRect fDevBounds; // "right < left" indicates even-odd fill type.
-        SkRect fDevBounds45; // Bounding box in "| 1  -1 | * devCoords" space.
-                             //                  | 1   1 |
-        std::array<int16_t, 2> fAtlasOffset;
+        SkRect fDevBounds;  // "right < left" indicates even-odd fill type.
+        SkRect fDevBounds45;  // Bounding box in "| 1  -1 | * devCoords" space.
+                              //                  | 1   1 |
+        SkIVector fDevToAtlasOffset;  // Translation from device space to location in atlas.
         uint32_t fColor;
 
         void set(SkPath::FillType, const SkRect& devBounds, const SkRect& devBounds45,
-                 int16_t atlasOffsetX, int16_t atlasOffsetY, uint32_t color);
+                 const SkIVector& devToAtlasOffset, uint32_t color);
     };
 
-    GR_STATIC_ASSERT(4 * 10 == sizeof(Instance));
+    GR_STATIC_ASSERT(4 * 11 == sizeof(Instance));
 
     static sk_sp<const GrBuffer> FindVertexBuffer(GrOnFlushResourceProvider*);
     static sk_sp<const GrBuffer> FindIndexBuffer(GrOnFlushResourceProvider*);
@@ -87,8 +87,8 @@
 };
 
 inline void GrCCPathProcessor::Instance::set(SkPath::FillType fillType, const SkRect& devBounds,
-                                             const SkRect& devBounds45, int16_t atlasOffsetX,
-                                             int16_t atlasOffsetY, uint32_t color) {
+                                             const SkRect& devBounds45,
+                                             const SkIVector& devToAtlasOffset, uint32_t color) {
     if (SkPath::kEvenOdd_FillType == fillType) {
         // "right < left" indicates even-odd fill type.
         fDevBounds.setLTRB(devBounds.fRight, devBounds.fTop, devBounds.fLeft, devBounds.fBottom);
@@ -97,7 +97,7 @@
         fDevBounds = devBounds;
     }
     fDevBounds45 = devBounds45;
-    fAtlasOffset = {{atlasOffsetX, atlasOffsetY}};
+    fDevToAtlasOffset = devToAtlasOffset;
     fColor = color;
 }
 
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.cpp b/src/gpu/ccpr/GrCCPerFlushResources.cpp
index a80d789..5b3dec0 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.cpp
+++ b/src/gpu/ccpr/GrCCPerFlushResources.cpp
@@ -7,17 +7,69 @@
 
 #include "GrCCPerFlushResources.h"
 
+#include "GrClip.h"
+#include "GrMemoryPool.h"
 #include "GrOnFlushResourceProvider.h"
+#include "GrSurfaceContextPriv.h"
 #include "GrRenderTargetContext.h"
-#include "SkIPoint16.h"
+#include "SkMakeUnique.h"
 
+using CoverageCountBatchID = GrCCPathParser::CoverageCountBatchID;
 using PathInstance = GrCCPathProcessor::Instance;
 
+namespace {
+
+// Renders coverage counts to a CCPR atlas using the resources' pre-filled GrCCPathParser.
+class RenderAtlasOp : public GrDrawOp {
+public:
+    DEFINE_OP_CLASS_ID
+
+    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+                                          sk_sp<const GrCCPerFlushResources> resources,
+                                          CoverageCountBatchID batchID, const SkISize& drawBounds) {
+        return std::unique_ptr<GrDrawOp>(new RenderAtlasOp(std::move(resources), batchID,
+                                                           drawBounds));
+    }
+
+    // GrDrawOp interface.
+    const char* name() const override { return "RenderAtlasOp (CCPR)"; }
+    FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
+    RequiresDstTexture finalize(const GrCaps&, const GrAppliedClip*,
+                                GrPixelConfigIsClamped) override { return RequiresDstTexture::kNo; }
+    bool onCombineIfPossible(GrOp* other, const GrCaps&) override {
+        SK_ABORT("Only expected one Op per CCPR atlas.");
+        return true;
+    }
+    void onPrepare(GrOpFlushState*) override {}
+
+    void onExecute(GrOpFlushState* flushState) override {
+        fResources->pathParser().drawCoverageCount(flushState, fBatchID, fDrawBounds);
+    }
+
+private:
+    friend class ::GrOpMemoryPool; // for ctor
+
+    RenderAtlasOp(sk_sp<const GrCCPerFlushResources> resources, CoverageCountBatchID batchID,
+                  const SkISize& drawBounds)
+            : GrDrawOp(ClassID())
+            , fResources(std::move(resources))
+            , fBatchID(batchID)
+            , fDrawBounds(SkIRect::MakeWH(drawBounds.width(), drawBounds.height())) {
+        this->setBounds(SkRect::MakeIWH(fDrawBounds.width(), fDrawBounds.height()),
+                        GrOp::HasAABloat::kNo, GrOp::IsZeroArea::kNo);
+    }
+
+    const sk_sp<const GrCCPerFlushResources> fResources;
+    const CoverageCountBatchID fBatchID;
+    const SkIRect fDrawBounds;
+};
+
+}
+
 GrCCPerFlushResources::GrCCPerFlushResources(GrOnFlushResourceProvider* onFlushRP,
                                              const GrCCPerFlushResourceSpecs& specs)
-        : fPathParser(sk_make_sp<GrCCPathParser>(specs.fNumRenderedPaths + specs.fNumClipPaths,
-                                                 specs.fParsingPathStats))
-        , fAtlasSpecs(specs.fAtlasSpecs)
+        : fPathParser(specs.fNumRenderedPaths + specs.fNumClipPaths, specs.fParsingPathStats)
+        , fAtlasStack(specs.fAtlasSpecs)
         , fIndexBuffer(GrCCPathProcessor::FindIndexBuffer(onFlushRP))
         , fVertexBuffer(GrCCPathProcessor::FindVertexBuffer(onFlushRP))
         , fInstanceBuffer(onFlushRP->makeBuffer(kVertex_GrBufferType,
@@ -36,34 +88,42 @@
     }
     fPathInstanceData = static_cast<PathInstance*>(fInstanceBuffer->map());
     SkASSERT(fPathInstanceData);
-    SkDEBUGCODE(fPathInstanceBufferCount = specs.fNumRenderedPaths);
+    SkDEBUGCODE(fEndPathInstance = specs.fNumRenderedPaths);
 }
 
-GrCCAtlas* GrCCPerFlushResources::renderPathInAtlas(const SkIRect& clipIBounds, const SkMatrix& m,
-                                                    const SkPath& path, SkRect* devBounds,
-                                                    SkRect* devBounds45, int16_t* atlasOffsetX,
-                                                    int16_t* atlasOffsetY) {
+const GrCCAtlas* GrCCPerFlushResources::renderPathInAtlas(const SkIRect& clipIBounds,
+                                                          const SkMatrix& m, const SkPath& path,
+                                                          SkRect* devBounds, SkRect* devBounds45,
+                                                          SkIVector* devToAtlasOffset) {
     SkASSERT(this->isMapped());
+    SkASSERT(fNextPathInstanceIdx < fEndPathInstance);
+
+    fPathParser.parsePath(m, path, devBounds, devBounds45);
+
     SkIRect devIBounds;
-    fPathParser->parsePath(m, path, devBounds, devBounds45);
     devBounds->roundOut(&devIBounds);
-    return this->placeParsedPathInAtlas(clipIBounds, devIBounds, atlasOffsetX, atlasOffsetY);
+
+    if (!this->placeParsedPathInAtlas(clipIBounds, devIBounds, devToAtlasOffset)) {
+        SkDEBUGCODE(--fEndPathInstance);
+        return nullptr;  // Path was degenerate or clipped away.
+    }
+    return &fAtlasStack.current();
 }
 
-GrCCAtlas* GrCCPerFlushResources::renderDeviceSpacePathInAtlas(const SkIRect& clipIBounds,
-                                                               const SkPath& devPath,
-                                                               const SkIRect& devPathIBounds,
-                                                               int16_t* atlasOffsetX,
-                                                               int16_t* atlasOffsetY) {
+const GrCCAtlas* GrCCPerFlushResources::renderDeviceSpacePathInAtlas(
+        const SkIRect& clipIBounds, const SkPath& devPath, const SkIRect& devPathIBounds,
+        SkIVector* devToAtlasOffset) {
     SkASSERT(this->isMapped());
-    fPathParser->parseDeviceSpacePath(devPath);
-    return this->placeParsedPathInAtlas(clipIBounds, devPathIBounds, atlasOffsetX, atlasOffsetY);
+    fPathParser.parseDeviceSpacePath(devPath);
+    if (!this->placeParsedPathInAtlas(clipIBounds, devPathIBounds, devToAtlasOffset)) {
+        return nullptr;
+    }
+    return &fAtlasStack.current();
 }
 
-GrCCAtlas* GrCCPerFlushResources::placeParsedPathInAtlas(const SkIRect& clipIBounds,
-                                                         const SkIRect& pathIBounds,
-                                                         int16_t* atlasOffsetX,
-                                                         int16_t* atlasOffsetY) {
+bool GrCCPerFlushResources::placeParsedPathInAtlas(const SkIRect& clipIBounds,
+                                                   const SkIRect& pathIBounds,
+                                                   SkIVector* devToAtlasOffset) {
     using ScissorMode = GrCCPathParser::ScissorMode;
     ScissorMode scissorMode;
     SkIRect clippedPathIBounds;
@@ -73,50 +133,48 @@
     } else if (clippedPathIBounds.intersect(clipIBounds, pathIBounds)) {
         scissorMode = ScissorMode::kScissored;
     } else {
-        fPathParser->discardParsedPath();
-        return nullptr;
+        fPathParser.discardParsedPath();
+        return false;
     }
 
-    SkIPoint16 atlasLocation;
-    int h = clippedPathIBounds.height(), w = clippedPathIBounds.width();
-    if (fAtlases.empty() || !fAtlases.back().addRect(w, h, &atlasLocation)) {
-        if (!fAtlases.empty()) {
-            // The atlas is out of room and can't grow any bigger.
-            auto coverageCountBatchID = fPathParser->closeCurrentBatch();
-            fAtlases.back().setCoverageCountBatchID(coverageCountBatchID);
-        }
-        fAtlases.emplace_back(fAtlasSpecs);
-        SkAssertResult(fAtlases.back().addRect(w, h, &atlasLocation));
+    if (GrCCAtlas* retiredAtlas = fAtlasStack.addRect(clippedPathIBounds, devToAtlasOffset)) {
+        // We did not fit in the previous coverage count atlas and it was retired. Close the path
+        // parser's current batch (which does not yet include the path we just parsed). We will
+        // render this batch into the retired atlas during finalize().
+        CoverageCountBatchID batchID = fPathParser.closeCurrentBatch();
+        retiredAtlas->setUserBatchID(batchID);
     }
-
-    *atlasOffsetX = atlasLocation.x() - static_cast<int16_t>(clippedPathIBounds.left());
-    *atlasOffsetY = atlasLocation.y() - static_cast<int16_t>(clippedPathIBounds.top());
-    fPathParser->saveParsedPath(scissorMode, clippedPathIBounds, *atlasOffsetX, *atlasOffsetY);
-
-    return &fAtlases.back();
+    fPathParser.saveParsedPath(scissorMode, clippedPathIBounds, *devToAtlasOffset);
+    return true;
 }
 
 bool GrCCPerFlushResources::finalize(GrOnFlushResourceProvider* onFlushRP,
-                                     SkTArray<sk_sp<GrRenderTargetContext>>* atlasDraws) {
+                                     SkTArray<sk_sp<GrRenderTargetContext>>* out) {
     SkASSERT(this->isMapped());
+    SkASSERT(fNextPathInstanceIdx == fEndPathInstance);
+
     fInstanceBuffer->unmap();
     fPathInstanceData = nullptr;
 
-    if (!fAtlases.empty()) {
-        auto coverageCountBatchID = fPathParser->closeCurrentBatch();
-        fAtlases.back().setCoverageCountBatchID(coverageCountBatchID);
+    if (!fAtlasStack.empty()) {
+        CoverageCountBatchID batchID = fPathParser.closeCurrentBatch();
+        fAtlasStack.current().setUserBatchID(batchID);
     }
 
-    if (!fPathParser->finalize(onFlushRP)) {
+    // Build the GPU buffers to render path coverage counts. (This must not happen until after the
+    // final call to fPathParser.closeCurrentBatch().)
+    if (!fPathParser.finalize(onFlushRP)) {
         SkDebugf("WARNING: failed to allocate GPU buffers for CCPR. No paths will be drawn.\n");
         return false;
     }
 
-    // Draw the atlas(es).
-    GrTAllocator<GrCCAtlas>::Iter atlasIter(&fAtlases);
-    while (atlasIter.next()) {
-        if (auto rtc = atlasIter.get()->finalize(onFlushRP, fPathParser)) {
-            atlasDraws->push_back(std::move(rtc));
+    // Render the atlas(es).
+    for (GrCCAtlasStack::Iter atlas(fAtlasStack); atlas.next();) {
+        if (auto rtc = atlas->initInternalTextureProxy(onFlushRP, kAlpha_half_GrPixelConfig)) {
+            auto op = RenderAtlasOp::Make(rtc->surfPriv().getContext(), sk_ref_sp(this),
+                                          atlas->getUserBatchID(), atlas->drawBounds());
+            rtc->addDrawOp(GrNoClip(), std::move(op));
+            out->push_back(std::move(rtc));
         }
     }
 
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.h b/src/gpu/ccpr/GrCCPerFlushResources.h
index 14e3a1c..e920c06 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.h
+++ b/src/gpu/ccpr/GrCCPerFlushResources.h
@@ -8,7 +8,6 @@
 #ifndef GrCCPerFlushResources_DEFINED
 #define GrCCPerFlushResources_DEFINED
 
-#include "GrAllocator.h"
 #include "GrNonAtomicRef.h"
 #include "ccpr/GrCCAtlas.h"
 #include "ccpr/GrCCPathParser.h"
@@ -38,42 +37,50 @@
 
     bool isMapped() const { return SkToBool(fPathInstanceData); }
 
-    GrCCAtlas* renderPathInAtlas(const SkIRect& clipIBounds, const SkMatrix&, const SkPath&,
-                                 SkRect* devBounds, SkRect* devBounds45, int16_t* offsetX,
-                                 int16_t* offsetY);
-    GrCCAtlas* renderDeviceSpacePathInAtlas(const SkIRect& clipIBounds, const SkPath& devPath,
-                                            const SkIRect& devPathIBounds, int16_t* atlasOffsetX,
-                                            int16_t* atlasOffsetY);
+    // Renders a path into a temporary atlas. See GrCCPathParser for a description of the arguments.
+    const GrCCAtlas* renderPathInAtlas(const SkIRect& clipIBounds, const SkMatrix&, const SkPath&,
+                                       SkRect* devBounds, SkRect* devBounds45,
+                                       SkIVector* devToAtlasOffset);
+    const GrCCAtlas* renderDeviceSpacePathInAtlas(const SkIRect& clipIBounds, const SkPath& devPath,
+                                                  const SkIRect& devPathIBounds,
+                                                  SkIVector* devToAtlasOffset);
 
-    GrCCPathProcessor::Instance& appendDrawPathInstance() {
-        SkASSERT(this->isMapped());
-        SkASSERT(fNextPathInstanceIdx < fPathInstanceBufferCount);
-        return fPathInstanceData[fNextPathInstanceIdx++];
-    }
+    // Returns the index in instanceBuffer() of the next instance that will be added by
+    // appendDrawPathInstance().
     int nextPathInstanceIdx() const { return fNextPathInstanceIdx; }
 
-    bool finalize(GrOnFlushResourceProvider*, SkTArray<sk_sp<GrRenderTargetContext>>* atlasDraws);
+    // Appends an instance to instanceBuffer() that will draw a path to the destination render
+    // target. The caller is responsible to call set() on the returned instance, to keep track of
+    // its atlas and index (see nextPathInstanceIdx()), and to issue the actual draw call.
+    GrCCPathProcessor::Instance& appendDrawPathInstance() {
+        SkASSERT(this->isMapped());
+        SkASSERT(fNextPathInstanceIdx < fEndPathInstance);
+        return fPathInstanceData[fNextPathInstanceIdx++];
+    }
 
+    // Finishes off the GPU buffers and renders the atlas(es).
+    bool finalize(GrOnFlushResourceProvider*, SkTArray<sk_sp<GrRenderTargetContext>>* out);
+
+    // Accessors used by draw calls, once the resources have been finalized.
+    const GrCCPathParser& pathParser() const { SkASSERT(!this->isMapped()); return fPathParser; }
     const GrBuffer* indexBuffer() const { SkASSERT(!this->isMapped()); return fIndexBuffer.get(); }
     const GrBuffer* vertexBuffer() const { SkASSERT(!this->isMapped()); return fVertexBuffer.get();}
     GrBuffer* instanceBuffer() const { SkASSERT(!this->isMapped()); return fInstanceBuffer.get(); }
 
 private:
-    GrCCAtlas* placeParsedPathInAtlas(const SkIRect& clipIBounds, const SkIRect& pathIBounds,
-                                      int16_t* atlasOffsetX, int16_t* atlasOffsetY);
+    bool placeParsedPathInAtlas(const SkIRect& clipIBounds, const SkIRect& pathIBounds,
+                                SkIVector* devToAtlasOffset);
 
-    const sk_sp<GrCCPathParser> fPathParser;
-    const GrCCAtlas::Specs fAtlasSpecs;
+    GrCCPathParser fPathParser;
+    GrCCAtlasStack fAtlasStack;
 
-    sk_sp<const GrBuffer> fIndexBuffer;
-    sk_sp<const GrBuffer> fVertexBuffer;
-    sk_sp<GrBuffer> fInstanceBuffer;
+    const sk_sp<const GrBuffer> fIndexBuffer;
+    const sk_sp<const GrBuffer> fVertexBuffer;
+    const sk_sp<GrBuffer> fInstanceBuffer;
 
     GrCCPathProcessor::Instance* fPathInstanceData = nullptr;
     int fNextPathInstanceIdx = 0;
-    SkDEBUGCODE(int fPathInstanceBufferCount);
-
-    GrSTAllocator<4, GrCCAtlas> fAtlases;
+    SkDEBUGCODE(int fEndPathInstance);
 };
 
 #endif
diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
index 9ad945c..727649b 100644
--- a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
+++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
@@ -179,7 +179,7 @@
 
 void GrCoverageCountingPathRenderer::preFlush(GrOnFlushResourceProvider* onFlushRP,
                                               const uint32_t* opListIDs, int numOpListIDs,
-                                              SkTArray<sk_sp<GrRenderTargetContext>>* atlasDraws) {
+                                              SkTArray<sk_sp<GrRenderTargetContext>>* out) {
     SkASSERT(!fFlushing);
     SkASSERT(fFlushingPaths.empty());
     SkDEBUGCODE(fFlushing = true);
@@ -236,7 +236,7 @@
     SkASSERT(resources->nextPathInstanceIdx() == resourceSpecs.fNumRenderedPaths - numSkippedPaths);
 
     // Allocate the atlases and create instance buffers to draw them.
-    if (!resources->finalize(onFlushRP, atlasDraws)) {
+    if (!resources->finalize(onFlushRP, out)) {
         return;
     }
 
diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.h b/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
index 63e52a9..abc824b 100644
--- a/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
+++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
@@ -67,7 +67,7 @@
 
     // GrOnFlushCallbackObject overrides.
     void preFlush(GrOnFlushResourceProvider*, const uint32_t* opListIDs, int numOpListIDs,
-                  SkTArray<sk_sp<GrRenderTargetContext>>* atlasDraws) override;
+                  SkTArray<sk_sp<GrRenderTargetContext>>* out) override;
     void postFlush(GrDeferredUploadToken, const uint32_t* opListIDs, int numOpListIDs) override;
 
 private: