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 "GrBatchTest.h" 14#include "GrContext.h" 15#include "GrPipelineBuilder.h" 16#include "GrResourceProvider.h" 17#include "GrSurfacePriv.h" 18#include "GrSWMaskHelper.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 struct FlushInfo { 173 SkAutoTUnref<const GrVertexBuffer> fVertexBuffer; 174 SkAutoTUnref<const GrIndexBuffer> fIndexBuffer; 175 int fVertexOffset; 176 int fInstancesToFlush; 177 }; 178 179 void generateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) override { 180 int instanceCount = fGeoData.count(); 181 182 SkMatrix invert; 183 if (this->usesLocalCoords() && !this->viewMatrix().invert(&invert)) { 184 SkDebugf("Could not invert viewmatrix\n"); 185 return; 186 } 187 188 uint32_t flags = 0; 189 flags |= this->viewMatrix().isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0; 190 191 GrTextureParams params(SkShader::kRepeat_TileMode, GrTextureParams::kBilerp_FilterMode); 192 193 // Setup GrGeometryProcessor 194 GrBatchAtlas* atlas = fAtlas; 195 SkAutoTUnref<GrGeometryProcessor> dfProcessor( 196 GrDistanceFieldPathGeoProc::Create(this->color(), 197 this->viewMatrix(), 198 atlas->getTexture(), 199 params, 200 flags)); 201 202 this->initDraw(batchTarget, dfProcessor, pipeline); 203 204 FlushInfo flushInfo; 205 206 // allocate vertices 207 size_t vertexStride = dfProcessor->getVertexStride(); 208 SkASSERT(vertexStride == 2 * sizeof(SkPoint)); 209 210 const GrVertexBuffer* vertexBuffer; 211 void* vertices = batchTarget->makeVertSpace(vertexStride, 212 kVerticesPerQuad * instanceCount, 213 &vertexBuffer, 214 &flushInfo.fVertexOffset); 215 flushInfo.fVertexBuffer.reset(SkRef(vertexBuffer)); 216 flushInfo.fIndexBuffer.reset(batchTarget->resourceProvider()->refQuadIndexBuffer()); 217 if (!vertices || !flushInfo.fIndexBuffer) { 218 SkDebugf("Could not allocate vertices\n"); 219 return; 220 } 221 222 flushInfo.fInstancesToFlush = 0; 223 for (int i = 0; i < instanceCount; i++) { 224 Geometry& args = fGeoData[i]; 225 226 // get mip level 227 SkScalar maxScale = this->viewMatrix().getMaxScale(); 228 const SkRect& bounds = args.fPath.getBounds(); 229 SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height()); 230 SkScalar size = maxScale * maxDim; 231 uint32_t desiredDimension; 232 if (size <= kSmallMIP) { 233 desiredDimension = kSmallMIP; 234 } else if (size <= kMediumMIP) { 235 desiredDimension = kMediumMIP; 236 } else { 237 desiredDimension = kLargeMIP; 238 } 239 240 // check to see if path is cached 241 // TODO: handle stroked vs. filled version of same path 242 PathData::Key key = { args.fPath.getGenerationID(), desiredDimension }; 243 args.fPathData = fPathCache->find(key); 244 if (NULL == args.fPathData || !atlas->hasID(args.fPathData->fID)) { 245 // Remove the stale cache entry 246 if (args.fPathData) { 247 fPathCache->remove(args.fPathData->fKey); 248 fPathList->remove(args.fPathData); 249 SkDELETE(args.fPathData); 250 } 251 SkScalar scale = desiredDimension/maxDim; 252 args.fPathData = SkNEW(PathData); 253 if (!this->addPathToAtlas(batchTarget, 254 dfProcessor, 255 pipeline, 256 &flushInfo, 257 atlas, 258 args.fPathData, 259 args.fPath, 260 args.fStroke, 261 args.fAntiAlias, 262 desiredDimension, 263 scale)) { 264 SkDebugf("Can't rasterize path\n"); 265 return; 266 } 267 } 268 269 atlas->setLastUseToken(args.fPathData->fID, batchTarget->currentToken()); 270 271 // Now set vertices 272 intptr_t offset = reinterpret_cast<intptr_t>(vertices); 273 offset += i * kVerticesPerQuad * vertexStride; 274 SkPoint* positions = reinterpret_cast<SkPoint*>(offset); 275 this->writePathVertices(batchTarget, 276 atlas, 277 pipeline, 278 dfProcessor, 279 positions, 280 vertexStride, 281 this->viewMatrix(), 282 args.fPath, 283 args.fPathData); 284 flushInfo.fInstancesToFlush++; 285 } 286 287 this->flush(batchTarget, &flushInfo); 288 } 289 290 SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; } 291 292private: 293 AADistanceFieldPathBatch(const Geometry& geometry, GrColor color, const SkMatrix& viewMatrix, 294 GrBatchAtlas* atlas, 295 PathCache* pathCache, PathDataList* pathList) { 296 this->initClassID<AADistanceFieldPathBatch>(); 297 fBatch.fColor = color; 298 fBatch.fViewMatrix = viewMatrix; 299 fGeoData.push_back(geometry); 300 fGeoData.back().fPathData = NULL; 301 302 fAtlas = atlas; 303 fPathCache = pathCache; 304 fPathList = pathList; 305 306 // Compute bounds 307 fBounds = geometry.fPath.getBounds(); 308 viewMatrix.mapRect(&fBounds); 309 } 310 311 bool addPathToAtlas(GrBatchTarget* batchTarget, 312 const GrGeometryProcessor* dfProcessor, 313 const GrPipeline* pipeline, 314 FlushInfo* flushInfo, 315 GrBatchAtlas* atlas, 316 PathData* pathData, 317 const SkPath& path, 318 const SkStrokeRec& 319 stroke, bool antiAlias, 320 uint32_t dimension, 321 SkScalar scale) { 322 const SkRect& bounds = path.getBounds(); 323 324 // generate bounding rect for bitmap draw 325 SkRect scaledBounds = bounds; 326 // scale to mip level size 327 scaledBounds.fLeft *= scale; 328 scaledBounds.fTop *= scale; 329 scaledBounds.fRight *= scale; 330 scaledBounds.fBottom *= scale; 331 // move the origin to an integer boundary (gives better results) 332 SkScalar dx = SkScalarFraction(scaledBounds.fLeft); 333 SkScalar dy = SkScalarFraction(scaledBounds.fTop); 334 scaledBounds.offset(-dx, -dy); 335 // get integer boundary 336 SkIRect devPathBounds; 337 scaledBounds.roundOut(&devPathBounds); 338 // pad to allow room for antialiasing 339 devPathBounds.outset(SkScalarCeilToInt(kAntiAliasPad), SkScalarCeilToInt(kAntiAliasPad)); 340 // move origin to upper left corner 341 devPathBounds.offsetTo(0,0); 342 343 // draw path to bitmap 344 SkMatrix drawMatrix; 345 drawMatrix.setTranslate(-bounds.left(), -bounds.top()); 346 drawMatrix.postScale(scale, scale); 347 drawMatrix.postTranslate(kAntiAliasPad, kAntiAliasPad); 348 349 // setup bitmap backing 350 // Now translate so the bound's UL corner is at the origin 351 drawMatrix.postTranslate(-devPathBounds.fLeft * SK_Scalar1, 352 -devPathBounds.fTop * SK_Scalar1); 353 SkIRect pathBounds = SkIRect::MakeWH(devPathBounds.width(), 354 devPathBounds.height()); 355 356 SkBitmap bmp; 357 const SkImageInfo bmImageInfo = SkImageInfo::MakeA8(pathBounds.fRight, 358 pathBounds.fBottom); 359 if (!bmp.tryAllocPixels(bmImageInfo)) { 360 return false; 361 } 362 363 sk_bzero(bmp.getPixels(), bmp.getSafeSize()); 364 365 // rasterize path 366 SkPaint paint; 367 if (stroke.isHairlineStyle()) { 368 paint.setStyle(SkPaint::kStroke_Style); 369 paint.setStrokeWidth(SK_Scalar1); 370 } else { 371 if (stroke.isFillStyle()) { 372 paint.setStyle(SkPaint::kFill_Style); 373 } else { 374 paint.setStyle(SkPaint::kStroke_Style); 375 paint.setStrokeJoin(stroke.getJoin()); 376 paint.setStrokeCap(stroke.getCap()); 377 paint.setStrokeWidth(stroke.getWidth()); 378 } 379 } 380 paint.setAntiAlias(antiAlias); 381 382 SkDraw draw; 383 sk_bzero(&draw, sizeof(draw)); 384 385 SkRasterClip rasterClip; 386 rasterClip.setRect(pathBounds); 387 draw.fRC = &rasterClip; 388 draw.fClip = &rasterClip.bwRgn(); 389 draw.fMatrix = &drawMatrix; 390 draw.fBitmap = &bmp; 391 392 draw.drawPathCoverage(path, paint); 393 394 // generate signed distance field 395 devPathBounds.outset(SK_DistanceFieldPad, SK_DistanceFieldPad); 396 int width = devPathBounds.width(); 397 int height = devPathBounds.height(); 398 // TODO We should really generate this directly into the plot somehow 399 SkAutoSMalloc<1024> dfStorage(width * height * sizeof(unsigned char)); 400 401 // Generate signed distance field 402 { 403 SkAutoLockPixels alp(bmp); 404 405 SkGenerateDistanceFieldFromA8Image((unsigned char*)dfStorage.get(), 406 (const unsigned char*)bmp.getPixels(), 407 bmp.width(), bmp.height(), bmp.rowBytes()); 408 } 409 410 // add to atlas 411 SkIPoint16 atlasLocation; 412 GrBatchAtlas::AtlasID id; 413 bool success = atlas->addToAtlas(&id, batchTarget, width, height, dfStorage.get(), 414 &atlasLocation); 415 if (!success) { 416 this->flush(batchTarget, flushInfo); 417 this->initDraw(batchTarget, dfProcessor, pipeline); 418 419 SkDEBUGCODE(success =) atlas->addToAtlas(&id, batchTarget, width, height, 420 dfStorage.get(), &atlasLocation); 421 SkASSERT(success); 422 423 } 424 425 // add to cache 426 pathData->fKey.fGenID = path.getGenerationID(); 427 pathData->fKey.fDimension = dimension; 428 pathData->fScale = scale; 429 pathData->fID = id; 430 // change the scaled rect to match the size of the inset distance field 431 scaledBounds.fRight = scaledBounds.fLeft + 432 SkIntToScalar(devPathBounds.width() - 2*SK_DistanceFieldInset); 433 scaledBounds.fBottom = scaledBounds.fTop + 434 SkIntToScalar(devPathBounds.height() - 2*SK_DistanceFieldInset); 435 // shift the origin to the correct place relative to the distance field 436 // need to also restore the fractional translation 437 scaledBounds.offset(-SkIntToScalar(SK_DistanceFieldInset) - kAntiAliasPad + dx, 438 -SkIntToScalar(SK_DistanceFieldInset) - kAntiAliasPad + dy); 439 pathData->fBounds = scaledBounds; 440 // origin we render from is inset from distance field edge 441 atlasLocation.fX += SK_DistanceFieldInset; 442 atlasLocation.fY += SK_DistanceFieldInset; 443 pathData->fAtlasLocation = atlasLocation; 444 445 fPathCache->add(pathData); 446 fPathList->addToTail(pathData); 447#ifdef DF_PATH_TRACKING 448 ++g_NumCachedPaths; 449#endif 450 return true; 451 } 452 453 void writePathVertices(GrBatchTarget* target, 454 GrBatchAtlas* atlas, 455 const GrPipeline* pipeline, 456 const GrGeometryProcessor* gp, 457 SkPoint* positions, 458 size_t vertexStride, 459 const SkMatrix& viewMatrix, 460 const SkPath& path, 461 const PathData* pathData) { 462 GrTexture* texture = atlas->getTexture(); 463 464 SkScalar dx = pathData->fBounds.fLeft; 465 SkScalar dy = pathData->fBounds.fTop; 466 SkScalar width = pathData->fBounds.width(); 467 SkScalar height = pathData->fBounds.height(); 468 469 SkScalar invScale = 1.0f / pathData->fScale; 470 dx *= invScale; 471 dy *= invScale; 472 width *= invScale; 473 height *= invScale; 474 475 SkFixed tx = SkIntToFixed(pathData->fAtlasLocation.fX); 476 SkFixed ty = SkIntToFixed(pathData->fAtlasLocation.fY); 477 SkFixed tw = SkScalarToFixed(pathData->fBounds.width()); 478 SkFixed th = SkScalarToFixed(pathData->fBounds.height()); 479 480 // vertex positions 481 // TODO make the vertex attributes a struct 482 SkRect r = SkRect::MakeXYWH(dx, dy, width, height); 483 positions->setRectFan(r.left(), r.top(), r.right(), r.bottom(), vertexStride); 484 485 // vertex texture coords 486 SkPoint* textureCoords = positions + 1; 487 textureCoords->setRectFan(SkFixedToFloat(texture->texturePriv().normalizeFixedX(tx)), 488 SkFixedToFloat(texture->texturePriv().normalizeFixedY(ty)), 489 SkFixedToFloat(texture->texturePriv().normalizeFixedX(tx + tw)), 490 SkFixedToFloat(texture->texturePriv().normalizeFixedY(ty + th)), 491 vertexStride); 492 } 493 494 void initDraw(GrBatchTarget* batchTarget, 495 const GrGeometryProcessor* dfProcessor, 496 const GrPipeline* pipeline) { 497 batchTarget->initDraw(dfProcessor, pipeline); 498 499 // TODO remove this when batch is everywhere 500 GrPipelineInfo init; 501 init.fColorIgnored = fBatch.fColorIgnored; 502 init.fOverrideColor = GrColor_ILLEGAL; 503 init.fCoverageIgnored = fBatch.fCoverageIgnored; 504 init.fUsesLocalCoords = this->usesLocalCoords(); 505 dfProcessor->initBatchTracker(batchTarget->currentBatchTracker(), init); 506 } 507 508 void flush(GrBatchTarget* batchTarget, FlushInfo* flushInfo) { 509 GrVertices vertices; 510 int maxInstancesPerDraw = flushInfo->fIndexBuffer->maxQuads(); 511 vertices.initInstanced(kTriangles_GrPrimitiveType, flushInfo->fVertexBuffer, 512 flushInfo->fIndexBuffer, flushInfo->fVertexOffset, kVerticesPerQuad, 513 kIndicesPerQuad, flushInfo->fInstancesToFlush, maxInstancesPerDraw); 514 batchTarget->draw(vertices); 515 flushInfo->fVertexOffset += kVerticesPerQuad * flushInfo->fInstancesToFlush; 516 flushInfo->fInstancesToFlush = 0; 517 } 518 519 GrColor color() const { return fBatch.fColor; } 520 const SkMatrix& viewMatrix() const { return fBatch.fViewMatrix; } 521 bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; } 522 523 bool onCombineIfPossible(GrBatch* t) override { 524 AADistanceFieldPathBatch* that = t->cast<AADistanceFieldPathBatch>(); 525 526 // TODO we could actually probably do a bunch of this work on the CPU, ie map viewMatrix, 527 // maybe upload color via attribute 528 if (this->color() != that->color()) { 529 return false; 530 } 531 532 if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) { 533 return false; 534 } 535 536 fGeoData.push_back_n(that->geoData()->count(), that->geoData()->begin()); 537 this->joinBounds(that->bounds()); 538 return true; 539 } 540 541 struct BatchTracker { 542 GrColor fColor; 543 SkMatrix fViewMatrix; 544 bool fUsesLocalCoords; 545 bool fColorIgnored; 546 bool fCoverageIgnored; 547 }; 548 549 BatchTracker fBatch; 550 SkSTArray<1, Geometry, true> fGeoData; 551 GrBatchAtlas* fAtlas; 552 PathCache* fPathCache; 553 PathDataList* fPathList; 554}; 555 556static GrBatchAtlas* create_atlas(GrContext* context, GrBatchAtlas::EvictionFunc func, void* data) { 557 GrBatchAtlas* atlas; 558 // Create a new atlas 559 GrSurfaceDesc desc; 560 desc.fFlags = kNone_GrSurfaceFlags; 561 desc.fWidth = ATLAS_TEXTURE_WIDTH; 562 desc.fHeight = ATLAS_TEXTURE_HEIGHT; 563 desc.fConfig = kAlpha_8_GrPixelConfig; 564 565 // We don't want to flush the context so we claim we're in the middle of flushing so as to 566 // guarantee we do not recieve a texture with pending IO 567 GrTexture* texture = context->textureProvider()->refScratchTexture( 568 desc, GrTextureProvider::kApprox_ScratchTexMatch, true); 569 if (texture) { 570 atlas = SkNEW_ARGS(GrBatchAtlas, (texture, NUM_PLOTS_X, NUM_PLOTS_Y)); 571 } else { 572 return NULL; 573 } 574 atlas->registerEvictionCallback(func, data); 575 return atlas; 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 fAtlas = create_atlas(fContext, &GrAADistanceFieldPathRenderer::HandleEviction, 594 (void*)this); 595 if (!fAtlas) { 596 return false; 597 } 598 } 599 600 AADistanceFieldPathBatch::Geometry geometry(stroke.getStrokeRec()); 601 geometry.fPath = path; 602 geometry.fAntiAlias = antiAlias; 603 604 SkAutoTUnref<GrBatch> batch(AADistanceFieldPathBatch::Create(geometry, color, viewMatrix, 605 fAtlas, &fPathCache, &fPathList)); 606 target->drawBatch(pipelineBuilder, batch); 607 608 return true; 609} 610 611/////////////////////////////////////////////////////////////////////////////////////////////////// 612 613#ifdef GR_TEST_UTILS 614 615struct PathTestStruct { 616 typedef GrAADistanceFieldPathRenderer::PathCache PathCache; 617 typedef GrAADistanceFieldPathRenderer::PathData PathData; 618 typedef GrAADistanceFieldPathRenderer::PathDataList PathDataList; 619 PathTestStruct() : fContextID(SK_InvalidGenID), fAtlas(NULL) {} 620 ~PathTestStruct() { this->reset(); } 621 622 void reset() { 623 PathDataList::Iter iter; 624 iter.init(fPathList, PathDataList::Iter::kHead_IterStart); 625 PathData* pathData; 626 while ((pathData = iter.get())) { 627 iter.next(); 628 fPathList.remove(pathData); 629 SkDELETE(pathData); 630 } 631 SkDELETE(fAtlas); 632 fPathCache.reset(); 633 } 634 635 static void HandleEviction(GrBatchAtlas::AtlasID id, void* pr) { 636 PathTestStruct* dfpr = (PathTestStruct*)pr; 637 // remove any paths that use this plot 638 PathDataList::Iter iter; 639 iter.init(dfpr->fPathList, PathDataList::Iter::kHead_IterStart); 640 PathData* pathData; 641 while ((pathData = iter.get())) { 642 iter.next(); 643 if (id == pathData->fID) { 644 dfpr->fPathCache.remove(pathData->fKey); 645 dfpr->fPathList.remove(pathData); 646 SkDELETE(pathData); 647 } 648 } 649 } 650 651 uint32_t fContextID; 652 GrBatchAtlas* fAtlas; 653 PathCache fPathCache; 654 PathDataList fPathList; 655}; 656 657BATCH_TEST_DEFINE(AADistanceFieldPathBatch) { 658 static PathTestStruct gTestStruct; 659 660 if (context->uniqueID() != gTestStruct.fContextID) { 661 gTestStruct.fContextID = context->uniqueID(); 662 gTestStruct.reset(); 663 gTestStruct.fAtlas = create_atlas(context, &PathTestStruct::HandleEviction, 664 (void*)&gTestStruct); 665 } 666 667 SkMatrix viewMatrix = GrTest::TestMatrix(random); 668 GrColor color = GrRandomColor(random); 669 670 AADistanceFieldPathBatch::Geometry geometry(GrTest::TestStrokeRec(random)); 671 geometry.fPath = GrTest::TestPath(random); 672 geometry.fAntiAlias = random->nextBool(); 673 674 return AADistanceFieldPathBatch::Create(geometry, color, viewMatrix, 675 gTestStruct.fAtlas, 676 &gTestStruct.fPathCache, 677 &gTestStruct.fPathList); 678} 679 680#endif 681