Extract a GrDynamicAtlas base class from GrCCAtlas

Change-Id: I85fd3a3ccc34b5616a75e2c7ddcc33af18809144
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/268723
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/gn/gpu.gni b/gn/gpu.gni
index c36c167..6717e5c 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -89,6 +89,8 @@
   "$_src/gpu/GrDrawOpTest.cpp",
   "$_src/gpu/GrDrawOpTest.h",
   "$_src/gpu/GrDriverBugWorkarounds.cpp",
+  "$_src/gpu/GrDynamicAtlas.cpp",
+  "$_src/gpu/GrDynamicAtlas.h",
   "$_src/gpu/GrEagerVertexAllocator.h",
   "$_src/gpu/GrFixedClip.cpp",
   "$_src/gpu/GrFixedClip.h",
diff --git a/src/gpu/GrDynamicAtlas.cpp b/src/gpu/GrDynamicAtlas.cpp
new file mode 100644
index 0000000..5cd8a5f
--- /dev/null
+++ b/src/gpu/GrDynamicAtlas.cpp
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/gpu/GrDynamicAtlas.h"
+
+#include "src/gpu/GrOnFlushResourceProvider.h"
+#include "src/gpu/GrProxyProvider.h"
+#include "src/gpu/GrRectanizerSkyline.h"
+#include "src/gpu/GrRenderTarget.h"
+#include "src/gpu/GrRenderTargetContext.h"
+
+// Each Node covers a sub-rectangle of the final atlas. When a GrDynamicAtlas runs out of room, we
+// create a new Node the same size as all combined nodes in the atlas as-is, and then place the new
+// Node immediately below or beside the others (thereby doubling the size of the GyDynamicAtlas).
+class GrDynamicAtlas::Node {
+public:
+    Node(std::unique_ptr<Node> previous, int l, int t, int r, int b)
+            : fPrevious(std::move(previous)), fX(l), fY(t), fRectanizer(r - l, b - t) {}
+
+    Node* previous() const { return fPrevious.get(); }
+
+    bool addRect(int w, int h, SkIPoint16* loc) {
+        // Pad all paths except those that are expected to take up an entire physical texture.
+        if (w < fRectanizer.width()) {
+            w = std::min(w + kPadding, fRectanizer.width());
+        }
+        if (h < fRectanizer.height()) {
+            h = std::min(h + kPadding, fRectanizer.height());
+        }
+        if (!fRectanizer.addRect(w, h, loc)) {
+            return false;
+        }
+        loc->fX += fX;
+        loc->fY += fY;
+        return true;
+    }
+
+private:
+    const std::unique_ptr<Node> fPrevious;
+    const int fX, fY;
+    GrRectanizerSkyline fRectanizer;
+};
+
+sk_sp<GrTextureProxy> GrDynamicAtlas::MakeLazyAtlasProxy(
+        const LazyInstantiateAtlasCallback& callback, GrColorType colorType,
+        InternalMultisample internalMultisample, const GrCaps& caps,
+        GrSurfaceProxy::UseAllocator useAllocator) {
+    GrBackendFormat format = caps.getDefaultBackendFormat(colorType, GrRenderable::kYes);
+    int sampleCount = (InternalMultisample::kYes == internalMultisample) ?
+            caps.internalMultisampleCount(format) : 1;
+
+    auto instantiate = [cb = std::move(callback), format, sampleCount](GrResourceProvider* rp) {
+        return cb(rp, format, sampleCount);
+    };
+
+    GrSwizzle readSwizzle = caps.getReadSwizzle(format, colorType);
+
+    sk_sp<GrTextureProxy> proxy = GrProxyProvider::MakeFullyLazyProxy(
+            std::move(instantiate), format, readSwizzle, GrRenderable::kYes, sampleCount,
+            GrProtected::kNo, kTextureOrigin, caps, useAllocator);
+
+    return proxy;
+}
+
+GrDynamicAtlas::GrDynamicAtlas(GrColorType colorType, InternalMultisample internalMultisample,
+                               SkISize initialSize, int maxAtlasSize, const GrCaps& caps)
+        : fColorType(colorType)
+        , fInternalMultisample(internalMultisample)
+        , fMaxAtlasSize(maxAtlasSize) {
+    SkASSERT(fMaxAtlasSize <= caps.maxTextureSize());
+    this->reset(initialSize, caps);
+}
+
+GrDynamicAtlas::~GrDynamicAtlas() {
+}
+
+void GrDynamicAtlas::reset(SkISize initialSize, const GrCaps& caps) {
+    fWidth = std::min(SkNextPow2(initialSize.width()), fMaxAtlasSize);
+    fHeight = std::min(SkNextPow2(initialSize.height()), fMaxAtlasSize);
+    fTopNode = nullptr;
+    fDrawBounds.setEmpty();
+    fTextureProxy = MakeLazyAtlasProxy(
+            [this](GrResourceProvider* resourceProvider, const GrBackendFormat& format,
+                   int sampleCount) {
+                if (!fBackingTexture) {
+                    fBackingTexture = resourceProvider->createTexture(
+                            {fWidth, fHeight}, format, GrRenderable::kYes, sampleCount,
+                            GrMipMapped::kNo, SkBudgeted::kYes, GrProtected::kNo);
+                }
+                return GrSurfaceProxy::LazyCallbackResult(fBackingTexture);
+            },
+            fColorType, fInternalMultisample, caps, GrSurfaceProxy::UseAllocator::kNo);
+    fBackingTexture = nullptr;
+}
+
+bool GrDynamicAtlas::addRect(const SkIRect& devIBounds, SkIVector* offset) {
+    // This can't be called anymore once instantiate() has been called.
+    SkASSERT(!this->isInstantiated());
+
+    SkIPoint16 location;
+    if (!this->internalPlaceRect(devIBounds.width(), devIBounds.height(), &location)) {
+        return false;
+    }
+    offset->set(location.x() - devIBounds.left(), location.y() - devIBounds.top());
+
+    fDrawBounds.fWidth = std::max(fDrawBounds.width(), location.x() + devIBounds.width());
+    fDrawBounds.fHeight = std::max(fDrawBounds.height(), location.y() + devIBounds.height());
+    return true;
+}
+
+bool GrDynamicAtlas::internalPlaceRect(int w, int h, SkIPoint16* loc) {
+    if (std::max(h, w) > fMaxAtlasSize) {
+        return false;
+    }
+    if (std::min(h, w) <= 0) {
+        loc->set(0, 0);
+        return true;
+    }
+
+    if (!fTopNode) {
+        if (w > fWidth) {
+            fWidth = std::min(SkNextPow2(w), fMaxAtlasSize);
+        }
+        if (h > fHeight) {
+            fHeight = std::min(SkNextPow2(h), fMaxAtlasSize);
+        }
+        fTopNode = std::make_unique<Node>(nullptr, 0, 0, fWidth, fHeight);
+    }
+
+    for (Node* node = fTopNode.get(); node; node = node->previous()) {
+        if (node->addRect(w, h, loc)) {
+            return true;
+        }
+    }
+
+    // The rect didn't fit. Grow the atlas and try again.
+    do {
+        if (fWidth >= fMaxAtlasSize && fHeight >= fMaxAtlasSize) {
+            return false;
+        }
+        if (fHeight <= fWidth) {
+            int top = fHeight;
+            fHeight = std::min(fHeight * 2, fMaxAtlasSize);
+            fTopNode = std::make_unique<Node>(std::move(fTopNode), 0, top, fWidth, fHeight);
+        } else {
+            int left = fWidth;
+            fWidth = std::min(fWidth * 2, fMaxAtlasSize);
+            fTopNode = std::make_unique<Node>(std::move(fTopNode), left, 0, fWidth, fHeight);
+        }
+    } while (!fTopNode->addRect(w, h, loc));
+
+    return true;
+}
+
+std::unique_ptr<GrRenderTargetContext> GrDynamicAtlas::instantiate(
+        GrOnFlushResourceProvider* onFlushRP, sk_sp<GrTexture> backingTexture) {
+    SkASSERT(!this->isInstantiated());  // This method should only be called once.
+    // Caller should have cropped any paths to the destination render target instead of asking for
+    // an atlas larger than maxRenderTargetSize.
+    SkASSERT(std::max(fHeight, fWidth) <= fMaxAtlasSize);
+    SkASSERT(fMaxAtlasSize <= onFlushRP->caps()->maxRenderTargetSize());
+
+    // Finalize the content size of our proxy. The GPU can potentially make optimizations if it
+    // knows we only intend to write out a smaller sub-rectangle of the backing texture.
+    fTextureProxy->priv().setLazyDimensions(fDrawBounds);
+
+    if (backingTexture) {
+#ifdef SK_DEBUG
+        auto backingRT = backingTexture->asRenderTarget();
+        SkASSERT(backingRT);
+        SkASSERT(backingRT->backendFormat() == fTextureProxy->backendFormat());
+        SkASSERT(backingRT->numSamples() == fTextureProxy->asRenderTargetProxy()->numSamples());
+        SkASSERT(backingRT->width() == fWidth);
+        SkASSERT(backingRT->height() == fHeight);
+#endif
+        fBackingTexture = std::move(backingTexture);
+    }
+    auto rtc = onFlushRP->makeRenderTargetContext(fTextureProxy, fColorType, nullptr, nullptr);
+    if (!rtc) {
+#if GR_TEST_UTILS
+        if (!onFlushRP->testingOnly_getSuppressAllocationWarnings())
+#endif
+        {
+            SkDebugf("WARNING: failed to allocate a %ix%i atlas. Some masks will not be drawn.\n",
+                     fWidth, fHeight);
+        }
+        return nullptr;
+    }
+
+    SkIRect clearRect = SkIRect::MakeSize(fDrawBounds);
+    rtc->clear(&clearRect, SK_PMColor4fTRANSPARENT,
+               GrRenderTargetContext::CanClearFullscreen::kYes);
+    return rtc;
+}
diff --git a/src/gpu/GrDynamicAtlas.h b/src/gpu/GrDynamicAtlas.h
new file mode 100644
index 0000000..b37cbc1
--- /dev/null
+++ b/src/gpu/GrDynamicAtlas.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrDynamicAtlas_DEFINED
+#define GrDynamicAtlas_DEFINED
+
+#include "src/gpu/GrAllocator.h"
+#include "src/gpu/GrTextureProxy.h"
+
+class GrOnFlushResourceProvider;
+class GrRenderTargetContext;
+class GrResourceProvider;
+struct SkIPoint16;
+struct SkIRect;
+
+/**
+ * This class implements a dynamic size GrRectanizer that grows until it reaches the implementation-
+ * dependent max texture size. When finalized, it also creates and stores a GrTextureProxy for the
+ * underlying atlas.
+ */
+class GrDynamicAtlas {
+public:
+    // 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.
+
+    using LazyInstantiateAtlasCallback = std::function<GrSurfaceProxy::LazyCallbackResult(
+            GrResourceProvider*, const GrBackendFormat&, int sampleCount)>;
+
+    enum class InternalMultisample : bool {
+        kNo = false,
+        kYes = true
+    };
+
+    static sk_sp<GrTextureProxy> MakeLazyAtlasProxy(const LazyInstantiateAtlasCallback&,
+                                                    GrColorType colorType, InternalMultisample,
+                                                    const GrCaps&, GrSurfaceProxy::UseAllocator);
+
+    GrDynamicAtlas(GrColorType colorType, InternalMultisample, SkISize initialSize,
+                   int maxAtlasSize, const GrCaps&);
+    virtual ~GrDynamicAtlas();
+
+    void reset(SkISize initialSize, const GrCaps& caps);
+
+    GrTextureProxy* textureProxy() const { return fTextureProxy.get(); }
+    bool isInstantiated() const { return fTextureProxy->isInstantiated(); }
+    int currentWidth() const { return fWidth; }
+    int currentHeight() const { return fHeight; }
+
+    // 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; }
+
+    // Instantiates 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(), setUserBatchID(), or this method again.
+    //
+    // 'backingTexture', if provided, is a renderable texture with which to instantiate our proxy.
+    // If null then we will create a texture using the resource provider. The purpose of this param
+    // is to provide a guaranteed way to recycle a stashed atlas texture from a previous flush.
+    std::unique_ptr<GrRenderTargetContext> instantiate(
+            GrOnFlushResourceProvider*, sk_sp<GrTexture> backingTexture = nullptr);
+
+private:
+    class Node;
+
+    bool internalPlaceRect(int w, int h, SkIPoint16* loc);
+
+    const GrColorType fColorType;
+    const InternalMultisample fInternalMultisample;
+    const int fMaxAtlasSize;
+    int fWidth;
+    int fHeight;
+    std::unique_ptr<Node> fTopNode;
+    SkISize fDrawBounds;
+
+    sk_sp<GrTextureProxy> fTextureProxy;
+    sk_sp<GrTexture> fBackingTexture;
+};
+
+#endif
diff --git a/src/gpu/GrRectanizerSkyline.h b/src/gpu/GrRectanizerSkyline.h
index 638d893..36af34d 100644
--- a/src/gpu/GrRectanizerSkyline.h
+++ b/src/gpu/GrRectanizerSkyline.h
@@ -49,10 +49,10 @@
     // at x,y.
     void addSkylineLevel(int skylineIndex, int x, int y, int width, int height);
 
