C++程序  |  681行  |  25.67 KB


/*
 * Copyright 2014 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "GrAADistanceFieldPathRenderer.h"

#include "GrBatch.h"
#include "GrBatchTarget.h"
#include "GrBatchTest.h"
#include "GrContext.h"
#include "GrPipelineBuilder.h"
#include "GrResourceProvider.h"
#include "GrSurfacePriv.h"
#include "GrSWMaskHelper.h"
#include "GrTexturePriv.h"
#include "GrVertexBuffer.h"
#include "effects/GrDistanceFieldGeoProc.h"

#include "SkDistanceFieldGen.h"
#include "SkRTConf.h"

#define ATLAS_TEXTURE_WIDTH 1024
#define ATLAS_TEXTURE_HEIGHT 2048
#define PLOT_WIDTH  256
#define PLOT_HEIGHT 256

#define NUM_PLOTS_X   (ATLAS_TEXTURE_WIDTH / PLOT_WIDTH)
#define NUM_PLOTS_Y   (ATLAS_TEXTURE_HEIGHT / PLOT_HEIGHT)

#ifdef DF_PATH_TRACKING
static int g_NumCachedPaths = 0;
static int g_NumFreedPaths = 0;
#endif

// mip levels
static const int kSmallMIP = 32;
static const int kMediumMIP = 78;
static const int kLargeMIP = 192;

// Callback to clear out internal path cache when eviction occurs
void GrAADistanceFieldPathRenderer::HandleEviction(GrBatchAtlas::AtlasID id, void* pr) {
    GrAADistanceFieldPathRenderer* dfpr = (GrAADistanceFieldPathRenderer*)pr;
    // remove any paths that use this plot
    PathDataList::Iter iter;
    iter.init(dfpr->fPathList, PathDataList::Iter::kHead_IterStart);
    PathData* pathData;
    while ((pathData = iter.get())) {
        iter.next();
        if (id == pathData->fID) {
            dfpr->fPathCache.remove(pathData->fKey);
            dfpr->fPathList.remove(pathData);
            SkDELETE(pathData);
#ifdef DF_PATH_TRACKING
            ++g_NumFreedPaths;
#endif
        }
    }
}

////////////////////////////////////////////////////////////////////////////////
GrAADistanceFieldPathRenderer::GrAADistanceFieldPathRenderer(GrContext* context)
    : fContext(context)
    , fAtlas(NULL) {
}

GrAADistanceFieldPathRenderer::~GrAADistanceFieldPathRenderer() {
    PathDataList::Iter iter;
    iter.init(fPathList, PathDataList::Iter::kHead_IterStart);
    PathData* pathData;
    while ((pathData = iter.get())) {
        iter.next();
        fPathList.remove(pathData);
        SkDELETE(pathData);
    }
    SkDELETE(fAtlas);

#ifdef DF_PATH_TRACKING
    SkDebugf("Cached paths: %d, freed paths: %d\n", g_NumCachedPaths, g_NumFreedPaths);
#endif
}

////////////////////////////////////////////////////////////////////////////////
bool GrAADistanceFieldPathRenderer::canDrawPath(const GrDrawTarget* target,
                                                const GrPipelineBuilder* pipelineBuilder,
                                                const SkMatrix& viewMatrix,
                                                const SkPath& path,
                                                const GrStrokeInfo& stroke,
                                                bool antiAlias) const {
    
    // TODO: Support inverse fill
    // TODO: Support strokes
    if (!target->caps()->shaderCaps()->shaderDerivativeSupport() || !antiAlias 
        || path.isInverseFillType() || path.isVolatile() || !stroke.isFillStyle()) {
        return false;
    }

    // currently don't support perspective
    if (viewMatrix.hasPerspective()) {
        return false;
    }
    
    // only support paths smaller than 64x64, scaled to less than 256x256
    // the goal is to accelerate rendering of lots of small paths that may be scaling
    SkScalar maxScale = viewMatrix.getMaxScale();
    const SkRect& bounds = path.getBounds();
    SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
    return maxDim < 64.f && maxDim * maxScale < 256.f;
}


GrPathRenderer::StencilSupport
GrAADistanceFieldPathRenderer::onGetStencilSupport(const GrDrawTarget*,
                                                   const GrPipelineBuilder*,
                                                   const SkPath&,
                                                   const GrStrokeInfo&) const {
    return GrPathRenderer::kNoSupport_StencilSupport;
}

////////////////////////////////////////////////////////////////////////////////

// padding around path bounds to allow for antialiased pixels
static const SkScalar kAntiAliasPad = 1.0f;

class AADistanceFieldPathBatch : public GrBatch {
public:
    typedef GrAADistanceFieldPathRenderer::PathData PathData;
    typedef SkTDynamicHash<PathData, PathData::Key> PathCache;
    typedef GrAADistanceFieldPathRenderer::PathDataList PathDataList;

    struct Geometry {
        Geometry(const SkStrokeRec& stroke) : fStroke(stroke) {}
        SkPath fPath;
        SkStrokeRec fStroke;
        bool fAntiAlias;
        PathData* fPathData;
    };

    static GrBatch* Create(const Geometry& geometry, GrColor color, const SkMatrix& viewMatrix,
                           GrBatchAtlas* atlas, PathCache* pathCache, PathDataList* pathList) {
        return SkNEW_ARGS(AADistanceFieldPathBatch, (geometry, color, viewMatrix,
                                                     atlas, pathCache, pathList));
    }

    const char* name() const override { return "AADistanceFieldPathBatch"; }

    void getInvariantOutputColor(GrInitInvariantOutput* out) const override {
        out->setKnownFourComponents(fBatch.fColor);
    }

    void getInvariantOutputCoverage(GrInitInvariantOutput* out) const override {
        out->setUnknownSingleComponent();
    }

    void initBatchTracker(const GrPipelineInfo& init) override {
        // Handle any color overrides
        if (init.fColorIgnored) {
            fBatch.fColor = GrColor_ILLEGAL;
        } else if (GrColor_ILLEGAL != init.fOverrideColor) {
            fBatch.fColor = init.fOverrideColor;
        }

        // setup batch properties
        fBatch.fColorIgnored = init.fColorIgnored;
        fBatch.fUsesLocalCoords = init.fUsesLocalCoords;
        fBatch.fCoverageIgnored = init.fCoverageIgnored;
    }

    struct FlushInfo {
        SkAutoTUnref<const GrVertexBuffer> fVertexBuffer;
        SkAutoTUnref<const GrIndexBuffer>  fIndexBuffer;
        int fVertexOffset;
        int fInstancesToFlush;
    };

    void generateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) override {
        int instanceCount = fGeoData.count();

        SkMatrix invert;
        if (this->usesLocalCoords() && !this->viewMatrix().invert(&invert)) {
            SkDebugf("Could not invert viewmatrix\n");
            return;
        }

        uint32_t flags = 0;
        flags |= this->viewMatrix().isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;

        GrTextureParams params(SkShader::kRepeat_TileMode, GrTextureParams::kBilerp_FilterMode);

        // Setup GrGeometryProcessor
        GrBatchAtlas* atlas = fAtlas;
        SkAutoTUnref<GrGeometryProcessor> dfProcessor(
                GrDistanceFieldPathGeoProc::Create(this->color(),
                                                   this->viewMatrix(),
                                                   atlas->getTexture(),
                                                   params,
                                                   flags));

        this->initDraw(batchTarget, dfProcessor, pipeline);

        FlushInfo flushInfo;

        // allocate vertices
        size_t vertexStride = dfProcessor->getVertexStride();
        SkASSERT(vertexStride == 2 * sizeof(SkPoint));

        const GrVertexBuffer* vertexBuffer;
        void* vertices = batchTarget->makeVertSpace(vertexStride,
                                                    kVerticesPerQuad * instanceCount,
                                                    &vertexBuffer,
                                                    &flushInfo.fVertexOffset);
        flushInfo.fVertexBuffer.reset(SkRef(vertexBuffer));
        flushInfo.fIndexBuffer.reset(batchTarget->resourceProvider()->refQuadIndexBuffer());
        if (!vertices || !flushInfo.fIndexBuffer) {
            SkDebugf("Could not allocate vertices\n");
            return;
        }

        flushInfo.fInstancesToFlush = 0;
        for (int i = 0; i < instanceCount; i++) {
            Geometry& args = fGeoData[i];

            // get mip level
            SkScalar maxScale = this->viewMatrix().getMaxScale();
            const SkRect& bounds = args.fPath.getBounds();
            SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
            SkScalar size = maxScale * maxDim;
            uint32_t desiredDimension;
            if (size <= kSmallMIP) {
                desiredDimension = kSmallMIP;
            } else if (size <= kMediumMIP) {
                desiredDimension = kMediumMIP;
            } else {
                desiredDimension = kLargeMIP;
            }

            // check to see if path is cached
            // TODO: handle stroked vs. filled version of same path
            PathData::Key key = { args.fPath.getGenerationID(), desiredDimension };
            args.fPathData = fPathCache->find(key);
            if (NULL == args.fPathData || !atlas->hasID(args.fPathData->fID)) {
                // Remove the stale cache entry
                if (args.fPathData) {
                    fPathCache->remove(args.fPathData->fKey);
                    fPathList->remove(args.fPathData);
                    SkDELETE(args.fPathData);
                }
                SkScalar scale = desiredDimension/maxDim;
                args.fPathData = SkNEW(PathData);
                if (!this->addPathToAtlas(batchTarget,
                                          dfProcessor,
                                          pipeline,
                                          &flushInfo,
                                          atlas,
                                          args.fPathData,
                                          args.fPath,
                                          args.fStroke,
                                          args.fAntiAlias,
                                          desiredDimension,
                                          scale)) {
                    SkDebugf("Can't rasterize path\n");
                    return;
                }
            }

            atlas->setLastUseToken(args.fPathData->fID, batchTarget->currentToken());

            // Now set vertices
            intptr_t offset = reinterpret_cast<intptr_t>(vertices);
            offset += i * kVerticesPerQuad * vertexStride;
            SkPoint* positions = reinterpret_cast<SkPoint*>(offset);
            this->writePathVertices(batchTarget,
                                    atlas,
                                    pipeline,
                                    dfProcessor,
                                    positions,
                                    vertexStride,
                                    this->viewMatrix(),
                                    args.fPath,
                                    args.fPathData);
            flushInfo.fInstancesToFlush++;
        }

        this->flush(batchTarget, &flushInfo);
    }

    SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; }

private:
    AADistanceFieldPathBatch(const Geometry& geometry, GrColor color, const SkMatrix& viewMatrix,
                             GrBatchAtlas* atlas,
                             PathCache* pathCache, PathDataList* pathList) {
        this->initClassID<AADistanceFieldPathBatch>();
        fBatch.fColor = color;
        fBatch.fViewMatrix = viewMatrix;
        fGeoData.push_back(geometry);
        fGeoData.back().fPathData = NULL;

        fAtlas = atlas;
        fPathCache = pathCache;
        fPathList = pathList;

        // Compute bounds
        fBounds = geometry.fPath.getBounds();
        viewMatrix.mapRect(&fBounds);
    }

    bool addPathToAtlas(GrBatchTarget* batchTarget,
                        const GrGeometryProcessor* dfProcessor,
                        const GrPipeline* pipeline,
                        FlushInfo* flushInfo,
                        GrBatchAtlas* atlas,
                        PathData* pathData,
                        const SkPath& path,
                        const SkStrokeRec&
                        stroke, bool antiAlias,
                        uint32_t dimension,
                        SkScalar scale) {
        const SkRect& bounds = path.getBounds();

        // generate bounding rect for bitmap draw
        SkRect scaledBounds = bounds;
        // scale to mip level size
        scaledBounds.fLeft *= scale;
        scaledBounds.fTop *= scale;
        scaledBounds.fRight *= scale;
        scaledBounds.fBottom *= scale;
        // move the origin to an integer boundary (gives better results)
        SkScalar dx = SkScalarFraction(scaledBounds.fLeft);
        SkScalar dy = SkScalarFraction(scaledBounds.fTop);
        scaledBounds.offset(-dx, -dy);
        // get integer boundary
        SkIRect devPathBounds;
        scaledBounds.roundOut(&devPathBounds);
        // pad to allow room for antialiasing
        devPathBounds.outset(SkScalarCeilToInt(kAntiAliasPad), SkScalarCeilToInt(kAntiAliasPad));
        // move origin to upper left corner
        devPathBounds.offsetTo(0,0);

        // draw path to bitmap
        SkMatrix drawMatrix;
        drawMatrix.setTranslate(-bounds.left(), -bounds.top());
        drawMatrix.postScale(scale, scale);
        drawMatrix.postTranslate(kAntiAliasPad, kAntiAliasPad);

        // setup bitmap backing
        // Now translate so the bound's UL corner is at the origin
        drawMatrix.postTranslate(-devPathBounds.fLeft * SK_Scalar1,
                                 -devPathBounds.fTop * SK_Scalar1);
        SkIRect pathBounds = SkIRect::MakeWH(devPathBounds.width(),
                                             devPathBounds.height());

        SkBitmap bmp;
        const SkImageInfo bmImageInfo = SkImageInfo::MakeA8(pathBounds.fRight,
                                                            pathBounds.fBottom);
        if (!bmp.tryAllocPixels(bmImageInfo)) {
            return false;
        }

        sk_bzero(bmp.getPixels(), bmp.getSafeSize());

        // rasterize path
        SkPaint paint;
        if (stroke.isHairlineStyle()) {
            paint.setStyle(SkPaint::kStroke_Style);
            paint.setStrokeWidth(SK_Scalar1);
        } else {
            if (stroke.isFillStyle()) {
                paint.setStyle(SkPaint::kFill_Style);
            } else {
                paint.setStyle(SkPaint::kStroke_Style);
                paint.setStrokeJoin(stroke.getJoin());
                paint.setStrokeCap(stroke.getCap());
                paint.setStrokeWidth(stroke.getWidth());
            }
        }
        paint.setAntiAlias(antiAlias);

        SkDraw draw;
        sk_bzero(&draw, sizeof(draw));

        SkRasterClip rasterClip;
        rasterClip.setRect(pathBounds);
        draw.fRC = &rasterClip;
        draw.fClip = &rasterClip.bwRgn();
        draw.fMatrix = &drawMatrix;
        draw.fBitmap = &bmp;

        draw.drawPathCoverage(path, paint);

        // generate signed distance field
        devPathBounds.outset(SK_DistanceFieldPad, SK_DistanceFieldPad);
        int width = devPathBounds.width();
        int height = devPathBounds.height();
        // TODO We should really generate this directly into the plot somehow
        SkAutoSMalloc<1024> dfStorage(width * height * sizeof(unsigned char));

        // Generate signed distance field
        {
            SkAutoLockPixels alp(bmp);

            SkGenerateDistanceFieldFromA8Image((unsigned char*)dfStorage.get(),
                                               (const unsigned char*)bmp.getPixels(),
                                               bmp.width(), bmp.height(), bmp.rowBytes());
        }

        // add to atlas
        SkIPoint16 atlasLocation;
        GrBatchAtlas::AtlasID id;
        bool success = atlas->addToAtlas(&id, batchTarget, width, height, dfStorage.get(),
                                         &atlasLocation);
        if (!success) {
            this->flush(batchTarget, flushInfo);
            this->initDraw(batchTarget, dfProcessor, pipeline);

            SkDEBUGCODE(success =) atlas->addToAtlas(&id, batchTarget, width, height,
                                                     dfStorage.get(), &atlasLocation);
            SkASSERT(success);

        }

        // add to cache
        pathData->fKey.fGenID = path.getGenerationID();
        pathData->fKey.fDimension = dimension;
        pathData->fScale = scale;
        pathData->fID = id;
        // change the scaled rect to match the size of the inset distance field
        scaledBounds.fRight = scaledBounds.fLeft +
            SkIntToScalar(devPathBounds.width() - 2*SK_DistanceFieldInset);
        scaledBounds.fBottom = scaledBounds.fTop +
            SkIntToScalar(devPathBounds.height() - 2*SK_DistanceFieldInset);
        // shift the origin to the correct place relative to the distance field
        // need to also restore the fractional translation
        scaledBounds.offset(-SkIntToScalar(SK_DistanceFieldInset) - kAntiAliasPad + dx,
                            -SkIntToScalar(SK_DistanceFieldInset) - kAntiAliasPad + dy);
        pathData->fBounds = scaledBounds;
        // origin we render from is inset from distance field edge
        atlasLocation.fX += SK_DistanceFieldInset;
        atlasLocation.fY += SK_DistanceFieldInset;
        pathData->fAtlasLocation = atlasLocation;

        fPathCache->add(pathData);
        fPathList->addToTail(pathData);
#ifdef DF_PATH_TRACKING
        ++g_NumCachedPaths;
#endif
        return true;
    }

    void writePathVertices(GrBatchTarget* target,
                           GrBatchAtlas* atlas,
                           const GrPipeline* pipeline,
                           const GrGeometryProcessor* gp,
                           SkPoint* positions,
                           size_t vertexStride,
                           const SkMatrix& viewMatrix,
                           const SkPath& path,
                           const PathData* pathData) {
        GrTexture* texture = atlas->getTexture();

        SkScalar dx = pathData->fBounds.fLeft;
        SkScalar dy = pathData->fBounds.fTop;
        SkScalar width = pathData->fBounds.width();
        SkScalar height = pathData->fBounds.height();

        SkScalar invScale = 1.0f / pathData->fScale;
        dx *= invScale;
        dy *= invScale;
        width *= invScale;
        height *= invScale;

        SkFixed tx = SkIntToFixed(pathData->fAtlasLocation.fX);
        SkFixed ty = SkIntToFixed(pathData->fAtlasLocation.fY);
        SkFixed tw = SkScalarToFixed(pathData->fBounds.width());
        SkFixed th = SkScalarToFixed(pathData->fBounds.height());

        // vertex positions
        // TODO make the vertex attributes a struct
        SkRect r = SkRect::MakeXYWH(dx, dy, width, height);
        positions->setRectFan(r.left(), r.top(), r.right(), r.bottom(), vertexStride);

        // vertex texture coords
        SkPoint* textureCoords = positions + 1;
        textureCoords->setRectFan(SkFixedToFloat(texture->texturePriv().normalizeFixedX(tx)),
                                  SkFixedToFloat(texture->texturePriv().normalizeFixedY(ty)),
                                  SkFixedToFloat(texture->texturePriv().normalizeFixedX(tx + tw)),
                                  SkFixedToFloat(texture->texturePriv().normalizeFixedY(ty + th)),
                                  vertexStride);
    }

    void initDraw(GrBatchTarget* batchTarget,
                  const GrGeometryProcessor* dfProcessor,
                  const GrPipeline* pipeline) {
        batchTarget->initDraw(dfProcessor, pipeline);

        // TODO remove this when batch is everywhere
        GrPipelineInfo init;
        init.fColorIgnored = fBatch.fColorIgnored;
        init.fOverrideColor = GrColor_ILLEGAL;
        init.fCoverageIgnored = fBatch.fCoverageIgnored;
        init.fUsesLocalCoords = this->usesLocalCoords();
        dfProcessor->initBatchTracker(batchTarget->currentBatchTracker(), init);
    }

    void flush(GrBatchTarget* batchTarget, FlushInfo* flushInfo) {
        GrVertices vertices;
        int maxInstancesPerDraw = flushInfo->fIndexBuffer->maxQuads();
        vertices.initInstanced(kTriangles_GrPrimitiveType, flushInfo->fVertexBuffer,
            flushInfo->fIndexBuffer, flushInfo->fVertexOffset, kVerticesPerQuad,
            kIndicesPerQuad, flushInfo->fInstancesToFlush, maxInstancesPerDraw);
        batchTarget->draw(vertices);
        flushInfo->fVertexOffset += kVerticesPerQuad * flushInfo->fInstancesToFlush;
        flushInfo->fInstancesToFlush = 0;
    }

    GrColor color() const { return fBatch.fColor; }
    const SkMatrix& viewMatrix() const { return fBatch.fViewMatrix; }
    bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; }

    bool onCombineIfPossible(GrBatch* t) override {
        AADistanceFieldPathBatch* that = t->cast<AADistanceFieldPathBatch>();

        // TODO we could actually probably do a bunch of this work on the CPU, ie map viewMatrix,
        // maybe upload color via attribute
        if (this->color() != that->color()) {
            return false;
        }

        if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
            return false;
        }

        fGeoData.push_back_n(that->geoData()->count(), that->geoData()->begin());
        this->joinBounds(that->bounds());
        return true;
    }

    struct BatchTracker {
        GrColor fColor;
        SkMatrix fViewMatrix;
        bool fUsesLocalCoords;
        bool fColorIgnored;
        bool fCoverageIgnored;
    };

    BatchTracker fBatch;
    SkSTArray<1, Geometry, true> fGeoData;
    GrBatchAtlas* fAtlas;
    PathCache* fPathCache;
    PathDataList* fPathList;
};

static GrBatchAtlas* create_atlas(GrContext* context, GrBatchAtlas::EvictionFunc func, void* data) {
    GrBatchAtlas* atlas;
    // Create a new atlas
    GrSurfaceDesc desc;
    desc.fFlags = kNone_GrSurfaceFlags;
    desc.fWidth = ATLAS_TEXTURE_WIDTH;
    desc.fHeight = ATLAS_TEXTURE_HEIGHT;
    desc.fConfig = kAlpha_8_GrPixelConfig;

    // We don't want to flush the context so we claim we're in the middle of flushing so as to
    // guarantee we do not recieve a texture with pending IO
    GrTexture* texture = context->textureProvider()->refScratchTexture(
        desc, GrTextureProvider::kApprox_ScratchTexMatch, true);
    if (texture) {
        atlas = SkNEW_ARGS(GrBatchAtlas, (texture, NUM_PLOTS_X, NUM_PLOTS_Y));
    } else {
        return NULL;
    }
    atlas->registerEvictionCallback(func, data);
    return atlas;
}

bool GrAADistanceFieldPathRenderer::onDrawPath(GrDrawTarget* target,
                                               GrPipelineBuilder* pipelineBuilder,
                                               GrColor color,
                                               const SkMatrix& viewMatrix,
                                               const SkPath& path,
                                               const GrStrokeInfo& stroke,
                                               bool antiAlias) {
    // we've already bailed on inverse filled paths, so this is safe
    if (path.isEmpty()) {
        return true;
    }

    SkASSERT(fContext);

    if (!fAtlas) {
        fAtlas = create_atlas(fContext, &GrAADistanceFieldPathRenderer::HandleEviction,
                              (void*)this);
        if (!fAtlas) {
            return false;
        }
    }

    AADistanceFieldPathBatch::Geometry geometry(stroke.getStrokeRec());
    geometry.fPath = path;
    geometry.fAntiAlias = antiAlias;

    SkAutoTUnref<GrBatch> batch(AADistanceFieldPathBatch::Create(geometry, color, viewMatrix,
                                                                 fAtlas, &fPathCache, &fPathList));
    target->drawBatch(pipelineBuilder, batch);

    return true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////

#ifdef GR_TEST_UTILS

struct PathTestStruct {
    typedef GrAADistanceFieldPathRenderer::PathCache PathCache;
    typedef GrAADistanceFieldPathRenderer::PathData PathData;
    typedef GrAADistanceFieldPathRenderer::PathDataList PathDataList;
    PathTestStruct() : fContextID(SK_InvalidGenID), fAtlas(NULL) {}
    ~PathTestStruct() { this->reset(); }

    void reset() {
        PathDataList::Iter iter;
        iter.init(fPathList, PathDataList::Iter::kHead_IterStart);
        PathData* pathData;
        while ((pathData = iter.get())) {
            iter.next();
            fPathList.remove(pathData);
            SkDELETE(pathData);
        }
        SkDELETE(fAtlas);
        fPathCache.reset();
    }

    static void HandleEviction(GrBatchAtlas::AtlasID id, void* pr) {
        PathTestStruct* dfpr = (PathTestStruct*)pr;
        // remove any paths that use this plot
        PathDataList::Iter iter;
        iter.init(dfpr->fPathList, PathDataList::Iter::kHead_IterStart);
        PathData* pathData;
        while ((pathData = iter.get())) {
            iter.next();
            if (id == pathData->fID) {
                dfpr->fPathCache.remove(pathData->fKey);
                dfpr->fPathList.remove(pathData);
                SkDELETE(pathData);
            }
        }
    }

    uint32_t fContextID;
    GrBatchAtlas* fAtlas;
    PathCache fPathCache;
    PathDataList fPathList;
};

BATCH_TEST_DEFINE(AADistanceFieldPathBatch) {
    static PathTestStruct gTestStruct;

    if (context->uniqueID() != gTestStruct.fContextID) {
        gTestStruct.fContextID = context->uniqueID();
        gTestStruct.reset();
        gTestStruct.fAtlas = create_atlas(context, &PathTestStruct::HandleEviction,
                                          (void*)&gTestStruct);
    }

    SkMatrix viewMatrix = GrTest::TestMatrix(random);
    GrColor color = GrRandomColor(random);

    AADistanceFieldPathBatch::Geometry geometry(GrTest::TestStrokeRec(random));
    geometry.fPath = GrTest::TestPath(random);
    geometry.fAntiAlias = random->nextBool();

    return AADistanceFieldPathBatch::Create(geometry, color, viewMatrix,
                                            gTestStruct.fAtlas,
                                            &gTestStruct.fPathCache,
                                            &gTestStruct.fPathList);
}

#endif