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