+    const int fWidth;
+    const int fHeight;
     SkTDArray<SkylineSegment> fSkyline;
     int32_t fAreaSoFar;
-    int fWidth;
-    int fHeight;
 };
 
 #endif  // GrRectanizerSkyline_DEFINED
diff --git a/src/gpu/ccpr/GrCCAtlas.cpp b/src/gpu/ccpr/GrCCAtlas.cpp
index 82f89a6..a980f68 100644
--- a/src/gpu/ccpr/GrCCAtlas.cpp
+++ b/src/gpu/ccpr/GrCCAtlas.cpp
@@ -7,181 +7,53 @@
 
 #include "src/gpu/ccpr/GrCCAtlas.h"
 
-#include "include/gpu/GrTexture.h"
-#include "src/core/SkIPoint16.h"
-#include "src/core/SkMathPriv.h"
-#include "src/gpu/GrCaps.h"
 #include "src/gpu/GrOnFlushResourceProvider.h"
-#include "src/gpu/GrProxyProvider.h"
-#include "src/gpu/GrRectanizerSkyline.h"
-#include "src/gpu/GrRenderTarget.h"
-#include "src/gpu/GrRenderTargetContext.h"
-#include "src/gpu/GrTextureProxy.h"
 #include "src/gpu/ccpr/GrCCPathCache.h"
