Add a simple atlas to the GPU tessellation path renderer

If a path is small and simple enough, we now try to atlas it. There
is only one atlas and it caps at 2048x2048. Once it runs out of room,
everything just draws direct. The atlas is rendered using the existing
GrTessellatePathOp. It provides alpha8 coverage even for msaa render
targets.

Change-Id: I715da9ce7347b6f1ef8e28b3e13ab47f6eade1c7
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/268724
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 6717e5c..ecc0228 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -420,6 +420,8 @@
   "$_src/gpu/gradients/generated/GrTiledGradientEffect.cpp",
   "$_src/gpu/gradients/generated/GrTiledGradientEffect.h",
 
+  "$_src/gpu/tessellate/GrDrawAtlasPathOp.cpp",
+  "$_src/gpu/tessellate/GrDrawAtlasPathOp.h",
   "$_src/gpu/tessellate/GrFillPathShader.cpp",
   "$_src/gpu/tessellate/GrFillPathShader.h",
   "$_src/gpu/tessellate/GrGpuTessellationPathRenderer.cpp",
diff --git a/src/gpu/GrOnFlushResourceProvider.cpp b/src/gpu/GrOnFlushResourceProvider.cpp
index 8c96d27..03d7334 100644
--- a/src/gpu/GrOnFlushResourceProvider.cpp
+++ b/src/gpu/GrOnFlushResourceProvider.cpp
@@ -142,6 +142,10 @@
     return fDrawingMgr->getContext()->priv().caps();
 }
 
