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
35static int g_NumCachedPaths = 0;
36static int g_NumFreedPaths = 0;
37#endif
38
39// mip levels
40static const int kSmallMIP = 32;
41static const int kMediumMIP = 73;
42static const int kLargeMIP = 162;
43
44// Callback to clear out internal path cache when eviction occurs
45void 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////////////////////////////////////////////////////////////////////////////////
65GrAADistanceFieldPathRenderer::GrAADistanceFieldPathRenderer() : fAtlas(nullptr) {}
66
67GrAADistanceFieldPathRenderer::~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////////////////////////////////////////////////////////////////////////////////
84bool 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
121static const SkScalar kAntiAliasPad = 1.0f;
122
123class AADistanceFieldPathBatch : public GrVertexBatch {
124public:
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
165private:
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
539bool 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
583struct 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
625DRAW_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