-#include <atomic>
 
-class GrCCAtlas::Node {
-public:
-    Node(std::unique_ptr<Node> previous, int l, int t, int r, int b)
-            : fPrevious(std::move(previous)), fX(l), fY(t), fRectanizer(r - l, b - t) {}
-
-    Node* previous() const { return fPrevious.get(); }
-
-    bool addRect(int w, int h, SkIPoint16* loc, int maxAtlasSize) {
-        // Pad all paths except those that are expected to take up an entire physical texture.
-        if (w < maxAtlasSize) {
-            w = std::min(w + kPadding, maxAtlasSize);
-        }
-        if (h < maxAtlasSize) {
-            h = std::min(h + kPadding, maxAtlasSize);
-        }
-        if (!fRectanizer.addRect(w, h, loc)) {
-            return false;
-        }
-        loc->fX += fX;
-        loc->fY += fY;
-        return true;
-    }
-
-private:
-    const std::unique_ptr<Node> fPrevious;
-    const int fX, fY;
-    GrRectanizerSkyline fRectanizer;
-};
-
-sk_sp<GrTextureProxy> GrCCAtlas::MakeLazyAtlasProxy(const LazyInstantiateAtlasCallback& callback,
-                                                    CoverageType coverageType,
-                                                    const GrCaps& caps,
-                                                    GrSurfaceProxy::UseAllocator useAllocator) {
-    int sampleCount;
-
-    auto colorType = CoverageTypeToColorType(coverageType);
-    GrBackendFormat format = caps.getDefaultBackendFormat(colorType, GrRenderable::kYes);
-    switch (coverageType) {
-        case CoverageType::kFP16_CoverageCount:
-            sampleCount = 1;
-            break;
-        case CoverageType::kA8_Multisample:
-            SkASSERT(caps.internalMultisampleCount(format) > 1);
-            sampleCount = (caps.mixedSamplesSupport()) ? 1 : caps.internalMultisampleCount(format);
-            break;
-        case CoverageType::kA8_LiteralCoverage:
-            sampleCount = 1;
-            break;
-    }
-
-    auto instantiate = [cb = std::move(callback), format, sampleCount](GrResourceProvider* rp) {
-        return cb(rp, format, sampleCount);
-    };
-
-    GrSwizzle readSwizzle = caps.getReadSwizzle(format, colorType);
-
-    sk_sp<GrTextureProxy> proxy = GrProxyProvider::MakeFullyLazyProxy(
-            std::move(instantiate), format, readSwizzle, GrRenderable::kYes, sampleCount,
-            GrProtected::kNo, kTextureOrigin, caps, useAllocator);
-
-    return proxy;
-}
-
-GrCCAtlas::GrCCAtlas(CoverageType coverageType, const Specs& specs, const GrCaps& caps)
-        : fCoverageType(coverageType)
-        , fMaxTextureSize(std::max(std::max(specs.fMinHeight, specs.fMinWidth),
-                                 specs.fMaxPreferredTextureSize)) {
-    // Caller should have cropped any paths to the destination render target instead of asking for
-    // an atlas larger than maxRenderTargetSize.
-    SkASSERT(fMaxTextureSize <= caps.maxTextureSize());
-    SkASSERT(specs.fMaxPreferredTextureSize > 0);
-
+static SkISize choose_initial_atlas_size(const GrCCAtlas::Specs& specs) {
     // 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(std::max(specs.fApproxNumPixels, 1));
-    fHeight = 1 << ((log2area + 1) / 2);
-    fWidth = 1 << (log2area / 2);
+    int height = 1 << ((log2area + 1) / 2);
+    int width = 1 << (log2area / 2);
 
-    fWidth = SkTPin(fWidth, specs.fMinTextureSize, specs.fMaxPreferredTextureSize);
-    fHeight = SkTPin(fHeight, specs.fMinTextureSize, specs.fMaxPreferredTextureSize);
+    width = SkTPin(width, specs.fMinTextureSize, specs.fMaxPreferredTextureSize);
+    height = SkTPin(height, 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 = std::min(specs.fMinWidth + kPadding, fMaxTextureSize);
-        fHeight = std::min(specs.fMinHeight + kPadding, fMaxTextureSize);
-    }
+    return SkISize::Make(width, height);
+}
 
-    fTopNode = std::make_unique<Node>(nullptr, 0, 0, fWidth, fHeight);
+static int choose_max_atlas_size(const GrCCAtlas::Specs& specs, const GrCaps& caps) {
+    return (std::max(specs.fMinHeight, specs.fMinWidth) <= specs.fMaxPreferredTextureSize) ?
+            specs.fMaxPreferredTextureSize : caps.maxRenderTargetSize();
+}
 
-    fTextureProxy = MakeLazyAtlasProxy(
-            [this](GrResourceProvider* resourceProvider,const GrBackendFormat& format,
-                   int sampleCount) {
-                if (!fBackingTexture) {
-                    fBackingTexture = resourceProvider->createTexture(
-                            {fWidth, fHeight}, format, GrRenderable::kYes, sampleCount,
-                            GrMipMapped::kNo, SkBudgeted::kYes, GrProtected::kNo);
-                }
-                return GrSurfaceProxy::LazyCallbackResult(fBackingTexture);
-            },
-            fCoverageType, caps, GrSurfaceProxy::UseAllocator::kNo);
+GrCCAtlas::GrCCAtlas(CoverageType coverageType, const Specs& specs, const GrCaps& caps)
+        : GrDynamicAtlas(CoverageTypeToColorType(coverageType),
+                         CoverageTypeHasInternalMultisample(coverageType),
+                         choose_initial_atlas_size(specs), choose_max_atlas_size(specs, caps), caps)
+        , fCoverageType(coverageType) {
+    SkASSERT(specs.fMaxPreferredTextureSize > 0);
 }
 
 GrCCAtlas::~GrCCAtlas() {
 }
 
-bool GrCCAtlas::addRect(const SkIRect& devIBounds, SkIVector* offset) {
-    // This can't be called anymore once makeRenderTargetContext() has been called.
-    SkASSERT(!fTextureProxy->isInstantiated());
-
-    SkIPoint16 location;
-    if (!this->internalPlaceRect(devIBounds.width(), devIBounds.height(), &location)) {
-        return false;
-    }
-    offset->set(location.x() - devIBounds.left(), location.y() - devIBounds.top());
-
-    fDrawBounds.fWidth = std::max(fDrawBounds.width(), location.x() + devIBounds.width());
-    fDrawBounds.fHeight = std::max(fDrawBounds.height(), location.y() + devIBounds.height());
-    return true;
-}
-
-bool GrCCAtlas::internalPlaceRect(int w, int h, SkIPoint16* loc) {
-    for (Node* node = fTopNode.get(); node; node = node->previous()) {
-        if (node->addRect(w, h, loc, fMaxTextureSize)) {
-            return true;
-        }
-    }
-
-    // The rect didn't fit. Grow the atlas and try again.
-    do {
-        if (fWidth == fMaxTextureSize && fHeight == fMaxTextureSize) {
-            return false;
-        }
-        if (fHeight <= fWidth) {
-            int top = fHeight;
-            fHeight = std::min(fHeight * 2, fMaxTextureSize);
-            fTopNode = std::make_unique<Node>(std::move(fTopNode), 0, top, fWidth, fHeight);
-        } else {
-            int left = fWidth;
-            fWidth = std::min(fWidth * 2, fMaxTextureSize);
-            fTopNode = std::make_unique<Node>(std::move(fTopNode), left, 0, fWidth, fHeight);
-        }
-    } while (!fTopNode->addRect(w, h, loc, fMaxTextureSize));
-
-    return true;
-}
-
 void GrCCAtlas::setFillBatchID(int id) {
     // This can't be called anymore once makeRenderTargetContext() has been called.
-    SkASSERT(!fTextureProxy->isInstantiated());
+    SkASSERT(!this->isInstantiated());
     fFillBatchID = id;
 }
 
 void GrCCAtlas::setStrokeBatchID(int id) {
     // This can't be called anymore once makeRenderTargetContext() has been called.
-    SkASSERT(!fTextureProxy->isInstantiated());
+    SkASSERT(!this->isInstantiated());
     fStrokeBatchID = id;
 }
 
 void GrCCAtlas::setEndStencilResolveInstance(int idx) {
     // This can't be called anymore once makeRenderTargetContext() has been called.
-    SkASSERT(!fTextureProxy->isInstantiated());
+    SkASSERT(!this->isInstantiated());
     fEndStencilResolveInstance = idx;
 }
 
@@ -199,59 +71,17 @@
         builder[0] = next_atlas_unique_id();
         builder.finish();
 
-        onFlushRP->assignUniqueKeyToProxy(atlasUniqueKey, fTextureProxy.get());
+        onFlushRP->assignUniqueKeyToProxy(atlasUniqueKey, this->textureProxy());
 
-        fCachedAtlas = sk_make_sp<GrCCCachedAtlas>(fCoverageType, atlasUniqueKey, fTextureProxy);
+        fCachedAtlas = sk_make_sp<GrCCCachedAtlas>(fCoverageType, atlasUniqueKey,
+                                                   sk_ref_sp(this->textureProxy()));
     }
 
     SkASSERT(fCachedAtlas->coverageType() == fCoverageType);