+GrOpMemoryPool* GrOnFlushResourceProvider::opMemoryPool() const {
+    return fDrawingMgr->getContext()->priv().opMemoryPool();
+}
+
 #if GR_TEST_UTILS
 bool GrOnFlushResourceProvider::testingOnly_getSuppressAllocationWarnings() const {
     return fDrawingMgr->getContext()->testingOnly_getSuppressAllocationWarnings();
diff --git a/src/gpu/GrOnFlushResourceProvider.h b/src/gpu/GrOnFlushResourceProvider.h
index ab70149..894a21e 100644
--- a/src/gpu/GrOnFlushResourceProvider.h
+++ b/src/gpu/GrOnFlushResourceProvider.h
@@ -91,6 +91,7 @@
 
     uint32_t contextID() const;
     const GrCaps* caps() const;
+    GrOpMemoryPool* opMemoryPool() const;
 
 #if GR_TEST_UTILS
     bool testingOnly_getSuppressAllocationWarnings() const;
diff --git a/src/gpu/GrPathRendererChain.cpp b/src/gpu/GrPathRendererChain.cpp
index 34d6931..54fc45a 100644
--- a/src/gpu/GrPathRendererChain.cpp
+++ b/src/gpu/GrPathRendererChain.cpp
@@ -33,7 +33,9 @@
     }
     if (options.fGpuPathRenderers & GpuPathRenderers::kGpuTessellation) {
         if (caps.shaderCaps()->tessellationSupport()) {
-            fChain.push_back(sk_make_sp<GrGpuTessellationPathRenderer>());
+            auto gtess = sk_make_sp<GrGpuTessellationPathRenderer>(caps);
+            context->priv().addOnFlushCallbackObject(gtess.get());
+            fChain.push_back(std::move(gtess));
         }
     }
     if (options.fGpuPathRenderers & GpuPathRenderers::kAAConvex) {
diff --git a/src/gpu/GrProcessor.h b/src/gpu/GrProcessor.h
index 384b8e1..3274a14 100644
--- a/src/gpu/GrProcessor.h
+++ b/src/gpu/GrProcessor.h
@@ -77,6 +77,7 @@
         kDefaultGeoProc_ClassID,
         kDIEllipseGeometryProcessor_ClassID,
         kDisableColorXP_ClassID,
+        kDrawAtlasPathShader_ClassID,
         kEllipseGeometryProcessor_ClassID,
         kEllipticalRRectEffect_ClassID,
         kGP_ClassID,
diff --git a/src/gpu/tessellate/GrDrawAtlasPathOp.cpp b/src/gpu/tessellate/GrDrawAtlasPathOp.cpp
new file mode 100644
index 0000000..1062263
--- /dev/null
+++ b/src/gpu/tessellate/GrDrawAtlasPathOp.cpp
@@ -0,0 +1,190 @@
+/*
+ * 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/tessellate/GrDrawAtlasPathOp.h"
+
+#include "src/gpu/GrOpFlushState.h"
+#include "src/gpu/GrOpsRenderPass.h"
+#include "src/gpu/GrProgramInfo.h"
+#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
+#include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
+#include "src/gpu/glsl/GrGLSLVarying.h"
+#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
+
+namespace {
+
+constexpr static GrGeometryProcessor::Attribute kInstanceAttribs[] = {
+        {"devibounds", kInt4_GrVertexAttribType, kInt4_GrSLType},
+        {"dev_to_atlas_offset", kInt2_GrVertexAttribType, kInt2_GrSLType},
+        {"color", kFloat4_GrVertexAttribType, kHalf4_GrSLType},
+        {"viewmatrix_scaleskew", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
+        {"viewmatrix_trans", kFloat2_GrVertexAttribType, kFloat2_GrSLType}};
+
+class DrawAtlasPathShader : public GrGeometryProcessor {
+public:
+    DrawAtlasPathShader(const GrTextureProxy* atlasProxy, GrSwizzle swizzle, bool usesLocalCoords)
+            : GrGeometryProcessor(kDrawAtlasPathShader_ClassID)
+            , fAtlasAccess(GrSamplerState::Filter::kNearest, atlasProxy->backendFormat(), swizzle)
+            , fAtlasDimensions(atlasProxy->backingStoreDimensions())
+            , fUsesLocalCoords(usesLocalCoords) {
+        int numInstanceAttribs = SK_ARRAY_COUNT(kInstanceAttribs);
+        if (!fUsesLocalCoords) {
+            numInstanceAttribs -= 2;
+        }
+        this->setInstanceAttributes(kInstanceAttribs, numInstanceAttribs);
+        this->setTextureSamplerCnt(1);
+    }
+
+private:
+    const char* name() const override { return "DrawAtlasPathShader"; }
+    void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
+        b->add32(fUsesLocalCoords);
+    }
+    const TextureSampler& onTextureSampler(int) const override { return fAtlasAccess; }
+    GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override;
+
+    const TextureSampler fAtlasAccess;
+    const SkISize fAtlasDimensions;
+    const bool fUsesLocalCoords;
+
+    class Impl;
+};
+
+class DrawAtlasPathShader::Impl : public GrGLSLGeometryProcessor {
+    void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
+        const auto& shader = args.fGP.cast<DrawAtlasPathShader>();
+        args.fVaryingHandler->emitAttributes(shader);
+
+        GrGLSLVarying atlasCoord(kFloat2_GrSLType);
+        args.fVaryingHandler->addVarying("atlascoord", &atlasCoord);
+
+        GrGLSLVarying color(kHalf4_GrSLType);
+        args.fVaryingHandler->addPassThroughAttribute(
+                kInstanceAttribs[2], args.fOutputColor,
+                GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
+
+        const char* atlasAdjust;
+        fAtlasAdjustUniform = args.fUniformHandler->addUniform(
+                kVertex_GrShaderFlag, kFloat2_GrSLType, "atlas_adjust", &atlasAdjust);
+
+        args.fVertBuilder->codeAppendf(R"(
+                float2 T = float2(sk_VertexID & 1, sk_VertexID >> 1);
+                float2 devcoord = mix(float2(devibounds.xy), float2(devibounds.zw), T);
+                float2 atlascoord = devcoord + float2(dev_to_atlas_offset);
+                %s = atlascoord * %s;)",
+                atlasCoord.vsOut(), atlasAdjust);
+
+        gpArgs->fPositionVar.set(kFloat2_GrSLType, "devcoord");
+
+        GrShaderVar localCoord = gpArgs->fPositionVar;
+        if (shader.fUsesLocalCoords) {
+            args.fVertBuilder->codeAppendf(R"(
+                    float2x2 M = float2x2(viewmatrix_scaleskew);
+                    float2 localcoord = inverse(M) * (devcoord - viewmatrix_trans);)");
+            localCoord.set(kFloat2_GrSLType, "localcoord");
+        }
+        this->emitTransforms(args.fVertBuilder, args.fVaryingHandler, args.fUniformHandler,
+                             localCoord, args.fFPCoordTransformHandler);
+
+        args.fFragBuilder->codeAppendf("%s = ", args.fOutputCoverage);
+        args.fFragBuilder->appendTextureLookup(args.fTexSamplers[0], atlasCoord.fsIn());
+        args.fFragBuilder->codeAppendf(".aaaa;");
+    }
+
+    void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
+                 const CoordTransformRange& transformRange) override {
+        const SkISize& dimensions = primProc.cast<DrawAtlasPathShader>().fAtlasDimensions;
+        pdman.set2f(fAtlasAdjustUniform, 1.f / dimensions.width(), 1.f / dimensions.height());
+        this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
+    }
+
+    GrGLSLUniformHandler::UniformHandle fAtlasAdjustUniform;
+};
+
+GrGLSLPrimitiveProcessor* DrawAtlasPathShader::createGLSLInstance(const GrShaderCaps&) const {
+    return new Impl();
+}
+
+}  // namespace
+
+GrProcessorSet::Analysis GrDrawAtlasPathOp::finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                                     bool hasMixedSampledCoverage,
+                                                     GrClampType clampType) {
+    const GrProcessorSet::Analysis& analysis = fProcessors.finalize(
+            fInstanceList.fInstance.fColor, GrProcessorAnalysisCoverage::kSingleChannel, clip,
+            &GrUserStencilSettings::kUnused, hasMixedSampledCoverage, caps, clampType,
+            &fInstanceList.fInstance.fColor);
+    fUsesLocalCoords = analysis.usesLocalCoords();
+    return analysis;
+}
+
+GrOp::CombineResult GrDrawAtlasPathOp::onCombineIfPossible(
+        GrOp* op, GrRecordingContext::Arenas* arenas, const GrCaps&) {
+    auto* that = op->cast<GrDrawAtlasPathOp>();
+    SkASSERT(fAtlasProxy == that->fAtlasProxy);
+    SkASSERT(fEnableHWAA == that->fEnableHWAA);
+
+    if (fProcessors != that->fProcessors) {
+        return CombineResult::kCannotCombine;
+    }
+
+    SkASSERT(fUsesLocalCoords == that->fUsesLocalCoords);
+    auto* copy = arenas->recordTimeAllocator()->make<InstanceList>(that->fInstanceList);
+    *fInstanceTail = copy;
+    fInstanceTail = (!copy->fNext) ? &copy->fNext : that->fInstanceTail;
+    fInstanceCount += that->fInstanceCount;
+    return CombineResult::kMerged;
+}
+
+void GrDrawAtlasPathOp::onPrepare(GrOpFlushState* state) {
+    size_t instanceStride = Instance::Stride(fUsesLocalCoords);
+    if (char* instanceData = (char*)state->makeVertexSpace(
+            instanceStride, fInstanceCount, &fInstanceBuffer, &fBaseInstance)) {
+        SkDEBUGCODE(char* end = instanceData + fInstanceCount * instanceStride);
+        for (const InstanceList* list = &fInstanceList; list; list = list->fNext) {
+            memcpy(instanceData, &list->fInstance, instanceStride);
+            instanceData += instanceStride;
+        }
+        SkASSERT(instanceData == end);
+    }
+}
+
+void GrDrawAtlasPathOp::onExecute(GrOpFlushState* state, const SkRect& chainBounds) {
+    SkASSERT(fAtlasProxy->isInstantiated());
+
+    GrPipeline::InitArgs initArgs;
+    if (fEnableHWAA) {
+        initArgs.fInputFlags |= GrPipeline::InputFlags::kHWAntialias;
+    }
+    initArgs.fCaps = &state->caps();
+    initArgs.fDstProxyView = state->drawOpArgs().dstProxyView();
+    initArgs.fOutputSwizzle = state->drawOpArgs().outputSwizzle();
+
+    GrAppliedClip clip = state->detachAppliedClip();
+    GrPipeline::FixedDynamicState fixedDynamicState;
+    if (clip.scissorState().enabled()) {
+        fixedDynamicState.fScissorRect = clip.scissorState().rect();
+    }
+    GrSurfaceProxy* atlasSurfaceProxy = fAtlasProxy.get();
+    fixedDynamicState.fPrimitiveProcessorTextures = &atlasSurfaceProxy;
+
+    GrPipeline pipeline(initArgs, std::move(fProcessors), std::move(clip));
+
+    GrSwizzle swizzle = state->caps().getReadSwizzle(fAtlasProxy->backendFormat(),
+                                                     GrColorType::kAlpha_8);
+    DrawAtlasPathShader shader(fAtlasProxy.get(), swizzle, fUsesLocalCoords);
+    SkASSERT(shader.instanceStride() == Instance::Stride(fUsesLocalCoords));
+
+    GrProgramInfo programInfo(state->proxy()->numSamples(), state->proxy()->numStencilSamples(),
+                              state->proxy()->backendFormat(), state->proxy()->origin(), &pipeline,
+                              &shader, &fixedDynamicState, nullptr, 0,
+                              GrPrimitiveType::kTriangleStrip);
+
+    GrMesh mesh(GrPrimitiveType::kTriangleStrip);
+    mesh.setInstanced(fInstanceBuffer, fInstanceCount, fBaseInstance, 4);
+    state->opsRenderPass()->draw(programInfo, &mesh, 1, this->bounds());
+}
diff --git a/src/gpu/tessellate/GrDrawAtlasPathOp.h b/src/gpu/tessellate/GrDrawAtlasPathOp.h
new file mode 100644
index 0000000..ab4e7cb
--- /dev/null
+++ b/src/gpu/tessellate/GrDrawAtlasPathOp.h
@@ -0,0 +1,90 @@
+/*
+ * 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 GrDrawAtlasPathOp_DEFINED
+#define GrDrawAtlasPathOp_DEFINED
+
+#include "src/gpu/ops/GrDrawOp.h"
+
+class GrDrawAtlasPathOp : public GrDrawOp {
+public:
+    DEFINE_OP_CLASS_ID
+
+    GrDrawAtlasPathOp(int numRenderTargetSamples, sk_sp<GrTextureProxy> atlasProxy,
+                      const SkIRect& devIBounds, const SkIVector& devToAtlasOffset,
+                      const SkMatrix& viewMatrix, GrPaint&& paint)
+            : GrDrawOp(ClassID())
+            , fEnableHWAA(numRenderTargetSamples > 1)
+            , fAtlasProxy(std::move(atlasProxy))
+            , fInstanceList(devIBounds, devToAtlasOffset, paint.getColor4f(), viewMatrix)
+            , fProcessors(std::move(paint)) {
+        SkASSERT(kTopLeft_GrSurfaceOrigin == fAtlasProxy->origin());
+        this->setBounds(SkRect::Make(devIBounds), HasAABloat::kYes, IsHairline::kNo);
+    }
+
+    const char* name() const override { return "GrDrawAtlasPathOp"; }
+    FixedFunctionFlags fixedFunctionFlags() const override {
+        return (fEnableHWAA) ? FixedFunctionFlags::kUsesHWAA : FixedFunctionFlags::kNone;
+    }
+    void visitProxies(const VisitProxyFunc& fn) const override {
+        fn(fAtlasProxy.get(), GrMipMapped::kNo);
+        fProcessors.visitProxies(fn);
+    }
+    GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*,
+                                      bool hasMixedSampledCoverage, GrClampType) override;
+    CombineResult onCombineIfPossible(GrOp*, GrRecordingContext::Arenas*, const GrCaps&) override;
+    void onPrepare(GrOpFlushState*) override;
+    void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
+
+private:
+    struct Instance {
+        constexpr static size_t Stride(bool usesLocalCoords) {
+            size_t stride = sizeof(Instance);
+            if (!usesLocalCoords) {
+                stride -= sizeof(Instance::fViewMatrixIfUsingLocalCoords);
+            }
+            return stride;
+        }
+        Instance(const SkIRect& devIBounds, SkIVector devToAtlasOffset, const SkPMColor4f& color,
+                 const SkMatrix& m)
+                : fDevIBounds(devIBounds)
+                , fDevToAtlasOffset(devToAtlasOffset)
+                , fColor(color)
+                , fViewMatrixIfUsingLocalCoords{m.getScaleX(), m.getSkewY(),
+                                                m.getSkewX(), m.getScaleY(),
+                                                m.getTranslateX(), m.getTranslateY()} {
+        }
+        SkIRect fDevIBounds;
+        SkIVector fDevToAtlasOffset;
+        SkPMColor4f fColor;
+        float fViewMatrixIfUsingLocalCoords[6];
+    };
+
+    struct InstanceList {
+        InstanceList(const SkIRect& devIBounds, SkIVector devToAtlasOffset,
+                     const SkPMColor4f& color, const SkMatrix& viewMatrix)
+                : fInstance(devIBounds, devToAtlasOffset, color, viewMatrix) {
+        }
+        InstanceList* fNext = nullptr;
+        Instance fInstance;
+    };
+
+    const bool fEnableHWAA;
+    const sk_sp<GrTextureProxy> fAtlasProxy;
+    bool fUsesLocalCoords = false;
+
+    InstanceList fInstanceList;
+    InstanceList** fInstanceTail = &fInstanceList.fNext;
+    int fInstanceCount = 1;
+
+    sk_sp<const GrBuffer> fInstanceBuffer;
+    int fBaseInstance;
+
+    GrProcessorSet fProcessors;
+};
+
+#endif
diff --git a/src/gpu/tessellate/GrGpuTessellationPathRenderer.cpp b/src/gpu/tessellate/GrGpuTessellationPathRenderer.cpp
index 1f70a0f..3e911b3 100644
--- a/src/gpu/tessellate/GrGpuTessellationPathRenderer.cpp
+++ b/src/gpu/tessellate/GrGpuTessellationPathRenderer.cpp
@@ -12,9 +12,19 @@
 #include "src/gpu/GrMemoryPool.h"
 #include "src/gpu/GrRecordingContextPriv.h"
 #include "src/gpu/GrRenderTargetContext.h"
+#include "src/gpu/GrRenderTargetContextPriv.h"
 #include "src/gpu/geometry/GrShape.h"
+#include "src/gpu/tessellate/GrDrawAtlasPathOp.h"
 #include "src/gpu/tessellate/GrTessellatePathOp.h"
 
+constexpr static SkISize kAtlasInitialSize{512, 512};
+constexpr static int kMaxAtlasSize = 2048;
+
+GrGpuTessellationPathRenderer::GrGpuTessellationPathRenderer(const GrCaps& caps) : fAtlas(
+        GrColorType::kAlpha_8, GrDynamicAtlas::InternalMultisample::kYes, kAtlasInitialSize,
+        std::min(kMaxAtlasSize, caps.maxPreferredRenderTargetSize()), caps) {
+}
+
 GrPathRenderer::CanDrawPath GrGpuTessellationPathRenderer::onCanDrawPath(
         const CanDrawPathArgs& args) const {
     // This class should not have been added to the chain without tessellation support.
@@ -38,13 +48,66 @@
 }
 
 bool GrGpuTessellationPathRenderer::onDrawPath(const DrawPathArgs& args) {
+    GrRenderTargetContext* renderTargetContext = args.fRenderTargetContext;
+    GrOpMemoryPool* pool = args.fContext->priv().opMemoryPool();
     SkPath path;
     args.fShape->asPath(&path);
 
-    auto op = args.fContext->priv().opMemoryPool()->allocate<GrTessellatePathOp>(
-            *args.fViewMatrix, path, std::move(args.fPaint), args.fAAType);
-    args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op));
+    // See if the path is small and simple enough to atlas instead of drawing directly.
+    //
+    // NOTE: The atlas uses alpha8 coverage even for msaa render targets. We could theoretically
+    // render the sample mask to an integer texture, but such a scheme would probably require
+    // GL_EXT_post_depth_coverage, which appears to have low adoption.
+    SkIRect devIBounds;
+    SkIVector devToAtlasOffset;
+    if (this->tryAddPathToAtlas(*args.fContext->priv().caps(), *args.fViewMatrix, path,
+                                args.fAAType, &devIBounds, &devToAtlasOffset)) {
+        auto op = pool->allocate<GrDrawAtlasPathOp>(
+                renderTargetContext->numSamples(), sk_ref_sp(fAtlas.textureProxy()),
+                devIBounds, devToAtlasOffset, *args.fViewMatrix, std::move(args.fPaint));
+        renderTargetContext->addDrawOp(*args.fClip, std::move(op));
+        return true;
+    }
 
+    auto op = pool->allocate<GrTessellatePathOp>(
+            *args.fViewMatrix, path, std::move(args.fPaint), args.fAAType);
+    renderTargetContext->addDrawOp(*args.fClip, std::move(op));
+    return true;
+}
+
+bool GrGpuTessellationPathRenderer::tryAddPathToAtlas(
+        const GrCaps& caps, const SkMatrix& viewMatrix, const SkPath& path, GrAAType aaType,
+        SkIRect* devIBounds, SkIVector* devToAtlasOffset) {
+    if (!caps.multisampleDisableSupport() && GrAAType::kNone == aaType) {
+        return false;
+    }
+
+    // Atlas paths require their points to be transformed on CPU. Check if the path has too many
+    // points to justify this CPU transformation.
+    if (path.countPoints() > 150) {
+        return false;
+    }
+
+    // Check if the path is too large for an atlas.
+    SkRect devBounds;
+    viewMatrix.mapRect(&devBounds, path.getBounds());
+    if (devBounds.height() * devBounds.width() > 100 * 100 ||
+        std::max(devBounds.height(), devBounds.width()) > kMaxAtlasSize / 2) {
+        return false;
+    }
+
+    devBounds.roundOut(devIBounds);
+    if (!fAtlas.addRect(*devIBounds, devToAtlasOffset)) {
+        return false;
+    }
+
+    SkMatrix atlasMatrix = viewMatrix;
+    atlasMatrix.postTranslate(devToAtlasOffset->x(), devToAtlasOffset->y());
+
+    // Concatenate this path onto our uber path that matches its fill and AA types.
+    SkPath* uberPath = this->getAtlasUberPath(path.getFillType(), GrAAType::kNone != aaType);
+    uberPath->moveTo(devToAtlasOffset->x(), devToAtlasOffset->y());  // Implicit moveTo(0,0).
+    uberPath->addPath(path, atlasMatrix);
     return true;
 }
 
@@ -58,3 +121,73 @@
             *args.fViewMatrix, path, GrPaint(), aaType, GrTessellatePathOp::Flags::kStencilOnly);
     args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op));
 }
+
+void GrGpuTessellationPathRenderer::preFlush(GrOnFlushResourceProvider* onFlushRP,
+                                             const uint32_t* opsTaskIDs, int numOpsTaskIDs) {
+    if (!fAtlas.drawBounds().isEmpty()) {
+        this->renderAtlas(onFlushRP);
+        fAtlas.reset(kAtlasInitialSize, *onFlushRP->caps());
+    }
+    for (SkPath& path : fAtlasUberPaths) {
+        path.reset();
+    }
+}
+
+constexpr static GrUserStencilSettings kTestStencil(
+    GrUserStencilSettings::StaticInit<
+        0x0000,
+        GrUserStencilTest::kNotEqual,
+        0xffff,
+        GrUserStencilOp::kKeep,
+        GrUserStencilOp::kKeep,
+        0xffff>());
+
+constexpr static GrUserStencilSettings kTestAndResetStencil(
+    GrUserStencilSettings::StaticInit<
+        0x0000,
+        GrUserStencilTest::kNotEqual,
+        0xffff,
+        GrUserStencilOp::kZero,
+        GrUserStencilOp::kKeep,
+        0xffff>());
+
+void GrGpuTessellationPathRenderer::renderAtlas(GrOnFlushResourceProvider* onFlushRP) {
+    auto rtc = fAtlas.instantiate(onFlushRP);
+    if (!rtc) {
+        return;
+    }
+
+    // Add ops to stencil the atlas paths.
+    for (auto antialias : {false, true}) {
+        for (auto fillType : {SkPathFillType::kWinding, SkPathFillType::kEvenOdd}) {
+            SkPath* uberPath = this->getAtlasUberPath(fillType, antialias);
+            if (uberPath->isEmpty()) {
+                continue;
+            }
+            uberPath->setFillType(fillType);
+            GrAAType aaType = (antialias) ? GrAAType::kMSAA : GrAAType::kNone;
+            auto op = onFlushRP->opMemoryPool()->allocate<GrTessellatePathOp>(
+                    SkMatrix::I(), *uberPath, GrPaint(), aaType,
+                    GrTessellatePathOp::Flags::kStencilOnly);
+            rtc->addDrawOp(GrNoClip(), std::move(op));
+        }
+    }
+
+    // The next draw will be the final op in the renderTargetContext. So if Ganesh is planning
+    // to discard the stencil values anyway, then we might not actually need to reset the
+    // stencil values back to zero.
+    bool mustResetStencil = !onFlushRP->caps()->discardStencilValuesAfterRenderPass() ||
+                            rtc->numSamples() <= 1;  // Need a stencil reset for mixed samples.
+
+    // Draw a fullscreen rect to convert our stencilled paths into alpha coverage masks.
+    GrPaint paint;
+    paint.setColor4f(SK_PMColor4fWHITE);
+    SkRect drawRect = SkRect::MakeIWH(fAtlas.drawBounds().width(), fAtlas.drawBounds().height());
+    rtc->priv().stencilRect(GrNoClip(), (mustResetStencil) ? &kTestAndResetStencil : &kTestStencil,
+                            std::move(paint), GrAA::kYes, SkMatrix::I(), drawRect, nullptr);
+
+    if (rtc->asSurfaceProxy()->requiresManualMSAAResolve()) {
+        onFlushRP->addTextureResolveTask(sk_ref_sp(rtc->asTextureProxy()),
+                                         GrSurfaceProxy::ResolveFlags::kMSAA);
+    }
+}
diff --git a/src/gpu/tessellate/GrGpuTessellationPathRenderer.h b/src/gpu/tessellate/GrGpuTessellationPathRenderer.h
index c26fb3d..8f53464 100644
--- a/src/gpu/tessellate/GrGpuTessellationPathRenderer.h
+++ b/src/gpu/tessellate/GrGpuTessellationPathRenderer.h
@@ -8,18 +8,38 @@
 #ifndef GrGpuTessellationPathRenderer_DEFINED
 #define GrGpuTessellationPathRenderer_DEFINED
 
+#include "src/gpu/GrDynamicAtlas.h"
+#include "src/gpu/GrOnFlushResourceProvider.h"
 #include "src/gpu/GrPathRenderer.h"
+#include <map>
 
 // This is the tie-in point for path rendering via GrTessellatePathOp.
-class GrGpuTessellationPathRenderer : public GrPathRenderer {
+class GrGpuTessellationPathRenderer : public GrPathRenderer, public GrOnFlushCallbackObject {
+public:
+    GrGpuTessellationPathRenderer(const GrCaps&);
     StencilSupport onGetStencilSupport(const GrShape& shape) const override {
         // TODO: Single-pass (e.g., convex) paths can have full support.
         return kStencilOnly_StencilSupport;
     }
-
     CanDrawPath onCanDrawPath(const CanDrawPathArgs&) const override;
     bool onDrawPath(const DrawPathArgs&) override;
     void onStencilPath(const StencilPathArgs&) override;
+    void preFlush(GrOnFlushResourceProvider*, const uint32_t* opsTaskIDs,
+                  int numOpsTaskIDs) override;
+
+private:
+    SkPath* getAtlasUberPath(SkPathFillType fillType, bool antialias) {
+        int idx = (int)antialias << 1;
+        idx |= (int)fillType & 1;
+        return &fAtlasUberPaths[idx];
+    }
+    // Allocates space in fAtlas if the path is small and simple enough, and if there is room.
+    bool tryAddPathToAtlas(const GrCaps&, const SkMatrix&, const SkPath&, GrAAType,
+                           SkIRect* devIBounds, SkIVector* devToAtlasOffset);
+    void renderAtlas(GrOnFlushResourceProvider*);
+
+    GrDynamicAtlas fAtlas;
+    SkPath fAtlasUberPaths[4];  // 2 fillTypes * 2 antialias modes.
 };
 
 #endif
diff --git a/src/gpu/tessellate/GrTessellatePathOp.h b/src/gpu/tessellate/GrTessellatePathOp.h
index e080a1d..2679ced 100644
--- a/src/gpu/tessellate/GrTessellatePathOp.h
+++ b/src/gpu/tessellate/GrTessellatePathOp.h
@@ -39,8 +39,7 @@
             , fProcessors(std::move(paint)) {
         SkRect devBounds;
         fViewMatrix.mapRect(&devBounds, path.getBounds());
-        this->setBounds(devBounds, HasAABloat(GrAAType::kCoverage == fAAType),
-                        GrOp::IsHairline::kNo);
+        this->setBounds(devBounds, HasAABloat(GrAAType::kCoverage == fAAType), IsHairline::kNo);
     }
 
     const char* name() const override { return "GrTessellatePathOp"; }