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