-    SkASSERT(fCachedAtlas->getOnFlushProxy() == fTextureProxy.get());
+    SkASSERT(fCachedAtlas->getOnFlushProxy() == this->textureProxy());
     return fCachedAtlas;
 }
 
-std::unique_ptr<GrRenderTargetContext> GrCCAtlas::makeRenderTargetContext(
-        GrOnFlushResourceProvider* onFlushRP, sk_sp<GrTexture> backingTexture) {
-    SkASSERT(!fTextureProxy->isInstantiated());  // This method should only be called once.
-    // Caller should have cropped any paths to the destination render target instead of asking for
-    // an atlas larger than maxRenderTargetSize.
-    SkASSERT(std::max(fHeight, fWidth) <= fMaxTextureSize);
-    SkASSERT(fMaxTextureSize <= onFlushRP->caps()->maxRenderTargetSize());
-
-    // Finalize the content size of our proxy. The GPU can potentially make optimizations if it
-    // knows we only intend to write out a smaller sub-rectangle of the backing texture.
-    fTextureProxy->priv().setLazyDimensions(fDrawBounds);
-
-    if (backingTexture) {
-#ifdef SK_DEBUG
-        auto backingRT = backingTexture->asRenderTarget();
-        SkASSERT(backingRT);
-        SkASSERT(backingRT->backendFormat() == fTextureProxy->backendFormat());
-        SkASSERT(backingRT->numSamples() == fTextureProxy->asRenderTargetProxy()->numSamples());
-        SkASSERT(backingRT->width() == fWidth);
-        SkASSERT(backingRT->height() == fHeight);
-#endif
-        fBackingTexture = std::move(backingTexture);
-    }
-    auto colorType = (CoverageType::kFP16_CoverageCount == fCoverageType)
-            ? GrColorType::kAlpha_F16 : GrColorType::kAlpha_8;
-    auto rtc = onFlushRP->makeRenderTargetContext(fTextureProxy, colorType, nullptr, nullptr);
-    if (!rtc) {
-#if GR_TEST_UTILS
-        if (!onFlushRP->testingOnly_getSuppressAllocationWarnings())
-#endif
-        {
-            SkDebugf("WARNING: failed to allocate a %ix%i atlas. Some paths will not be drawn.\n",
-                     fWidth, fHeight);
-        }
-        return nullptr;
-    }
-
-    SkIRect clearRect = SkIRect::MakeSize(fDrawBounds);
-    rtc->clear(&clearRect, SK_PMColor4fTRANSPARENT,
-               GrRenderTargetContext::CanClearFullscreen::kYes);
-    return rtc;
-}
-
 GrCCAtlas* GrCCAtlasStack::addRect(const SkIRect& devIBounds, SkIVector* devToAtlasOffset) {
     GrCCAtlas* retiredAtlas = nullptr;
     if (fAtlases.empty() || !fAtlases.back().addRect(devIBounds, devToAtlasOffset)) {
diff --git a/src/gpu/ccpr/GrCCAtlas.h b/src/gpu/ccpr/GrCCAtlas.h
index 06909ae..9a1b9f5 100644
--- a/src/gpu/ccpr/GrCCAtlas.h
+++ b/src/gpu/ccpr/GrCCAtlas.h
@@ -8,33 +8,17 @@
 #ifndef GrCCAtlas_DEFINED
 #define GrCCAtlas_DEFINED
 
-#include "include/core/SkRefCnt.h"
-#include "include/core/SkSize.h"
-#include "include/gpu/GrTexture.h"
-#include "include/private/GrResourceKey.h"
-#include "src/gpu/GrAllocator.h"
-#include "src/gpu/GrNonAtomicRef.h"
-#include "src/gpu/GrSurfaceProxy.h"
+#include "src/gpu/GrDynamicAtlas.h"
+
+#include "src/gpu/ccpr/GrCCPathProcessor.h"
 
 class GrCCCachedAtlas;
-class GrOnFlushResourceProvider;
-class GrRenderTargetContext;
-class GrResourceProvider;
-class GrTextureProxy;
-struct SkIPoint16;
-struct SkIRect;
 
 /**
- * This class implements a dynamic size GrRectanizer that grows until it reaches the implementation-
- * dependent max texture size. When finalized, it also creates and stores a GrTextureProxy for the
- * underlying atlas.
+ * GrDynamicAtlas with CCPR caching capabilities.
  */
-class GrCCAtlas {
+class GrCCAtlas : public GrDynamicAtlas {
 public:
-    // 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
     // approximate number of pixels to help select a good initial size.
     struct Specs {
@@ -65,25 +49,36 @@
         SkUNREACHABLE;
     }
 
-    using LazyInstantiateAtlasCallback = std::function<GrSurfaceProxy::LazyCallbackResult(
-            GrResourceProvider*, const GrBackendFormat&, int sampleCount)>;
+    static constexpr InternalMultisample CoverageTypeHasInternalMultisample(
+            CoverageType coverageType) {
+        switch (coverageType) {
+            case CoverageType::kFP16_CoverageCount:
+            case CoverageType::kA8_LiteralCoverage:
+                return InternalMultisample::kNo;
+            case CoverageType::kA8_Multisample:
+                return InternalMultisample::kYes;
+        }
+        SkUNREACHABLE;
+    }
 
-    static sk_sp<GrTextureProxy> MakeLazyAtlasProxy(const LazyInstantiateAtlasCallback&,
-                                                    CoverageType,
-                                                    const GrCaps&,
-                                                    GrSurfaceProxy::UseAllocator);
+    static constexpr GrCCPathProcessor::CoverageMode CoverageTypeToPathCoverageMode(
+            CoverageType coverageType) {
+        return (GrCCAtlas::CoverageType::kFP16_CoverageCount == coverageType)
+                ? GrCCPathProcessor::CoverageMode::kCoverageCount
+                : GrCCPathProcessor::CoverageMode::kLiteral;
+    }
+
+
+    static sk_sp<GrTextureProxy> MakeLazyAtlasProxy(const LazyInstantiateAtlasCallback& callback,
+                                                    CoverageType coverageType, const GrCaps& caps,
+                                                    GrSurfaceProxy::UseAllocator useAllocator) {
+        return GrDynamicAtlas::MakeLazyAtlasProxy(callback, CoverageTypeToColorType(coverageType),
+                                                  CoverageTypeHasInternalMultisample(coverageType),
+                                                  caps, useAllocator);
+    }
 
     GrCCAtlas(CoverageType, const Specs&, const GrCaps&);
-    ~GrCCAtlas();
-
-    GrTextureProxy* textureProxy() const { return fTextureProxy.get(); }
-    int currentWidth() const { return fWidth; }
-    int currentHeight() const { return fHeight; }
-
-    // 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; }
+    ~GrCCAtlas() override;
 
     // This is an optional space for the caller to jot down user-defined instance data to use when
     // rendering atlas content.
@@ -96,34 +91,12 @@
 
     sk_sp<GrCCCachedAtlas> refOrMakeCachedAtlas(GrOnFlushResourceProvider*);
 
-    // Instantiates 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(), setUserBatchID(), or this method again.
-    //
-    // 'backingTexture', if provided, is a renderable texture with which to instantiate our proxy.
-    // If null then we will create a texture using the resource provider. The purpose of this param
-    // is to provide a guaranteed way to recycle a stashed atlas texture from a previous flush.
-    std::unique_ptr<GrRenderTargetContext> makeRenderTargetContext(
-            GrOnFlushResourceProvider*, sk_sp<GrTexture> backingTexture = nullptr);
-
 private:
-    class Node;
-
-    bool internalPlaceRect(int w, int h, SkIPoint16* loc);
-
     const CoverageType fCoverageType;
-    const int fMaxTextureSize;
-    int fWidth, fHeight;
-    std::unique_ptr<Node> fTopNode;
-    SkISize fDrawBounds = {0, 0};
-
     int fFillBatchID;
     int fStrokeBatchID;
     int fEndStencilResolveInstance;
-
     sk_sp<GrCCCachedAtlas> fCachedAtlas;
-    sk_sp<GrTextureProxy> fTextureProxy;
-    sk_sp<GrTexture> fBackingTexture;
 };
 
 /**
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.cpp b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
index 4dbe087..3ce17ae 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.cpp
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
@@ -358,7 +358,7 @@
                               == fCacheEntry->cachedAtlas()->coverageType())
                     ? SkPMColor4f{0,0,.25,.25} : SkPMColor4f{0,.25,0,.25};
 #endif
-            auto coverageMode = GrCCPathProcessor::GetCoverageMode(
+            auto coverageMode = GrCCAtlas::CoverageTypeToPathCoverageMode(
                     fCacheEntry->cachedAtlas()->coverageType());
             op->recordInstance(coverageMode, fCacheEntry->cachedAtlas()->getOnFlushProxy(),
                                resources->nextPathInstanceIdx());
@@ -386,7 +386,7 @@
     if (auto atlas = resources->renderShapeInAtlas(
                 fMaskDevIBounds, fMatrix, fShape, fStrokeDevWidth, &octoBounds, &devIBounds,
                 &devToAtlasOffset)) {
-        auto coverageMode = GrCCPathProcessor::GetCoverageMode(
+        auto coverageMode = GrCCAtlas::CoverageTypeToPathCoverageMode(
                 resources->renderedPathCoverageType());
         op->recordInstance(coverageMode, atlas->textureProxy(), resources->nextPathInstanceIdx());
         resources->appendDrawPathInstance().set(
diff --git a/src/gpu/ccpr/GrCCPathProcessor.h b/src/gpu/ccpr/GrCCPathProcessor.h
index 3f13647..5273607 100644
--- a/src/gpu/ccpr/GrCCPathProcessor.h
+++ b/src/gpu/ccpr/GrCCPathProcessor.h
@@ -13,7 +13,6 @@
 #include "src/gpu/GrCaps.h"
 #include "src/gpu/GrGeometryProcessor.h"
 #include "src/gpu/GrPipeline.h"
-#include "src/gpu/ccpr/GrCCAtlas.h"
 #include "src/gpu/ccpr/GrOctoBounds.h"
 
 class GrCCPathCacheEntry;
@@ -54,16 +53,9 @@
         kLiteral
     };
 
-    static CoverageMode GetCoverageMode(GrCCAtlas::CoverageType coverageType) {
-        return (GrCCAtlas::CoverageType::kFP16_CoverageCount == coverageType)
-                ? CoverageMode::kCoverageCount
-                : CoverageMode::kLiteral;
-    }
-
-    GrCCPathProcessor(
-            CoverageMode, const GrTexture* atlasTexture, const GrSwizzle&,
-            GrSurfaceOrigin atlasOrigin,
-            const SkMatrix& viewMatrixIfUsingLocalCoords = SkMatrix::I());
+    GrCCPathProcessor(CoverageMode, const GrTexture* atlasTexture, const GrSwizzle&,
+                      GrSurfaceOrigin atlasOrigin,
+                      const SkMatrix& viewMatrixIfUsingLocalCoords = SkMatrix::I());
 
     const char* name() const override { return "GrCCPathProcessor"; }
     void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.cpp b/src/gpu/ccpr/GrCCPerFlushResources.cpp
index bc3e8ac..a7ebf66 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.cpp
+++ b/src/gpu/ccpr/GrCCPerFlushResources.cpp
@@ -85,7 +85,7 @@
         GrSurfaceProxy* srcProxy = fSrcProxy.get();
         SkASSERT(srcProxy->isInstantiated());
 
-        auto coverageMode = GrCCPathProcessor::GetCoverageMode(
+        auto coverageMode = GrCCAtlas::CoverageTypeToPathCoverageMode(
                 fResources->renderedPathCoverageType());
         GrCCPathProcessor pathProc(coverageMode, srcProxy->peekTexture(),
                                    srcProxy->textureSwizzle(), srcProxy->origin());
@@ -533,7 +533,7 @@
         int endCopyRange = atlas->getFillBatchID();
         SkASSERT(endCopyRange > copyRangeIdx);
 
-        auto rtc = atlas->makeRenderTargetContext(onFlushRP);
+        auto rtc = atlas->instantiate(onFlushRP);
         for (; copyRangeIdx < endCopyRange; ++copyRangeIdx) {
             const CopyPathRange& copyRange = fCopyPathRanges[copyRangeIdx];
             int endCopyInstance = baseCopyInstance + copyRange.fCount;
@@ -564,7 +564,7 @@
             }
         }
 
-        if (auto rtc = atlas->makeRenderTargetContext(onFlushRP, std::move(backingTexture))) {
+        if (auto rtc = atlas->instantiate(onFlushRP, std::move(backingTexture))) {
             std::unique_ptr<GrDrawOp> op;
             if (CoverageType::kA8_Multisample == fRenderedAtlasStack.coverageType()) {
                 op = GrStencilAtlasOp::Make(