Home | History | Annotate | Download | only in batches
      1 
      2 /*
      3  * Copyright 2014 Google Inc.
      4  *
      5  * Use of this source code is governed by a BSD-style license that can be
      6  * found in the LICENSE file.
      7  */
      8 
      9 #include "GrAADistanceFieldPathRenderer.h"
     10 
     11 #include "GrBatchFlushState.h"
     12 #include "GrBatchTest.h"
     13 #include "GrContext.h"
     14 #include "GrPipelineBuilder.h"
     15 #include "GrResourceProvider.h"
     16 #include "GrSurfacePriv.h"
     17 #include "GrSWMaskHelper.h"
     18 #include "GrTexturePriv.h"
     19 #include "GrVertexBuffer.h"
     20 #include "batches/GrVertexBatch.h"
     21 #include "effects/GrDistanceFieldGeoProc.h"
     22 
     23 #include "SkDistanceFieldGen.h"
     24 #include "SkRTConf.h"
     25 
     26 #define ATLAS_TEXTURE_WIDTH 2048
     27 #define ATLAS_TEXTURE_HEIGHT 2048
     28 #define PLOT_WIDTH  512
     29 #define PLOT_HEIGHT 256
     30 
     31 #define NUM_PLOTS_X   (ATLAS_TEXTURE_WIDTH / PLOT_WIDTH)
     32 #define NUM_PLOTS_Y   (ATLAS_TEXTURE_HEIGHT / PLOT_HEIGHT)
     33 
     34 #ifdef DF_PATH_TRACKING
     35 static int g_NumCachedPaths = 0;
     36 static int g_NumFreedPaths = 0;
     37 #endif
     38 
     39 // mip levels
     40 static const int kSmallMIP = 32;
     41 static const int kMediumMIP = 73;
     42 static const int kLargeMIP = 162;
     43 
     44 // Callback to clear out internal path cache when eviction occurs
     45 void GrAADistanceFieldPathRenderer::HandleEviction(GrBatchAtlas::AtlasID id, void* pr) {
     46     GrAADistanceFieldPathRenderer* dfpr = (GrAADistanceFieldPathRenderer*)pr;
     47     // remove any paths that use this plot
     48     PathDataList::Iter iter;
     49     iter.init(dfpr->fPathList, PathDataList::Iter::kHead_IterStart);
     50     PathData* pathData;
     51     while ((pathData = iter.get())) {
     52         iter.next();
     53         if (id == pathData->fID) {
     54             dfpr->fPathCache.remove(pathData->fKey);
     55             dfpr->fPathList.remove(pathData);
     56             delete pathData;
     57 #ifdef DF_PATH_TRACKING
     58             ++g_NumFreedPaths;
     59 #endif
     60         }
     61     }
     62 }
     63 
     64 ////////////////////////////////////////////////////////////////////////////////
     65 GrAADistanceFieldPathRenderer::GrAADistanceFieldPathRenderer() : fAtlas(nullptr) {}
     66 
     67 GrAADistanceFieldPathRenderer::~GrAADistanceFieldPathRenderer() {
     68     PathDataList::Iter iter;
     69     iter.init(fPathList, PathDataList::Iter::kHead_IterStart);
     70     PathData* pathData;
     71     while ((pathData = iter.get())) {
     72         iter.next();
     73         fPathList.remove(pathData);
     74         delete pathData;
     75     }
     76     delete fAtlas;
     77 
     78 #ifdef DF_PATH_TRACKING
     79     SkDebugf("Cached paths: %d, freed paths: %d\n", g_NumCachedPaths, g_NumFreedPaths);
     80 #endif
     81 }
     82 
     83 ////////////////////////////////////////////////////////////////////////////////
     84 bool GrAADistanceFieldPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
     85 
     86     // TODO: Support inverse fill
     87     if (!args.fShaderCaps->shaderDerivativeSupport() || !args.fAntiAlias ||
     88         SkStrokeRec::kHairline_Style == args.fStroke->getStyle() ||
     89         args.fPath->isInverseFillType() || args.fPath->isVolatile() ||
     90         // We don't currently apply the dash or factor it into the DF key. (skbug.com/5082)
     91         args.fStroke->isDashed()) {
     92         return false;
     93     }
     94 
     95     // currently don't support perspective
     96     if (args.fViewMatrix->hasPerspective()) {
     97         return false;
     98     }
     99 
    100     // only support paths with bounds within kMediumMIP by kMediumMIP,
    101     // scaled to have bounds within 2.0f*kLargeMIP by 2.0f*kLargeMIP
    102     // the goal is to accelerate rendering of lots of small paths that may be scaling
    103     SkScalar maxScale = args.fViewMatrix->getMaxScale();
    104     const SkRect& bounds = args.fPath->getBounds();
    105     SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
    106     // Approximate stroked size by adding the maximum of the stroke width or 2x the miter limit
    107     if (!args.fStroke->isFillStyle()) {
    108         SkScalar extraWidth = args.fStroke->getWidth();
    109         if (SkPaint::kMiter_Join == args.fStroke->getJoin()) {
    110             extraWidth = SkTMax(extraWidth, 2.0f*args.fStroke->getMiter());
    111         }
    112         maxDim += extraWidth;
    113     }
    114 
    115     return maxDim <= kMediumMIP && maxDim * maxScale <= 2.0f*kLargeMIP;
    116 }
    117 
    118 ////////////////////////////////////////////////////////////////////////////////
    119 
    120 // padding around path bounds to allow for antialiased pixels
    121 static const SkScalar kAntiAliasPad = 1.0f;
    122 
    123 class AADistanceFieldPathBatch : public GrVertexBatch {
    124 public:
    125     DEFINE_BATCH_CLASS_ID
    126 
    127     typedef GrAADistanceFieldPathRenderer::PathData PathData;
    128     typedef SkTDynamicHash<PathData, PathData::Key> PathCache;
    129     typedef GrAADistanceFieldPathRenderer::PathDataList PathDataList;
    130 
    131     struct Geometry {
    132         Geometry(const SkStrokeRec& stroke) : fStroke(stroke) {
    133             if (!stroke.needToApply()) {
    134                 // purify unused values to ensure binary equality
    135                 fStroke.setStrokeParams(SkPaint::kDefault_Cap, SkPaint::kDefault_Join,
    136                                         SkIntToScalar(4));
    137                 if (fStroke.getWidth() < 0) {
    138                     fStroke.setStrokeStyle(-1.0f);
    139                 }
    140             }
    141         }
    142         SkPath fPath;
    143         // The unique ID of the path involved in this draw. This may be different than the ID
    144         // in fPath since that path may have resulted from a SkStrokeRec::applyToPath call.
    145         uint32_t fGenID;
    146         SkStrokeRec fStroke;
    147         GrColor fColor;
    148         bool fAntiAlias;
    149     };
    150 
    151     static GrDrawBatch* Create(const Geometry& geometry, const SkMatrix& viewMatrix,
    152                                GrBatchAtlas* atlas, PathCache* pathCache, PathDataList* pathList) {
    153         return new AADistanceFieldPathBatch(geometry, viewMatrix, atlas, pathCache, pathList);
    154     }
    155 
    156     const char* name() const override { return "AADistanceFieldPathBatch"; }
    157 
    158     void computePipelineOptimizations(GrInitInvariantOutput* color,
    159                                       GrInitInvariantOutput* coverage,
    160                                       GrBatchToXPOverrides* overrides) const override {
    161         color->setKnownFourComponents(fGeoData[0].fColor);
    162         coverage->setUnknownSingleComponent();
    163     }
    164 
    165 private:
    166     void initBatchTracker(const GrXPOverridesForBatch& overrides) override {
    167         // Handle any color overrides
    168         if (!overrides.readsColor()) {
    169             fGeoData[0].fColor = GrColor_ILLEGAL;
    170         }
    171         overrides.getOverrideColorIfSet(&fGeoData[0].fColor);
    172 
    173         // setup batch properties
    174         fBatch.fColorIgnored = !overrides.readsColor();
    175         fBatch.fUsesLocalCoords = overrides.readsLocalCoords();
    176         fBatch.fCoverageIgnored = !overrides.readsCoverage();
    177     }
    178 
    179     struct FlushInfo {
    180         SkAutoTUnref<const GrVertexBuffer> fVertexBuffer;
    181         SkAutoTUnref<const GrIndexBuffer>  fIndexBuffer;
    182         int fVertexOffset;
    183         int fInstancesToFlush;
    184     };
    185 
    186     void onPrepareDraws(Target* target) const override {
    187         int instanceCount = fGeoData.count();
    188 
    189         SkMatrix invert;
    190         if (this->usesLocalCoords() && !this->viewMatrix().invert(&invert)) {
    191             SkDebugf("Could not invert viewmatrix\n");
    192             return;
    193         }
    194 
    195         uint32_t flags = 0;
    196         flags |= this->viewMatrix().isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
    197 
    198         GrTextureParams params(SkShader::kRepeat_TileMode, GrTextureParams::kBilerp_FilterMode);
    199 
    200         // Setup GrGeometryProcessor
    201         GrBatchAtlas* atlas = fAtlas;
    202         SkAutoTUnref<GrGeometryProcessor> dfProcessor(
    203                 GrDistanceFieldPathGeoProc::Create(this->color(),
    204                                                    this->viewMatrix(),
    205                                                    atlas->getTexture(),
    206                                                    params,
    207                                                    flags,
    208                                                    this->usesLocalCoords()));
    209 
    210         target->initDraw(dfProcessor, this->pipeline());
    211 
    212         FlushInfo flushInfo;
    213 
    214         // allocate vertices
    215         size_t vertexStride = dfProcessor->getVertexStride();
    216         SkASSERT(vertexStride == 2 * sizeof(SkPoint) + sizeof(GrColor));
    217 
    218         const GrVertexBuffer* vertexBuffer;
    219         void* vertices = target->makeVertexSpace(vertexStride,
    220                                                  kVerticesPerQuad * instanceCount,
    221                                                  &vertexBuffer,
    222                                                  &flushInfo.fVertexOffset);
    223         flushInfo.fVertexBuffer.reset(SkRef(vertexBuffer));
    224         flushInfo.fIndexBuffer.reset(target->resourceProvider()->refQuadIndexBuffer());
    225         if (!vertices || !flushInfo.fIndexBuffer) {
    226             SkDebugf("Could not allocate vertices\n");
    227             return;
    228         }
    229 
    230         flushInfo.fInstancesToFlush = 0;
    231         for (int i = 0; i < instanceCount; i++) {
    232             const Geometry& args = fGeoData[i];
    233 
    234             // get mip level
    235             SkScalar maxScale = this->viewMatrix().getMaxScale();
    236             const SkRect& bounds = args.fPath.getBounds();
    237             SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
    238             SkScalar size = maxScale * maxDim;
    239             uint32_t desiredDimension;
    240             if (size <= kSmallMIP) {
    241                 desiredDimension = kSmallMIP;
    242             } else if (size <= kMediumMIP) {
    243                 desiredDimension = kMediumMIP;
    244             } else {
    245                 desiredDimension = kLargeMIP;
    246             }
    247 
    248             // check to see if path is cached
    249             PathData::Key key(args.fGenID, desiredDimension, args.fStroke);
    250             PathData* pathData = fPathCache->find(key);
    251             if (nullptr == pathData || !atlas->hasID(pathData->fID)) {
    252                 // Remove the stale cache entry
    253                 if (pathData) {
    254                     fPathCache->remove(pathData->fKey);
    255                     fPathList->remove(pathData);
    256                     delete pathData;
    257                 }
    258                 SkScalar scale = desiredDimension/maxDim;
    259                 pathData = new PathData;
    260                 if (!this->addPathToAtlas(target,
    261                                           dfProcessor,
    262                                           this->pipeline(),
    263                                           &flushInfo,
    264                                           atlas,
    265                                           pathData,
    266                                           args.fPath,
    267                                           args.fGenID,
    268                                           args.fStroke,
    269                                           args.fAntiAlias,
    270                                           desiredDimension,
    271                                           scale)) {
    272                     SkDebugf("Can't rasterize path\n");
    273                     return;
    274                 }
    275             }
    276 
    277             atlas->setLastUseToken(pathData->fID, target->currentToken());
    278 
    279             // Now set vertices
    280             intptr_t offset = reinterpret_cast<intptr_t>(vertices);
    281             offset += i * kVerticesPerQuad * vertexStride;
    282             this->writePathVertices(target,
    283                                     atlas,
    284                                     this->pipeline(),
    285                                     dfProcessor,
    286                                     offset,
    287                                     args.fColor,
    288                                     vertexStride,
    289                                     this->viewMatrix(),
    290                                     args.fPath,
    291                                     pathData);
    292             flushInfo.fInstancesToFlush++;
    293         }
    294 
    295         this->flush(target, &flushInfo);
    296     }
    297 
    298     SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; }
    299 
    300     AADistanceFieldPathBatch(const Geometry& geometry,
    301                              const SkMatrix& viewMatrix,
    302                              GrBatchAtlas* atlas,
    303                              PathCache* pathCache, PathDataList* pathList)
    304         : INHERITED(ClassID()) {
    305         fBatch.fViewMatrix = viewMatrix;
    306         fGeoData.push_back(geometry);
    307 
    308         fAtlas = atlas;
    309         fPathCache = pathCache;
    310         fPathList = pathList;
    311 
    312         // Compute bounds
    313         fBounds = geometry.fPath.getBounds();
    314         viewMatrix.mapRect(&fBounds);
    315     }
    316 
    317     bool addPathToAtlas(GrVertexBatch::Target* target,
    318                         const GrGeometryProcessor* dfProcessor,
    319                         const GrPipeline* pipeline,
    320                         FlushInfo* flushInfo,
    321                         GrBatchAtlas* atlas,
    322                         PathData* pathData,
    323                         const SkPath& path,
    324                         uint32_t genID,
    325                         const SkStrokeRec& stroke,
    326                         bool antiAlias,
    327                         uint32_t dimension,
    328                         SkScalar scale) const {
    329         const SkRect& bounds = path.getBounds();
    330 
    331         // generate bounding rect for bitmap draw
    332         SkRect scaledBounds = bounds;
    333         // scale to mip level size
    334         scaledBounds.fLeft *= scale;
    335         scaledBounds.fTop *= scale;
    336         scaledBounds.fRight *= scale;
    337         scaledBounds.fBottom *= scale;
    338         // move the origin to an integer boundary (gives better results)
    339         SkScalar dx = SkScalarFraction(scaledBounds.fLeft);
    340         SkScalar dy = SkScalarFraction(scaledBounds.fTop);
    341         scaledBounds.offset(-dx, -dy);
    342         // get integer boundary
    343         SkIRect devPathBounds;
    344         scaledBounds.roundOut(&devPathBounds);
    345         // pad to allow room for antialiasing
    346         const int intPad = SkScalarCeilToInt(kAntiAliasPad);
    347         // pre-move origin (after outset, will be 0,0)
    348         int width = devPathBounds.width();
    349         int height = devPathBounds.height();
    350         devPathBounds.fLeft = intPad;
    351         devPathBounds.fTop = intPad;
    352         devPathBounds.fRight = intPad + width;
    353         devPathBounds.fBottom = intPad + height;
    354         devPathBounds.outset(intPad, intPad);
    355 
    356         // draw path to bitmap
    357         SkMatrix drawMatrix;
    358         drawMatrix.setTranslate(-bounds.left(), -bounds.top());
    359         drawMatrix.postScale(scale, scale);
    360         drawMatrix.postTranslate(kAntiAliasPad, kAntiAliasPad);
    361 
    362         // setup bitmap backing
    363         SkASSERT(devPathBounds.fLeft == 0);
    364         SkASSERT(devPathBounds.fTop == 0);
    365         SkAutoPixmapStorage dst;
    366         if (!dst.tryAlloc(SkImageInfo::MakeA8(devPathBounds.width(),
    367                                               devPathBounds.height()))) {
    368             return false;
    369         }
    370         sk_bzero(dst.writable_addr(), dst.getSafeSize());
    371 
    372         // rasterize path
    373         SkPaint paint;
    374         paint.setStyle(SkPaint::kFill_Style);
    375         paint.setAntiAlias(antiAlias);
    376 
    377         SkDraw draw;
    378         sk_bzero(&draw, sizeof(draw));
    379 
    380         SkRasterClip rasterClip;
    381         rasterClip.setRect(devPathBounds);
    382         draw.fRC = &rasterClip;
    383         draw.fClip = &rasterClip.bwRgn();
    384         draw.fMatrix = &drawMatrix;
    385         draw.fDst = dst;
    386 
    387         draw.drawPathCoverage(path, paint);
    388 
    389         // generate signed distance field
    390         devPathBounds.outset(SK_DistanceFieldPad, SK_DistanceFieldPad);
    391         width = devPathBounds.width();
    392         height = devPathBounds.height();
    393         // TODO We should really generate this directly into the plot somehow
    394         SkAutoSMalloc<1024> dfStorage(width * height * sizeof(unsigned char));
    395 
    396         // Generate signed distance field
    397         SkGenerateDistanceFieldFromA8Image((unsigned char*)dfStorage.get(),
    398                                            (const unsigned char*)dst.addr(),
    399                                            dst.width(), dst.height(), dst.rowBytes());
    400 
    401         // add to atlas
    402         SkIPoint16 atlasLocation;
    403         GrBatchAtlas::AtlasID id;
    404         bool success = atlas->addToAtlas(&id, target, width, height, dfStorage.get(),
    405                                          &atlasLocation);
    406         if (!success) {
    407             this->flush(target, flushInfo);
    408             target->initDraw(dfProcessor, pipeline);
    409 
    410             SkDEBUGCODE(success =) atlas->addToAtlas(&id, target, width, height,
    411                                                      dfStorage.get(), &atlasLocation);
    412             SkASSERT(success);
    413 
    414         }
    415 
    416         // add to cache
    417         pathData->fKey = PathData::Key(genID, dimension, stroke);
    418         pathData->fScale = scale;
    419         pathData->fID = id;
    420         // change the scaled rect to match the size of the inset distance field
    421         scaledBounds.fRight = scaledBounds.fLeft +
    422             SkIntToScalar(devPathBounds.width() - 2*SK_DistanceFieldInset);
    423         scaledBounds.fBottom = scaledBounds.fTop +
    424             SkIntToScalar(devPathBounds.height() - 2*SK_DistanceFieldInset);
    425         // shift the origin to the correct place relative to the distance field
    426         // need to also restore the fractional translation
    427         scaledBounds.offset(-SkIntToScalar(SK_DistanceFieldInset) - kAntiAliasPad + dx,
    428                             -SkIntToScalar(SK_DistanceFieldInset) - kAntiAliasPad + dy);
    429         pathData->fBounds = scaledBounds;
    430         // origin we render from is inset from distance field edge
    431         atlasLocation.fX += SK_DistanceFieldInset;
    432         atlasLocation.fY += SK_DistanceFieldInset;
    433         pathData->fAtlasLocation = atlasLocation;
    434 
    435         fPathCache->add(pathData);
    436         fPathList->addToTail(pathData);
    437 #ifdef DF_PATH_TRACKING
    438         ++g_NumCachedPaths;
    439 #endif
    440         return true;
    441     }
    442 
    443     void writePathVertices(GrDrawBatch::Target* target,
    444                            GrBatchAtlas* atlas,
    445                            const GrPipeline* pipeline,
    446                            const GrGeometryProcessor* gp,
    447                            intptr_t offset,
    448                            GrColor color,
    449                            size_t vertexStride,
    450                            const SkMatrix& viewMatrix,
    451                            const SkPath& path,
    452                            const PathData* pathData) const {
    453         GrTexture* texture = atlas->getTexture();
    454 
    455         SkScalar dx = pathData->fBounds.fLeft;
    456         SkScalar dy = pathData->fBounds.fTop;
    457         SkScalar width = pathData->fBounds.width();
    458         SkScalar height = pathData->fBounds.height();
    459 
    460         SkScalar invScale = 1.0f / pathData->fScale;
    461         dx *= invScale;
    462         dy *= invScale;
    463         width *= invScale;
    464         height *= invScale;
    465 
    466         SkPoint* positions = reinterpret_cast<SkPoint*>(offset);
    467 
    468         // vertex positions
    469         // TODO make the vertex attributes a struct
    470         SkRect r = SkRect::MakeXYWH(dx, dy, width, height);
    471         positions->setRectFan(r.left(), r.top(), r.right(), r.bottom(), vertexStride);
    472 
    473         // colors
    474         for (int i = 0; i < kVerticesPerQuad; i++) {
    475             GrColor* colorPtr = (GrColor*)(offset + sizeof(SkPoint) + i * vertexStride);
    476             *colorPtr = color;
    477         }
    478 
    479         const SkScalar tx = SkIntToScalar(pathData->fAtlasLocation.fX);
    480         const SkScalar ty = SkIntToScalar(pathData->fAtlasLocation.fY);
    481 
    482         // vertex texture coords
    483         SkPoint* textureCoords = (SkPoint*)(offset + sizeof(SkPoint) + sizeof(GrColor));
    484         textureCoords->setRectFan(tx / texture->width(),
    485                                   ty / texture->height(),
    486                                   (tx + pathData->fBounds.width()) / texture->width(),
    487                                   (ty + pathData->fBounds.height())  / texture->height(),
    488                                   vertexStride);
    489     }
    490 
    491     void flush(GrVertexBatch::Target* target, FlushInfo* flushInfo) const {
    492         GrVertices vertices;
    493         int maxInstancesPerDraw = flushInfo->fIndexBuffer->maxQuads();
    494         vertices.initInstanced(kTriangles_GrPrimitiveType, flushInfo->fVertexBuffer,
    495             flushInfo->fIndexBuffer, flushInfo->fVertexOffset, kVerticesPerQuad,
    496             kIndicesPerQuad, flushInfo->fInstancesToFlush, maxInstancesPerDraw);
    497         target->draw(vertices);
    498         flushInfo->fVertexOffset += kVerticesPerQuad * flushInfo->fInstancesToFlush;
    499         flushInfo->fInstancesToFlush = 0;
    500     }
    501 
    502     GrColor color() const { return fGeoData[0].fColor; }
    503     const SkMatrix& viewMatrix() const { return fBatch.fViewMatrix; }
    504     bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; }
    505 
    506     bool onCombineIfPossible(GrBatch* t, const GrCaps& caps) override {
    507         AADistanceFieldPathBatch* that = t->cast<AADistanceFieldPathBatch>();
    508         if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pipeline(),
    509                                     that->bounds(), caps)) {
    510             return false;
    511         }
    512 
    513         // TODO We can position on the cpu
    514         if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
    515             return false;
    516         }
    517 
    518         fGeoData.push_back_n(that->geoData()->count(), that->geoData()->begin());
    519         this->joinBounds(that->bounds());
    520         return true;
    521     }
    522 
    523     struct BatchTracker {
    524         SkMatrix fViewMatrix;
    525         bool fUsesLocalCoords;
    526         bool fColorIgnored;
    527         bool fCoverageIgnored;
    528     };
    529 
    530     BatchTracker fBatch;
    531     SkSTArray<1, Geometry, true> fGeoData;
    532     GrBatchAtlas* fAtlas;
    533     PathCache* fPathCache;
    534     PathDataList* fPathList;
    535 
    536     typedef GrVertexBatch INHERITED;
    537 };
    538 
    539 bool GrAADistanceFieldPathRenderer::onDrawPath(const DrawPathArgs& args) {
    540     GR_AUDIT_TRAIL_AUTO_FRAME(args.fTarget->getAuditTrail(),
    541                               "GrAADistanceFieldPathRenderer::onDrawPath");
    542     // we've already bailed on inverse filled paths, so this is safe
    543     if (args.fPath->isEmpty()) {
    544         return true;
    545     }
    546 
    547     if (!fAtlas) {
    548         fAtlas = args.fResourceProvider->createAtlas(kAlpha_8_GrPixelConfig,
    549                                                      ATLAS_TEXTURE_WIDTH, ATLAS_TEXTURE_HEIGHT,
    550                                                      NUM_PLOTS_X, NUM_PLOTS_Y,
    551                                                      &GrAADistanceFieldPathRenderer::HandleEviction,
    552                                                      (void*)this);
    553         if (!fAtlas) {
    554             return false;
    555         }
    556     }
    557 
    558     AADistanceFieldPathBatch::Geometry geometry(*args.fStroke);
    559     if (SkStrokeRec::kFill_Style == args.fStroke->getStyle()) {
    560         geometry.fPath = *args.fPath;
    561     } else {
    562         args.fStroke->applyToPath(&geometry.fPath, *args.fPath);
    563     }
    564     geometry.fColor = args.fColor;
    565     geometry.fAntiAlias = args.fAntiAlias;
    566     // Note: this is the generation ID of the _original_ path. When a new path is
    567     // generated due to stroking it is important that the original path's id is used
    568     // for caching.
    569     geometry.fGenID = args.fPath->getGenerationID();
    570 
    571     SkAutoTUnref<GrDrawBatch> batch(AADistanceFieldPathBatch::Create(geometry,
    572                                                                      *args.fViewMatrix, fAtlas,
    573                                                                      &fPathCache, &fPathList));
    574     args.fTarget->drawBatch(*args.fPipelineBuilder, batch);
    575 
    576     return true;
    577 }
    578 
    579 ///////////////////////////////////////////////////////////////////////////////////////////////////
    580 
    581 #ifdef GR_TEST_UTILS
    582 
    583 struct PathTestStruct {
    584     typedef GrAADistanceFieldPathRenderer::PathCache PathCache;
    585     typedef GrAADistanceFieldPathRenderer::PathData PathData;
    586     typedef GrAADistanceFieldPathRenderer::PathDataList PathDataList;
    587     PathTestStruct() : fContextID(SK_InvalidGenID), fAtlas(nullptr) {}
    588     ~PathTestStruct() { this->reset(); }
    589 
    590     void reset() {
    591         PathDataList::Iter iter;
    592         iter.init(fPathList, PathDataList::Iter::kHead_IterStart);
    593         PathData* pathData;
    594         while ((pathData = iter.get())) {
    595             iter.next();
    596             fPathList.remove(pathData);
    597             delete pathData;
    598         }
    599         delete fAtlas;
    600         fPathCache.reset();
    601     }
    602 
    603     static void HandleEviction(GrBatchAtlas::AtlasID id, void* pr) {
    604         PathTestStruct* dfpr = (PathTestStruct*)pr;
    605         // remove any paths that use this plot
    606         PathDataList::Iter iter;
    607         iter.init(dfpr->fPathList, PathDataList::Iter::kHead_IterStart);
    608         PathData* pathData;
    609         while ((pathData = iter.get())) {
    610             iter.next();
    611             if (id == pathData->fID) {
    612                 dfpr->fPathCache.remove(pathData->fKey);
    613                 dfpr->fPathList.remove(pathData);
    614                 delete pathData;
    615             }
    616         }
    617     }
    618 
    619     uint32_t fContextID;
    620     GrBatchAtlas* fAtlas;
    621     PathCache fPathCache;
    622     PathDataList fPathList;
    623 };
    624 
    625 DRAW_BATCH_TEST_DEFINE(AADistanceFieldPathBatch) {
    626     static PathTestStruct gTestStruct;
    627 
    628     if (context->uniqueID() != gTestStruct.fContextID) {
    629         gTestStruct.fContextID = context->uniqueID();
    630         gTestStruct.reset();
    631         gTestStruct.fAtlas =
    632                 context->resourceProvider()->createAtlas(kAlpha_8_GrPixelConfig,
    633                                                      ATLAS_TEXTURE_WIDTH, ATLAS_TEXTURE_HEIGHT,
    634                                                      NUM_PLOTS_X, NUM_PLOTS_Y,
    635                                                      &PathTestStruct::HandleEviction,
    636                                                      (void*)&gTestStruct);
    637     }
    638 
    639     SkMatrix viewMatrix = GrTest::TestMatrix(random);
    640     GrColor color = GrRandomColor(random);
    641 
    642     AADistanceFieldPathBatch::Geometry geometry(GrTest::TestStrokeRec(random));
    643     geometry.fColor = color;
    644     geometry.fPath = GrTest::TestPath(random);
    645     geometry.fAntiAlias = random->nextBool();
    646     geometry.fGenID = random->nextU();
    647 
    648     return AADistanceFieldPathBatch::Create(geometry, viewMatrix,
    649                                             gTestStruct.fAtlas,
    650                                             &gTestStruct.fPathCache,
    651                                             &gTestStruct.fPathList);
    652 }
    653 
    654 #endif
    655