GrAADistanceFieldPathRenderer.cpp revision ed0bcad9c8147fd37c23bdda00ec27ec9ef8d66b
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 "GrBatch.h"
12#include "GrBatchTarget.h"
13#include "GrBufferAllocPool.h"
14#include "GrContext.h"
15#include "GrPipelineBuilder.h"
16#include "GrSurfacePriv.h"
17#include "GrSWMaskHelper.h"
18#include "GrResourceProvider.h"
19#include "GrTexturePriv.h"
20#include "GrVertexBuffer.h"
21#include "effects/GrDistanceFieldGeoProc.h"
22
23#include "SkDistanceFieldGen.h"
24#include "SkRTConf.h"
25
26#define ATLAS_TEXTURE_WIDTH 1024
27#define ATLAS_TEXTURE_HEIGHT 2048
28#define PLOT_WIDTH  256
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 = 78;
42static const int kLargeMIP = 192;
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            SkDELETE(pathData);
57#ifdef DF_PATH_TRACKING
58            ++g_NumFreedPaths;
59#endif
60        }
61    }
62}
63
64////////////////////////////////////////////////////////////////////////////////
65GrAADistanceFieldPathRenderer::GrAADistanceFieldPathRenderer(GrContext* context)
66    : fContext(context)
67    , fAtlas(NULL) {
68}
69
70GrAADistanceFieldPathRenderer::~GrAADistanceFieldPathRenderer() {
71    PathDataList::Iter iter;
72    iter.init(fPathList, PathDataList::Iter::kHead_IterStart);
73    PathData* pathData;
74    while ((pathData = iter.get())) {
75        iter.next();
76        fPathList.remove(pathData);
77        SkDELETE(pathData);
78    }
79    SkDELETE(fAtlas);
80
81#ifdef DF_PATH_TRACKING
82    SkDebugf("Cached paths: %d, freed paths: %d\n", g_NumCachedPaths, g_NumFreedPaths);
83#endif
84}
85
86////////////////////////////////////////////////////////////////////////////////
87bool GrAADistanceFieldPathRenderer::canDrawPath(const GrDrawTarget* target,
88                                                const GrPipelineBuilder* pipelineBuilder,
89                                                const SkMatrix& viewMatrix,
90                                                const SkPath& path,
91                                                const GrStrokeInfo& stroke,
92                                                bool antiAlias) const {
93
94    // TODO: Support inverse fill
95    // TODO: Support strokes
96    if (!target->caps()->shaderCaps()->shaderDerivativeSupport() || !antiAlias
97        || path.isInverseFillType() || path.isVolatile() || !stroke.isFillStyle()) {
98        return false;
99    }
100
101    // currently don't support perspective
102    if (viewMatrix.hasPerspective()) {
103        return false;
104    }
105
106    // only support paths smaller than 64x64, scaled to less than 256x256
107    // the goal is to accelerate rendering of lots of small paths that may be scaling
108    SkScalar maxScale = viewMatrix.getMaxScale();
109    const SkRect& bounds = path.getBounds();
110    SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
111    return maxDim < 64.f && maxDim * maxScale < 256.f;
112}
113
114
115GrPathRenderer::StencilSupport
116GrAADistanceFieldPathRenderer::onGetStencilSupport(const GrDrawTarget*,
117                                                   const GrPipelineBuilder*,
118                                                   const SkPath&,
119                                                   const GrStrokeInfo&) const {
120    return GrPathRenderer::kNoSupport_StencilSupport;
121}
122
123////////////////////////////////////////////////////////////////////////////////
124
125// padding around path bounds to allow for antialiased pixels
126static const SkScalar kAntiAliasPad = 1.0f;
127
128class AADistanceFieldPathBatch : public GrBatch {
129public:
130    typedef GrAADistanceFieldPathRenderer::PathData PathData;
131    typedef SkTDynamicHash<PathData, PathData::Key> PathCache;
132    typedef GrAADistanceFieldPathRenderer::PathDataList PathDataList;
133
134    struct Geometry {
135        Geometry(const SkStrokeRec& stroke) : fStroke(stroke) {}
136        SkPath fPath;
137        SkStrokeRec fStroke;
138        bool fAntiAlias;
139        PathData* fPathData;
140    };
141
142    static GrBatch* Create(const Geometry& geometry, GrColor color, const SkMatrix& viewMatrix,
143                           GrBatchAtlas* atlas, PathCache* pathCache, PathDataList* pathList) {
144        return SkNEW_ARGS(AADistanceFieldPathBatch, (geometry, color, viewMatrix,
145                                                     atlas, pathCache, pathList));
146    }
147
148    const char* name() const override { return "AADistanceFieldPathBatch"; }
149
150    void getInvariantOutputColor(GrInitInvariantOutput* out) const override {
151        out->setKnownFourComponents(fBatch.fColor);
152    }
153
154    void getInvariantOutputCoverage(GrInitInvariantOutput* out) const override {
155        out->setUnknownSingleComponent();
156    }
157
158    void initBatchTracker(const GrPipelineInfo& init) override {
159        // Handle any color overrides
160        if (init.fColorIgnored) {
161            fBatch.fColor = GrColor_ILLEGAL;
162        } else if (GrColor_ILLEGAL != init.fOverrideColor) {
163            fBatch.fColor = init.fOverrideColor;
164        }
165
166        // setup batch properties
167        fBatch.fColorIgnored = init.fColorIgnored;
168        fBatch.fUsesLocalCoords = init.fUsesLocalCoords;
169        fBatch.fCoverageIgnored = init.fCoverageIgnored;
170    }
171
172    void generateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) override {
173        int instanceCount = fGeoData.count();
174
175        SkMatrix invert;
176        if (this->usesLocalCoords() && !this->viewMatrix().invert(&invert)) {
177            SkDebugf("Could not invert viewmatrix\n");
178            return;
179        }
180
181        uint32_t flags = 0;
182        flags |= this->viewMatrix().isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
183
184        GrTextureParams params(SkShader::kRepeat_TileMode, GrTextureParams::kBilerp_FilterMode);
185
186        // Setup GrGeometryProcessor
187        GrBatchAtlas* atlas = fAtlas;
188        SkAutoTUnref<GrGeometryProcessor> dfProcessor(
189                GrDistanceFieldPathGeoProc::Create(this->color(),
190                                                   this->viewMatrix(),
191                                                   atlas->getTexture(),
192                                                   params,
193                                                   flags,
194                                                   false));
195
196        this->initDraw(batchTarget, dfProcessor, pipeline);
197
198        static const int kVertsPerQuad = 4;
199        static const int kIndicesPerQuad = 6;
200
201        SkAutoTUnref<const GrIndexBuffer> indexBuffer(
202            batchTarget->resourceProvider()->refQuadIndexBuffer());
203
204        // allocate vertices
205        size_t vertexStride = dfProcessor->getVertexStride();
206        SkASSERT(vertexStride == 2 * sizeof(SkPoint));
207        const GrVertexBuffer* vertexBuffer;
208        int vertexCount = kVertsPerQuad * instanceCount;
209        int firstVertex;
210
211        void* vertices = batchTarget->vertexPool()->makeSpace(vertexStride,
212                                                              vertexCount,
213                                                              &vertexBuffer,
214                                                              &firstVertex);
215
216        if (!vertices || !indexBuffer) {
217            SkDebugf("Could not allocate vertices\n");
218            return;
219        }
220
221        // We may have to flush while uploading path data to the atlas, so we set up the draw here
222        int maxInstancesPerDraw = indexBuffer->maxQuads();
223
224        GrDrawTarget::DrawInfo drawInfo;
225        drawInfo.setPrimitiveType(kTriangles_GrPrimitiveType);
226        drawInfo.setStartVertex(0);
227        drawInfo.setStartIndex(0);
228        drawInfo.setVerticesPerInstance(kVertsPerQuad);
229        drawInfo.setIndicesPerInstance(kIndicesPerQuad);
230        drawInfo.adjustStartVertex(firstVertex);
231        drawInfo.setVertexBuffer(vertexBuffer);
232        drawInfo.setIndexBuffer(indexBuffer);
233
234        int instancesToFlush = 0;
235        for (int i = 0; i < instanceCount; i++) {
236            Geometry& args = fGeoData[i];
237
238            // get mip level
239            SkScalar maxScale = this->viewMatrix().getMaxScale();
240            const SkRect& bounds = args.fPath.getBounds();
241            SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
242            SkScalar size = maxScale * maxDim;
243            uint32_t desiredDimension;
244            if (size <= kSmallMIP) {
245                desiredDimension = kSmallMIP;
246            } else if (size <= kMediumMIP) {
247                desiredDimension = kMediumMIP;
248            } else {
249                desiredDimension = kLargeMIP;
250            }
251
252            // check to see if path is cached
253            // TODO: handle stroked vs. filled version of same path
254            PathData::Key key = { args.fPath.getGenerationID(), desiredDimension };
255            args.fPathData = fPathCache->find(key);
256            if (NULL == args.fPathData || !atlas->hasID(args.fPathData->fID)) {
257                // Remove the stale cache entry
258                if (args.fPathData) {
259                    fPathCache->remove(args.fPathData->fKey);
260                    fPathList->remove(args.fPathData);
261                    SkDELETE(args.fPathData);
262                }
263                SkScalar scale = desiredDimension/maxDim;
264                args.fPathData = SkNEW(PathData);
265                if (!this->addPathToAtlas(batchTarget,
266                                          dfProcessor,
267                                          pipeline,
268                                          &drawInfo,
269                                          &instancesToFlush,
270                                          maxInstancesPerDraw,
271                                          atlas,
272                                          args.fPathData,
273                                          args.fPath,
274                                          args.fStroke,
275                                          args.fAntiAlias,
276                                          desiredDimension,
277                                          scale)) {
278                    SkDebugf("Can't rasterize path\n");
279                    return;
280                }
281            }
282
283            atlas->setLastUseToken(args.fPathData->fID, batchTarget->currentToken());
284
285            // Now set vertices
286            intptr_t offset = reinterpret_cast<intptr_t>(vertices);
287            offset += i * kVertsPerQuad * vertexStride;
288            SkPoint* positions = reinterpret_cast<SkPoint*>(offset);
289            this->drawPath(batchTarget,
290                           atlas,
291                           pipeline,
292                           dfProcessor,
293                           positions,
294                           vertexStride,
295                           this->viewMatrix(),
296                           args.fPath,
297                           args.fPathData);
298            instancesToFlush++;
299        }
300
301        this->flush(batchTarget, &drawInfo, instancesToFlush, maxInstancesPerDraw);
302    }
303
304    SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; }
305
306private:
307    AADistanceFieldPathBatch(const Geometry& geometry, GrColor color, const SkMatrix& viewMatrix,
308                             GrBatchAtlas* atlas,
309                             PathCache* pathCache, PathDataList* pathList) {
310        this->initClassID<AADistanceFieldPathBatch>();
311        fBatch.fColor = color;
312        fBatch.fViewMatrix = viewMatrix;
313        fGeoData.push_back(geometry);
314        fGeoData.back().fPathData = NULL;
315
316        fAtlas = atlas;
317        fPathCache = pathCache;
318        fPathList = pathList;
319
320        // Compute bounds
321        fBounds = geometry.fPath.getBounds();
322        viewMatrix.mapRect(&fBounds);
323    }
324
325    bool addPathToAtlas(GrBatchTarget* batchTarget,
326                        const GrGeometryProcessor* dfProcessor,
327                        const GrPipeline* pipeline,
328                        GrDrawTarget::DrawInfo* drawInfo,
329                        int* instancesToFlush,
330                        int maxInstancesPerDraw,
331                        GrBatchAtlas* atlas,
332                        PathData* pathData,
333                        const SkPath& path,
334                        const SkStrokeRec&
335                        stroke, bool antiAlias,
336                        uint32_t dimension,
337                        SkScalar scale) {
338        const SkRect& bounds = path.getBounds();
339
340        // generate bounding rect for bitmap draw
341        SkRect scaledBounds = bounds;
342        // scale to mip level size
343        scaledBounds.fLeft *= scale;
344        scaledBounds.fTop *= scale;
345        scaledBounds.fRight *= scale;
346        scaledBounds.fBottom *= scale;
347        // move the origin to an integer boundary (gives better results)
348        SkScalar dx = SkScalarFraction(scaledBounds.fLeft);
349        SkScalar dy = SkScalarFraction(scaledBounds.fTop);
350        scaledBounds.offset(-dx, -dy);
351        // get integer boundary
352        SkIRect devPathBounds;
353        scaledBounds.roundOut(&devPathBounds);
354        // pad to allow room for antialiasing
355        devPathBounds.outset(SkScalarCeilToInt(kAntiAliasPad), SkScalarCeilToInt(kAntiAliasPad));
356        // move origin to upper left corner
357        devPathBounds.offsetTo(0,0);
358
359        // draw path to bitmap
360        SkMatrix drawMatrix;
361        drawMatrix.setTranslate(-bounds.left(), -bounds.top());
362        drawMatrix.postScale(scale, scale);
363        drawMatrix.postTranslate(kAntiAliasPad, kAntiAliasPad);
364
365        // setup bitmap backing
366        // Now translate so the bound's UL corner is at the origin
367        drawMatrix.postTranslate(-devPathBounds.fLeft * SK_Scalar1,
368                                 -devPathBounds.fTop * SK_Scalar1);
369        SkIRect pathBounds = SkIRect::MakeWH(devPathBounds.width(),
370                                             devPathBounds.height());
371
372        SkBitmap bmp;
373        const SkImageInfo bmImageInfo = SkImageInfo::MakeA8(pathBounds.fRight,
374                                                            pathBounds.fBottom);
375        if (!bmp.tryAllocPixels(bmImageInfo)) {
376            return false;
377        }
378
379        sk_bzero(bmp.getPixels(), bmp.getSafeSize());
380
381        // rasterize path
382        SkPaint paint;
383        if (stroke.isHairlineStyle()) {
384            paint.setStyle(SkPaint::kStroke_Style);
385            paint.setStrokeWidth(SK_Scalar1);
386        } else {
387            if (stroke.isFillStyle()) {
388                paint.setStyle(SkPaint::kFill_Style);
389            } else {
390                paint.setStyle(SkPaint::kStroke_Style);
391                paint.setStrokeJoin(stroke.getJoin());
392                paint.setStrokeCap(stroke.getCap());
393                paint.setStrokeWidth(stroke.getWidth());
394            }
395        }
396        paint.setAntiAlias(antiAlias);
397
398        SkDraw draw;
399        sk_bzero(&draw, sizeof(draw));
400
401        SkRasterClip rasterClip;
402        rasterClip.setRect(pathBounds);
403        draw.fRC = &rasterClip;
404        draw.fClip = &rasterClip.bwRgn();
405        draw.fMatrix = &drawMatrix;
406        draw.fBitmap = &bmp;
407
408        draw.drawPathCoverage(path, paint);
409
410        // generate signed distance field
411        devPathBounds.outset(SK_DistanceFieldPad, SK_DistanceFieldPad);
412        int width = devPathBounds.width();
413        int height = devPathBounds.height();
414        // TODO We should really generate this directly into the plot somehow
415        SkAutoSMalloc<1024> dfStorage(width * height * sizeof(unsigned char));
416
417        // Generate signed distance field
418        {
419            SkAutoLockPixels alp(bmp);
420
421            SkGenerateDistanceFieldFromA8Image((unsigned char*)dfStorage.get(),
422                                               (const unsigned char*)bmp.getPixels(),
423                                               bmp.width(), bmp.height(), bmp.rowBytes());
424        }
425
426        // add to atlas
427        SkIPoint16 atlasLocation;
428        GrBatchAtlas::AtlasID id;
429        bool success = atlas->addToAtlas(&id, batchTarget, width, height, dfStorage.get(),
430                                         &atlasLocation);
431        if (!success) {
432            this->flush(batchTarget, drawInfo, *instancesToFlush, maxInstancesPerDraw);
433            this->initDraw(batchTarget, dfProcessor, pipeline);
434            *instancesToFlush = 0;
435
436            SkDEBUGCODE(success =) atlas->addToAtlas(&id, batchTarget, width, height,
437                                                     dfStorage.get(), &atlasLocation);
438            SkASSERT(success);
439
440        }
441
442        // add to cache
443        pathData->fKey.fGenID = path.getGenerationID();
444        pathData->fKey.fDimension = dimension;
445        pathData->fScale = scale;
446        pathData->fID = id;
447        // change the scaled rect to match the size of the inset distance field
448        scaledBounds.fRight = scaledBounds.fLeft +
449            SkIntToScalar(devPathBounds.width() - 2*SK_DistanceFieldInset);
450        scaledBounds.fBottom = scaledBounds.fTop +
451            SkIntToScalar(devPathBounds.height() - 2*SK_DistanceFieldInset);
452        // shift the origin to the correct place relative to the distance field
453        // need to also restore the fractional translation
454        scaledBounds.offset(-SkIntToScalar(SK_DistanceFieldInset) - kAntiAliasPad + dx,
455                            -SkIntToScalar(SK_DistanceFieldInset) - kAntiAliasPad + dy);
456        pathData->fBounds = scaledBounds;
457        // origin we render from is inset from distance field edge
458        atlasLocation.fX += SK_DistanceFieldInset;
459        atlasLocation.fY += SK_DistanceFieldInset;
460        pathData->fAtlasLocation = atlasLocation;
461
462        fPathCache->add(pathData);
463        fPathList->addToTail(pathData);
464#ifdef DF_PATH_TRACKING
465        ++g_NumCachedPaths;
466#endif
467        return true;
468    }
469
470    void drawPath(GrBatchTarget* target,
471                  GrBatchAtlas* atlas,
472                  const GrPipeline* pipeline,
473                  const GrGeometryProcessor* gp,
474                  SkPoint* positions,
475                  size_t vertexStride,
476                  const SkMatrix& viewMatrix,
477                  const SkPath& path,
478                  const PathData* pathData) {
479        GrTexture* texture = atlas->getTexture();
480
481        SkScalar dx = pathData->fBounds.fLeft;
482        SkScalar dy = pathData->fBounds.fTop;
483        SkScalar width = pathData->fBounds.width();
484        SkScalar height = pathData->fBounds.height();
485
486        SkScalar invScale = 1.0f / pathData->fScale;
487        dx *= invScale;
488        dy *= invScale;
489        width *= invScale;
490        height *= invScale;
491
492        SkFixed tx = SkIntToFixed(pathData->fAtlasLocation.fX);
493        SkFixed ty = SkIntToFixed(pathData->fAtlasLocation.fY);
494        SkFixed tw = SkScalarToFixed(pathData->fBounds.width());
495        SkFixed th = SkScalarToFixed(pathData->fBounds.height());
496
497        // vertex positions
498        // TODO make the vertex attributes a struct
499        SkRect r = SkRect::MakeXYWH(dx, dy, width, height);
500        positions->setRectFan(r.left(), r.top(), r.right(), r.bottom(), vertexStride);
501
502        // vertex texture coords
503        SkPoint* textureCoords = positions + 1;
504        textureCoords->setRectFan(SkFixedToFloat(texture->texturePriv().normalizeFixedX(tx)),
505                                  SkFixedToFloat(texture->texturePriv().normalizeFixedY(ty)),
506                                  SkFixedToFloat(texture->texturePriv().normalizeFixedX(tx + tw)),
507                                  SkFixedToFloat(texture->texturePriv().normalizeFixedY(ty + th)),
508                                  vertexStride);
509    }
510
511    void initDraw(GrBatchTarget* batchTarget,
512                  const GrGeometryProcessor* dfProcessor,
513                  const GrPipeline* pipeline) {
514        batchTarget->initDraw(dfProcessor, pipeline);
515
516        // TODO remove this when batch is everywhere
517        GrPipelineInfo init;
518        init.fColorIgnored = fBatch.fColorIgnored;
519        init.fOverrideColor = GrColor_ILLEGAL;
520        init.fCoverageIgnored = fBatch.fCoverageIgnored;
521        init.fUsesLocalCoords = this->usesLocalCoords();
522        dfProcessor->initBatchTracker(batchTarget->currentBatchTracker(), init);
523    }
524
525    void flush(GrBatchTarget* batchTarget,
526               GrDrawTarget::DrawInfo* drawInfo,
527               int instanceCount,
528               int maxInstancesPerDraw) {
529        while (instanceCount) {
530            drawInfo->setInstanceCount(SkTMin(instanceCount, maxInstancesPerDraw));
531            drawInfo->setVertexCount(drawInfo->instanceCount() * drawInfo->verticesPerInstance());
532            drawInfo->setIndexCount(drawInfo->instanceCount() * drawInfo->indicesPerInstance());
533
534            batchTarget->draw(*drawInfo);
535
536            drawInfo->setStartVertex(drawInfo->startVertex() + drawInfo->vertexCount());
537            instanceCount -= drawInfo->instanceCount();
538       }
539    }
540
541    GrColor color() const { return fBatch.fColor; }
542    const SkMatrix& viewMatrix() const { return fBatch.fViewMatrix; }
543    bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; }
544
545    bool onCombineIfPossible(GrBatch* t) override {
546        AADistanceFieldPathBatch* that = t->cast<AADistanceFieldPathBatch>();
547
548        // TODO we could actually probably do a bunch of this work on the CPU, ie map viewMatrix,
549        // maybe upload color via attribute
550        if (this->color() != that->color()) {
551            return false;
552        }
553
554        if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
555            return false;
556        }
557
558        fGeoData.push_back_n(that->geoData()->count(), that->geoData()->begin());
559        this->joinBounds(that->bounds());
560        return true;
561    }
562
563    struct BatchTracker {
564        GrColor fColor;
565        SkMatrix fViewMatrix;
566        bool fUsesLocalCoords;
567        bool fColorIgnored;
568        bool fCoverageIgnored;
569    };
570
571    BatchTracker fBatch;
572    SkSTArray<1, Geometry, true> fGeoData;
573    GrBatchAtlas* fAtlas;
574    PathCache* fPathCache;
575    PathDataList* fPathList;
576};
577
578bool GrAADistanceFieldPathRenderer::onDrawPath(GrDrawTarget* target,
579                                               GrPipelineBuilder* pipelineBuilder,
580                                               GrColor color,
581                                               const SkMatrix& viewMatrix,
582                                               const SkPath& path,
583                                               const GrStrokeInfo& stroke,
584                                               bool antiAlias) {
585    // we've already bailed on inverse filled paths, so this is safe
586    if (path.isEmpty()) {
587        return true;
588    }
589
590    SkASSERT(fContext);
591
592    if (!fAtlas) {
593        // Create a new atlas
594        GrSurfaceDesc desc;
595        desc.fFlags = kNone_GrSurfaceFlags;
596        desc.fWidth = ATLAS_TEXTURE_WIDTH;
597        desc.fHeight = ATLAS_TEXTURE_HEIGHT;
598        desc.fConfig = kAlpha_8_GrPixelConfig;
599
600        // We don't want to flush the context so we claim we're in the middle of flushing so as to
601        // guarantee we do not recieve a texture with pending IO
602        GrTexture* texture = fContext->textureProvider()->refScratchTexture(
603            desc, GrTextureProvider::kApprox_ScratchTexMatch, true);
604        if (texture) {
605            fAtlas = SkNEW_ARGS(GrBatchAtlas, (texture, NUM_PLOTS_X, NUM_PLOTS_Y));
606        } else {
607            return false;
608        }
609        fAtlas->registerEvictionCallback(&GrAADistanceFieldPathRenderer::HandleEviction,
610                                         (void*)this);
611    }
612
613    AADistanceFieldPathBatch::Geometry geometry(stroke.getStrokeRec());
614    geometry.fPath = path;
615    geometry.fAntiAlias = antiAlias;
616
617    SkAutoTUnref<GrBatch> batch(AADistanceFieldPathBatch::Create(geometry, color, viewMatrix,
618                                                                 fAtlas, &fPathCache, &fPathList));
619    target->drawBatch(pipelineBuilder, batch);
620
621    return true;
622}
623
624