ccpr: Initialize the atlas size more intelligently

Rather than always starting the atlas at 1024 x 1024, begin with the
first pow2 dimensions whose area is theoretically large enough to
contain the pending paths.

Bug: skia:
Change-Id: I263e77ff6a697e865f6b3b62b9df7002225f9544
Reviewed-on: https://skia-review.googlesource.com/133660
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/src/gpu/ccpr/GrCCAtlas.cpp b/src/gpu/ccpr/GrCCAtlas.cpp
index df84ed7..551b2a0 100644
--- a/src/gpu/ccpr/GrCCAtlas.cpp
+++ b/src/gpu/ccpr/GrCCAtlas.cpp
@@ -21,9 +21,6 @@
 #include "ccpr/GrCCPathParser.h"
 #include "ops/GrDrawOp.h"
 
-static constexpr int kAtlasMinSize = 1024;
-static constexpr int kPadding = 1;
-
 class GrCCAtlas::Node {
 public:
     Node(std::unique_ptr<Node> previous, int l, int t, int r, int b)
@@ -97,15 +94,27 @@
     typedef GrDrawOp INHERITED;
 };
 
-GrCCAtlas::GrCCAtlas(const GrCaps& caps, int minSize)
-        : fMaxAtlasSize(SkTMax(minSize, caps.maxPreferredRenderTargetSize())) {
-    // Caller should have cropped any paths to the destination render target instead of asking for
-    // an atlas larger than maxRenderTargetSize.
-    SkASSERT(fMaxAtlasSize <= caps.maxRenderTargetSize());
-    int initialSize = GrNextPow2(minSize + kPadding);
-    initialSize = SkTMax(kAtlasMinSize, initialSize);
-    initialSize = SkTMin(initialSize, fMaxAtlasSize);
-    fHeight = fWidth = initialSize;
+GrCCAtlas::GrCCAtlas(const Specs& specs)
+        : fMaxTextureSize(SkTMax(SkTMax(specs.fMinHeight, specs.fMinWidth),
+                                 specs.fMaxPreferredTextureSize)) {
+    SkASSERT(specs.fMaxPreferredTextureSize > 0);
+
+    // Begin with the first pow2 dimensions whose area is theoretically large enough to contain the
+    // pending paths, favoring height over width if necessary.
+    int log2area = SkNextLog2(SkTMax(specs.fApproxNumPixels, 1));
+    fHeight = 1 << ((log2area + 1) / 2);
+    fWidth = 1 << (log2area / 2);
+
+    fWidth = SkTClamp(fWidth, specs.fMinTextureSize, specs.fMaxPreferredTextureSize);
+    fHeight = SkTClamp(fHeight, specs.fMinTextureSize, specs.fMaxPreferredTextureSize);
+
+    if (fWidth < specs.fMinWidth || fHeight < specs.fMinHeight) {
+        // They want to stuff a particularly large path into the atlas. Just punt and go with their
+        // min width and height. The atlas will grow as needed.
+        fWidth = SkTMin(specs.fMinWidth + kPadding, fMaxTextureSize);
+        fHeight = SkTMin(specs.fMinHeight + kPadding, fMaxTextureSize);
+    }
+
     fTopNode = skstd::make_unique<Node>(nullptr, 0, 0, fWidth, fHeight);
 }
 
@@ -128,26 +137,26 @@
 
 bool GrCCAtlas::internalPlaceRect(int w, int h, SkIPoint16* loc) {
     for (Node* node = fTopNode.get(); node; node = node->previous()) {
-        if (node->addRect(w, h, loc, fMaxAtlasSize)) {
+        if (node->addRect(w, h, loc, fMaxTextureSize)) {
             return true;
         }
     }
 
     // The rect didn't fit. Grow the atlas and try again.
     do {
-        if (fWidth == fMaxAtlasSize && fHeight == fMaxAtlasSize) {
+        if (fWidth == fMaxTextureSize && fHeight == fMaxTextureSize) {
             return false;
         }
         if (fHeight <= fWidth) {
             int top = fHeight;
-            fHeight = SkTMin(fHeight * 2, fMaxAtlasSize);
+            fHeight = SkTMin(fHeight * 2, fMaxTextureSize);
             fTopNode = skstd::make_unique<Node>(std::move(fTopNode), 0, top, fWidth, fHeight);
         } else {
             int left = fWidth;
-            fWidth = SkTMin(fWidth * 2, fMaxAtlasSize);
+            fWidth = SkTMin(fWidth * 2, fMaxTextureSize);
             fTopNode = skstd::make_unique<Node>(std::move(fTopNode), left, 0, fWidth, fHeight);
         }
-    } while (!fTopNode->addRect(w, h, loc, fMaxAtlasSize));
+    } while (!fTopNode->addRect(w, h, loc, fMaxTextureSize));
 
     return true;
 }
@@ -156,6 +165,9 @@
                                                  sk_sp<const GrCCPathParser> parser) {
     SkASSERT(fCoverageCountBatchID);
     SkASSERT(!fTextureProxy);
+    // Caller should have cropped any paths to the destination render target instead of asking for
+    // an atlas larger than maxRenderTargetSize.
+    SkASSERT(fMaxTextureSize <= onFlushRP->caps()->maxRenderTargetSize());
 
     GrSurfaceDesc desc;
     desc.fFlags = kRenderTarget_GrSurfaceFlag;
diff --git a/src/gpu/ccpr/GrCCAtlas.h b/src/gpu/ccpr/GrCCAtlas.h
index 184022e..b30bdbe 100644
--- a/src/gpu/ccpr/GrCCAtlas.h
+++ b/src/gpu/ccpr/GrCCAtlas.h
@@ -27,8 +27,22 @@
 class GrCCAtlas {
 public:
     using CoverageCountBatchID = int;
+    static constexpr int kPadding = 1;  // Amount of padding below and to the right of each path.
 
-    GrCCAtlas(const GrCaps&, int minSize);
+    // This struct encapsulates the minimum and desired requirements for an atlas, as well as an
+    // approximate number of pixels to help select a good initial size.
+    struct Specs {
+        int fMaxPreferredTextureSize = 0;
+        int fMinTextureSize = 0;
+        int fMinWidth = 0;  // If there are 100 20x10 paths, this should be 20.
+        int fMinHeight = 0;  // If there are 100 20x10 paths, this should be 10.
+        int fApproxNumPixels = 0;
+
+        // Add space for a rect in the desired atlas specs.
+        void accountForSpace(int width, int height);
+    };
+
+    GrCCAtlas(const Specs&);
     ~GrCCAtlas();
 
     bool addRect(int devWidth, int devHeight, SkIPoint16* loc);
@@ -51,8 +65,7 @@
 
     bool internalPlaceRect(int w, int h, SkIPoint16* loc);
 
-    const int fMaxAtlasSize;
-
+    const int fMaxTextureSize;
     int fWidth, fHeight;
     std::unique_ptr<Node> fTopNode;
     SkISize fDrawBounds = {0, 0};
@@ -61,4 +74,10 @@
     sk_sp<GrTextureProxy> fTextureProxy;
 };
 
+inline void GrCCAtlas::Specs::accountForSpace(int width, int height) {
+    fMinWidth = SkTMax(width, fMinWidth);
+    fMinHeight = SkTMax(height, fMinHeight);
+    fApproxNumPixels += (width + kPadding) * (height + kPadding);
+}
+
 #endif
diff --git a/src/gpu/ccpr/GrCCClipPath.cpp b/src/gpu/ccpr/GrCCClipPath.cpp
index 9b8a737..c988657 100644
--- a/src/gpu/ccpr/GrCCClipPath.cpp
+++ b/src/gpu/ccpr/GrCCClipPath.cpp
@@ -48,12 +48,23 @@
     fAccessRect = accessRect;
 }
 
+void GrCCClipPath::accountForOwnPath(GrCCPerFlushResourceSpecs* resourceSpecs) const {
+    SkASSERT(this->isInitialized());
+
+    ++resourceSpecs->fNumClipPaths;
+    resourceSpecs->fParsingPathStats.statPath(fDeviceSpacePath);
+
+    SkIRect ibounds;
+    if (ibounds.intersect(fAccessRect, fPathDevIBounds)) {
+        resourceSpecs->fAtlasSpecs.accountForSpace(ibounds.width(), ibounds.height());
+    }
+}
+
 void GrCCClipPath::renderPathInAtlas(GrCCPerFlushResources* resources,
                                      GrOnFlushResourceProvider* onFlushRP) {
     SkASSERT(this->isInitialized());
     SkASSERT(!fHasAtlas);
-    fAtlas = resources->renderDeviceSpacePathInAtlas(*onFlushRP->caps(), fAccessRect,
-                                                     fDeviceSpacePath, fPathDevIBounds,
+    fAtlas = resources->renderDeviceSpacePathInAtlas(fAccessRect, fDeviceSpacePath, fPathDevIBounds,
                                                      &fAtlasOffsetX, &fAtlasOffsetY);
     SkDEBUGCODE(fHasAtlas = true);
 }
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.cpp b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
index 01913fa..f29fca2 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.cpp
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
@@ -13,15 +13,6 @@
 #include "ccpr/GrCCPerFlushResources.h"
 #include "ccpr/GrCoverageCountingPathRenderer.h"
 
-GrCCDrawPathsOp* GrCCDrawPathsOp::Make(GrContext* context,
-                                       GrPaint&& paint,
-                                       const SkIRect& clipIBounds,
-                                       const SkMatrix& m,
-                                       const SkPath& path,
-                                       const SkRect& devBounds) {
-    return new GrCCDrawPathsOp(std::move(paint), clipIBounds, m, path, devBounds);
-}
-
 static bool has_coord_transforms(const GrPaint& paint) {
     GrFragmentProcessor::Iter iter(paint);
     while (const GrFragmentProcessor* fp = iter.next()) {
@@ -32,13 +23,25 @@
     return false;
 }
 
-GrCCDrawPathsOp::GrCCDrawPathsOp(GrPaint&& paint, const SkIRect& clipIBounds, const SkMatrix& m,
-                                 const SkPath& path, const SkRect& devBounds)
+std::unique_ptr<GrCCDrawPathsOp> GrCCDrawPathsOp::Make(GrContext*, const SkIRect& clipIBounds,
+                                                       const SkMatrix& m, const SkPath& path,
+                                                       const SkRect& devBounds, GrPaint&& paint) {
+    SkIRect looseClippedIBounds;
+    devBounds.roundOut(&looseClippedIBounds);  // GrCCPathParser might find slightly tighter bounds.
+    if (!looseClippedIBounds.intersect(clipIBounds)) {
+        return nullptr;
+    }
+    return std::unique_ptr<GrCCDrawPathsOp>(
+                   new GrCCDrawPathsOp(looseClippedIBounds, m, path, devBounds, std::move(paint)));
+}
+
+GrCCDrawPathsOp::GrCCDrawPathsOp(const SkIRect& looseClippedIBounds, const SkMatrix& m,
+                                 const SkPath& path, const SkRect& devBounds, GrPaint&& paint)
         : GrDrawOp(ClassID())
-        , fSRGBFlags(GrPipeline::SRGBFlagsFromPaint(paint))
         , fViewMatrixIfUsingLocalCoords(has_coord_transforms(paint) ? m : SkMatrix::I())
-        , fDraws({clipIBounds, m, path, paint.getColor(), nullptr})
-        , fProcessors(std::move(paint)) {
+        , fSRGBFlags(GrPipeline::SRGBFlagsFromPaint(paint))
+        , fDraws({looseClippedIBounds, m, path, paint.getColor(), nullptr})
+        , fProcessors(std::move(paint)) {  // Paint must be moved after fetching its color above.
     SkDEBUGCODE(fBaseInstance = -1);
     // FIXME: intersect with clip bounds to (hopefully) improve batching.
     // (This is nontrivial due to assumptions in generating the octagon cover geometry.)
@@ -90,13 +93,13 @@
     fOwningPerOpListPaths = owningPerOpListPaths;
 }
 
-int GrCCDrawPathsOp::countPaths(GrCCPathParser::PathStats* stats) const {
-    int numPaths = 0;
+void GrCCDrawPathsOp::accountForOwnPaths(GrCCPerFlushResourceSpecs* resourceSpecs) const {
     for (const GrCCDrawPathsOp::SingleDraw& draw : fDraws) {
-        stats->statPath(draw.fPath);
-        ++numPaths;
+        ++resourceSpecs->fNumRenderedPaths;
+        resourceSpecs->fParsingPathStats.statPath(draw.fPath);
+        resourceSpecs->fAtlasSpecs.accountForSpace(draw.fLooseClippedIBounds.width(),
+                                                   draw.fLooseClippedIBounds.height());
     }
-    return numPaths;
 }
 
 void GrCCDrawPathsOp::setupResources(GrCCPerFlushResources* resources,
@@ -112,9 +115,9 @@
         // bounding boxes to generate an octagon that circumscribes the path.
         SkRect devBounds, devBounds45;
         int16_t atlasOffsetX, atlasOffsetY;
-        GrCCAtlas* atlas = resources->renderPathInAtlas(*onFlushRP->caps(), draw.fClipIBounds,
-                                                        draw.fMatrix, draw.fPath, &devBounds,
-                                                        &devBounds45, &atlasOffsetX, &atlasOffsetY);
+        GrCCAtlas* atlas = resources->renderPathInAtlas(draw.fLooseClippedIBounds, draw.fMatrix,
+                                                        draw.fPath, &devBounds, &devBounds45,
+                                                        &atlasOffsetX, &atlasOffsetY);
         if (!atlas) {
             SkDEBUGCODE(++fNumSkippedInstances);
             continue;
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.h b/src/gpu/ccpr/GrCCDrawPathsOp.h
index 303f52b..b46039d 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.h
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.h
@@ -9,11 +9,11 @@
 #define GrCCDrawPathsOp_DEFINED
 
 #include "SkTInternalLList.h"
-#include "ccpr/GrCCPathParser.h"
 #include "ccpr/GrCCPathProcessor.h"
 #include "ccpr/GrCCSTLList.h"
 #include "ops/GrDrawOp.h"
 
+struct GrCCPerFlushResourceSpecs;
 class GrCCAtlas;
 class GrCCPerFlushResources;
 class GrCCPerOpListPaths;
@@ -26,13 +26,9 @@
     DEFINE_OP_CLASS_ID
     SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrCCDrawPathsOp);
 
-    static GrCCDrawPathsOp* Make(GrContext*,
-                                 GrPaint&&,
-                                 const SkIRect& clipIBounds,
-                                 const SkMatrix&,
-                                 const SkPath&,
-                                 const SkRect& devBounds);
-
+    static std::unique_ptr<GrCCDrawPathsOp> Make(GrContext*, const SkIRect& clipIBounds,
+                                                 const SkMatrix&, const SkPath&,
+                                                 const SkRect& devBounds, GrPaint&&);
     ~GrCCDrawPathsOp() override;
 
     const char* name() const override { return "GrCCDrawOp"; }
@@ -46,7 +42,7 @@
     void onPrepare(GrOpFlushState*) override {}
 
     void wasRecorded(GrCCPerOpListPaths* owningPerOpListPaths);
-    int countPaths(GrCCPathParser::PathStats*) const;
+    void accountForOwnPaths(GrCCPerFlushResourceSpecs*) const;
     void setupResources(GrCCPerFlushResources*, GrOnFlushResourceProvider*);
     SkDEBUGCODE(int numSkippedInstances_debugOnly() const { return fNumSkippedInstances; })
 
@@ -55,8 +51,8 @@
 private:
     friend class GrOpMemoryPool;
 
-    GrCCDrawPathsOp(GrPaint&&, const SkIRect& clipIBounds, const SkMatrix&, const SkPath&,
-                    const SkRect& devBounds);
+    GrCCDrawPathsOp(const SkIRect& clippedDevIBounds, const SkMatrix&, const SkPath&,
+                    const SkRect& devBounds, GrPaint&&);
 
     struct AtlasBatch {
         const GrCCAtlas* fAtlas;
@@ -70,11 +66,11 @@
         fAtlasBatches.push_back() = {atlas, endInstanceIdx};
     }
 
-    const uint32_t fSRGBFlags;
     const SkMatrix fViewMatrixIfUsingLocalCoords;
+    const uint32_t fSRGBFlags;
 
     struct SingleDraw {
-        SkIRect fClipIBounds;
+        SkIRect fLooseClippedIBounds;
         SkMatrix fMatrix;
         SkPath fPath;
         GrColor fColor;
@@ -84,8 +80,8 @@
     GrCCSTLList<SingleDraw> fDraws;
     SkDEBUGCODE(int fNumDraws = 1);
 
-    GrProcessorSet fProcessors;
     GrCCPerOpListPaths* fOwningPerOpListPaths = nullptr;
+    GrProcessorSet fProcessors;
 
     int fBaseInstance;
     SkSTArray<1, AtlasBatch, true> fAtlasBatches;
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.cpp b/src/gpu/ccpr/GrCCPerFlushResources.cpp
index 3f02ac7..a80d789 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.cpp
+++ b/src/gpu/ccpr/GrCCPerFlushResources.cpp
@@ -14,13 +14,14 @@
 using PathInstance = GrCCPathProcessor::Instance;
 
 GrCCPerFlushResources::GrCCPerFlushResources(GrOnFlushResourceProvider* onFlushRP,
-                                             int numPathDraws, int numClipPaths,
-                                             const GrCCPathParser::PathStats& pathStats)
-        : fPathParser(sk_make_sp<GrCCPathParser>(numPathDraws + numClipPaths, pathStats))
+                                             const GrCCPerFlushResourceSpecs& specs)
+        : fPathParser(sk_make_sp<GrCCPathParser>(specs.fNumRenderedPaths + specs.fNumClipPaths,
+                                                 specs.fParsingPathStats))
+        , fAtlasSpecs(specs.fAtlasSpecs)
         , fIndexBuffer(GrCCPathProcessor::FindIndexBuffer(onFlushRP))
         , fVertexBuffer(GrCCPathProcessor::FindVertexBuffer(onFlushRP))
         , fInstanceBuffer(onFlushRP->makeBuffer(kVertex_GrBufferType,
-                                                numPathDraws * sizeof(PathInstance))) {
+                                                specs.fNumRenderedPaths * sizeof(PathInstance))) {
     if (!fIndexBuffer) {
         SkDebugf("WARNING: failed to allocate CCPR index buffer. No paths will be drawn.\n");
         return;
@@ -35,34 +36,31 @@
     }
     fPathInstanceData = static_cast<PathInstance*>(fInstanceBuffer->map());
     SkASSERT(fPathInstanceData);
-    SkDEBUGCODE(fPathInstanceBufferCount = numPathDraws);
+    SkDEBUGCODE(fPathInstanceBufferCount = specs.fNumRenderedPaths);
 }
 
-GrCCAtlas* GrCCPerFlushResources::renderPathInAtlas(const GrCaps& caps, const SkIRect& clipIBounds,
-                                                    const SkMatrix& m, const SkPath& path,
-                                                    SkRect* devBounds, SkRect* devBounds45,
-                                                    int16_t* atlasOffsetX, int16_t* atlasOffsetY) {
+GrCCAtlas* GrCCPerFlushResources::renderPathInAtlas(const SkIRect& clipIBounds, const SkMatrix& m,
+                                                    const SkPath& path, SkRect* devBounds,
+                                                    SkRect* devBounds45, int16_t* atlasOffsetX,
+                                                    int16_t* atlasOffsetY) {
     SkASSERT(this->isMapped());
     SkIRect devIBounds;
     fPathParser->parsePath(m, path, devBounds, devBounds45);
     devBounds->roundOut(&devIBounds);
-    return this->placeParsedPathInAtlas(caps, clipIBounds, devIBounds, atlasOffsetX, atlasOffsetY);
+    return this->placeParsedPathInAtlas(clipIBounds, devIBounds, atlasOffsetX, atlasOffsetY);
 }
 
-GrCCAtlas* GrCCPerFlushResources::renderDeviceSpacePathInAtlas(const GrCaps& caps,
-                                                               const SkIRect& clipIBounds,
+GrCCAtlas* GrCCPerFlushResources::renderDeviceSpacePathInAtlas(const SkIRect& clipIBounds,
                                                                const SkPath& devPath,
                                                                const SkIRect& devPathIBounds,
                                                                int16_t* atlasOffsetX,
                                                                int16_t* atlasOffsetY) {
     SkASSERT(this->isMapped());
     fPathParser->parseDeviceSpacePath(devPath);
-    return this->placeParsedPathInAtlas(caps, clipIBounds, devPathIBounds, atlasOffsetX,
-                                        atlasOffsetY);
+    return this->placeParsedPathInAtlas(clipIBounds, devPathIBounds, atlasOffsetX, atlasOffsetY);
 }
 
-GrCCAtlas* GrCCPerFlushResources::placeParsedPathInAtlas(const GrCaps& caps,
-                                                         const SkIRect& clipIBounds,
+GrCCAtlas* GrCCPerFlushResources::placeParsedPathInAtlas(const SkIRect& clipIBounds,
                                                          const SkIRect& pathIBounds,
                                                          int16_t* atlasOffsetX,
                                                          int16_t* atlasOffsetY) {
@@ -87,7 +85,7 @@
             auto coverageCountBatchID = fPathParser->closeCurrentBatch();
             fAtlases.back().setCoverageCountBatchID(coverageCountBatchID);
         }
-        fAtlases.emplace_back(caps, SkTMax(w, h));
+        fAtlases.emplace_back(fAtlasSpecs);
         SkAssertResult(fAtlases.back().addRect(w, h, &atlasLocation));
     }
 
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.h b/src/gpu/ccpr/GrCCPerFlushResources.h
index bc6b6ed..14e3a1c 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.h
+++ b/src/gpu/ccpr/GrCCPerFlushResources.h
@@ -15,23 +15,35 @@
 #include "ccpr/GrCCPathProcessor.h"
 
 /**
+ * This struct encapsulates the minimum and desired requirements for the GPU resources required by
+ * CCPR in a given flush.
+ */
+struct GrCCPerFlushResourceSpecs {
+    int fNumRenderedPaths = 0;
+    int fNumClipPaths = 0;
+    GrCCPathParser::PathStats fParsingPathStats;
+    GrCCAtlas::Specs fAtlasSpecs;
+
+    bool isEmpty() const { return 0 == fNumRenderedPaths + fNumClipPaths; }
+};
+
+/**
  * This class wraps all the GPU resources that CCPR builds at flush time. It is allocated in CCPR's
  * preFlush() method, and referenced by all the GrCCPerOpListPaths objects that are being flushed.
  * It is deleted in postFlush() once all the flushing GrCCPerOpListPaths objects are deleted.
  */
 class GrCCPerFlushResources : public GrNonAtomicRef<GrCCPerFlushResources> {
 public:
-    GrCCPerFlushResources(GrOnFlushResourceProvider*, int numPathDraws, int numClipPaths,
-                          const GrCCPathParser::PathStats&);
+    GrCCPerFlushResources(GrOnFlushResourceProvider*, const GrCCPerFlushResourceSpecs&);
 
     bool isMapped() const { return SkToBool(fPathInstanceData); }
 
-    GrCCAtlas* renderPathInAtlas(const GrCaps&, const SkIRect& clipIBounds, const SkMatrix&,
-                                 const SkPath&, SkRect* devBounds, SkRect* devBounds45,
-                                 int16_t* offsetX, int16_t* offsetY);
-    GrCCAtlas* renderDeviceSpacePathInAtlas(const GrCaps&, const SkIRect& clipIBounds,
-                                            const SkPath& devPath, const SkIRect& devPathIBounds,
-                                            int16_t* atlasOffsetX, int16_t* atlasOffsetY);
+    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);
 
     GrCCPathProcessor::Instance& appendDrawPathInstance() {
         SkASSERT(this->isMapped());
@@ -47,11 +59,11 @@
     GrBuffer* instanceBuffer() const { SkASSERT(!this->isMapped()); return fInstanceBuffer.get(); }
 
 private:
-    GrCCAtlas* placeParsedPathInAtlas(const GrCaps&, const SkIRect& clipIBounds,
-                                      const SkIRect& pathIBounds, int16_t* atlasOffsetX,
-                                      int16_t* atlasOffsetY);
+    GrCCAtlas* placeParsedPathInAtlas(const SkIRect& clipIBounds, const SkIRect& pathIBounds,
+                                      int16_t* atlasOffsetX, int16_t* atlasOffsetY);
 
     const sk_sp<GrCCPathParser> fPathParser;
+    const GrCCAtlas::Specs fAtlasSpecs;
 
     sk_sp<const GrBuffer> fIndexBuffer;
     sk_sp<const GrBuffer> fVertexBuffer;
diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
index dcb9a58..9ad945c 100644
--- a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
+++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
@@ -68,15 +68,6 @@
     return it->second.get();
 }
 
-void GrCoverageCountingPathRenderer::adoptAndRecordOp(GrCCDrawPathsOp* op,
-                                                      const DrawPathArgs& args) {
-    GrRenderTargetContext* rtc = args.fRenderTargetContext;
-    if (uint32_t opListID = rtc->addDrawOp(*args.fClip, std::unique_ptr<GrDrawOp>(op))) {
-        // If the Op wasn't dropped or combined, give it a pointer to its owning GrCCPerOpListPaths.
-        op->wasRecorded(this->lookupPendingPaths(opListID));
-    }
-}
-
 GrPathRenderer::CanDrawPath GrCoverageCountingPathRenderer::onCanDrawPath(
         const CanDrawPathArgs& args) const {
     if (args.fShape->hasUnstyledKey() && !fDrawCachablePaths) {
@@ -127,23 +118,34 @@
     SkRect devBounds;
     args.fViewMatrix->mapRect(&devBounds, path.getBounds());
 
-    GrCCDrawPathsOp* op;
+    std::unique_ptr<GrCCDrawPathsOp> op;
     if (SkTMax(devBounds.height(), devBounds.width()) > kPathCropThreshold) {
         // The path is too large. Crop it or analytic AA can run out of fp32 precision.
         SkPath croppedPath;
         path.transform(*args.fViewMatrix, &croppedPath);
         crop_path(croppedPath, clipIBounds, &croppedPath);
-        op = GrCCDrawPathsOp::Make(args.fContext, std::move(args.fPaint), clipIBounds,
-                                   SkMatrix::I(), croppedPath, croppedPath.getBounds());
+        // FIXME: This breaks local coords: http://skbug.com/8003
+        op = GrCCDrawPathsOp::Make(args.fContext, clipIBounds, SkMatrix::I(), croppedPath,
+                                   croppedPath.getBounds(), std::move(args.fPaint));
     } else {
-        op = GrCCDrawPathsOp::Make(args.fContext, std::move(args.fPaint), clipIBounds,
-                                   *args.fViewMatrix, path, devBounds);
+        op = GrCCDrawPathsOp::Make(args.fContext, clipIBounds, *args.fViewMatrix, path, devBounds,
+                                   std::move(args.fPaint));
     }
 
-    this->adoptAndRecordOp(op, args);
+    this->recordOp(std::move(op), args);
     return true;
 }
 
+void GrCoverageCountingPathRenderer::recordOp(std::unique_ptr<GrCCDrawPathsOp> opHolder,
+                                              const DrawPathArgs& args) {
+    if (GrCCDrawPathsOp* op = opHolder.get()) {
+        GrRenderTargetContext* rtc = args.fRenderTargetContext;
+        if (uint32_t opListID = rtc->addDrawOp(*args.fClip, std::move(opHolder))) {
+            op->wasRecorded(this->lookupPendingPaths(opListID));
+        }
+    }
+}
+
 std::unique_ptr<GrFragmentProcessor> GrCoverageCountingPathRenderer::makeClipProcessor(
         GrProxyProvider* proxyProvider,
         uint32_t opListID, const SkPath& deviceSpacePath, const SkIRect& accessRect,
@@ -186,11 +188,13 @@
         return;  // Nothing to draw.
     }
 
+    GrCCPerFlushResourceSpecs resourceSpecs;
+    int maxPreferredRTSize = onFlushRP->caps()->maxPreferredRenderTargetSize();
+    resourceSpecs.fAtlasSpecs.fMaxPreferredTextureSize = maxPreferredRTSize;
+    resourceSpecs.fAtlasSpecs.fMinTextureSize = SkTMin(1024, maxPreferredRTSize);
+
     // Move the per-opList paths that are about to be flushed from fPendingPaths to fFlushingPaths,
-    // and count up the paths about to be flushed so we can preallocate buffers.
-    int numPathDraws = 0;
-    int numClipPaths = 0;
-    GrCCPathParser::PathStats flushingPathStats;
+    // and count them up so we can preallocate buffers.
     fFlushingPaths.reserve(numOpListIDs);
     for (int i = 0; i < numOpListIDs; ++i) {
         auto iter = fPendingPaths.find(opListIDs[i]);
@@ -202,20 +206,18 @@
         fPendingPaths.erase(iter);
 
         for (const GrCCDrawPathsOp* op : fFlushingPaths.back()->fDrawOps) {
-            numPathDraws += op->countPaths(&flushingPathStats);
+            op->accountForOwnPaths(&resourceSpecs);
         }
         for (const auto& clipsIter : fFlushingPaths.back()->fClipPaths) {
-            flushingPathStats.statPath(clipsIter.second.deviceSpacePath());
+            clipsIter.second.accountForOwnPath(&resourceSpecs);
         }
-        numClipPaths += fFlushingPaths.back()->fClipPaths.size();
     }
 
-    if (0 == numPathDraws + numClipPaths) {
+    if (resourceSpecs.isEmpty()) {
         return;  // Nothing to draw.
     }
 
-    auto resources = sk_make_sp<GrCCPerFlushResources>(onFlushRP, numPathDraws, numClipPaths,
-                                                       flushingPathStats);
+    auto resources = sk_make_sp<GrCCPerFlushResources>(onFlushRP, resourceSpecs);
     if (!resources->isMapped()) {
         return;  // Some allocation failed.
     }
@@ -231,7 +233,7 @@
             clipsIter.second.renderPathInAtlas(resources.get(), onFlushRP);
         }
     }
-    SkASSERT(resources->nextPathInstanceIdx() == numPathDraws - numSkippedPaths);
+    SkASSERT(resources->nextPathInstanceIdx() == resourceSpecs.fNumRenderedPaths - numSkippedPaths);
 
     // Allocate the atlases and create instance buffers to draw them.
     if (!resources->finalize(onFlushRP, atlasDraws)) {
diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.h b/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
index 4032bd5..63e52a9 100644
--- a/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
+++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
@@ -75,7 +75,7 @@
             : fDrawCachablePaths(drawCachablePaths) {}
 
     GrCCPerOpListPaths* lookupPendingPaths(uint32_t opListID);
-    void adoptAndRecordOp(GrCCDrawPathsOp*, const DrawPathArgs&);
+    void recordOp(std::unique_ptr<GrCCDrawPathsOp>, const DrawPathArgs&);
 
     // fPendingPaths holds the GrCCPerOpListPaths objects that have already been created, but not
     // flushed, and those that are still being created. All GrCCPerOpListPaths objects will first