aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/gpu/batches/GrAADistanceFieldPathRenderer.cpp
diff options
context:
space:
mode:
authorGravatar joshualitt <joshualitt@chromium.org>2015-09-01 06:50:55 -0700
committerGravatar Commit bot <commit-bot@chromium.org>2015-09-01 06:50:55 -0700
commit6335a729769b06f0d6a1450c7c02e03f95bbb049 (patch)
tree86463f9ac9a06a6cc7d333e3cfcdfe602245f8b7 /src/gpu/batches/GrAADistanceFieldPathRenderer.cpp
parentfbccb5995dcda645a3a8af51805639cb0c0d2300 (diff)
Move PathRenderers to batches folder
Diffstat (limited to 'src/gpu/batches/GrAADistanceFieldPathRenderer.cpp')
-rw-r--r--src/gpu/batches/GrAADistanceFieldPathRenderer.cpp626
1 files changed, 626 insertions, 0 deletions
diff --git a/src/gpu/batches/GrAADistanceFieldPathRenderer.cpp b/src/gpu/batches/GrAADistanceFieldPathRenderer.cpp
new file mode 100644
index 0000000000..45a3d65179
--- /dev/null
+++ b/src/gpu/batches/GrAADistanceFieldPathRenderer.cpp
@@ -0,0 +1,626 @@
+
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrAADistanceFieldPathRenderer.h"
+
+#include "GrBatchFlushState.h"
+#include "GrBatchTest.h"
+#include "GrContext.h"
+#include "GrPipelineBuilder.h"
+#include "GrResourceProvider.h"
+#include "GrSurfacePriv.h"
+#include "GrSWMaskHelper.h"
+#include "GrTexturePriv.h"
+#include "GrVertexBuffer.h"
+#include "batches/GrVertexBatch.h"
+#include "effects/GrDistanceFieldGeoProc.h"
+
+#include "SkDistanceFieldGen.h"
+#include "SkRTConf.h"
+
+#define ATLAS_TEXTURE_WIDTH 1024
+#define ATLAS_TEXTURE_HEIGHT 2048
+#define PLOT_WIDTH 256
+#define PLOT_HEIGHT 256
+
+#define NUM_PLOTS_X (ATLAS_TEXTURE_WIDTH / PLOT_WIDTH)
+#define NUM_PLOTS_Y (ATLAS_TEXTURE_HEIGHT / PLOT_HEIGHT)
+
+#ifdef DF_PATH_TRACKING
+static int g_NumCachedPaths = 0;
+static int g_NumFreedPaths = 0;
+#endif
+
+// mip levels
+static const int kSmallMIP = 32;
+static const int kMediumMIP = 78;
+static const int kLargeMIP = 192;
+
+// Callback to clear out internal path cache when eviction occurs
+void GrAADistanceFieldPathRenderer::HandleEviction(GrBatchAtlas::AtlasID id, void* pr) {
+ GrAADistanceFieldPathRenderer* dfpr = (GrAADistanceFieldPathRenderer*)pr;
+ // remove any paths that use this plot
+ PathDataList::Iter iter;
+ iter.init(dfpr->fPathList, PathDataList::Iter::kHead_IterStart);
+ PathData* pathData;
+ while ((pathData = iter.get())) {
+ iter.next();
+ if (id == pathData->fID) {
+ dfpr->fPathCache.remove(pathData->fKey);
+ dfpr->fPathList.remove(pathData);
+ delete pathData;
+#ifdef DF_PATH_TRACKING
+ ++g_NumFreedPaths;
+#endif
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+GrAADistanceFieldPathRenderer::GrAADistanceFieldPathRenderer() : fAtlas(nullptr) {}
+
+GrAADistanceFieldPathRenderer::~GrAADistanceFieldPathRenderer() {
+ PathDataList::Iter iter;
+ iter.init(fPathList, PathDataList::Iter::kHead_IterStart);
+ PathData* pathData;
+ while ((pathData = iter.get())) {
+ iter.next();
+ fPathList.remove(pathData);
+ delete pathData;
+ }
+ delete fAtlas;
+
+#ifdef DF_PATH_TRACKING
+ SkDebugf("Cached paths: %d, freed paths: %d\n", g_NumCachedPaths, g_NumFreedPaths);
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////////////
+bool GrAADistanceFieldPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
+
+ // TODO: Support inverse fill
+ // TODO: Support strokes
+ if (!args.fShaderCaps->shaderDerivativeSupport() || !args.fAntiAlias ||
+ args.fPath->isInverseFillType() || args.fPath->isVolatile() ||
+ !args.fStroke->isFillStyle()) {
+ return false;
+ }
+
+ // currently don't support perspective
+ if (args.fViewMatrix->hasPerspective()) {
+ return false;
+ }
+
+ // only support paths smaller than 64x64, scaled to less than 256x256
+ // the goal is to accelerate rendering of lots of small paths that may be scaling
+ SkScalar maxScale = args.fViewMatrix->getMaxScale();
+ const SkRect& bounds = args.fPath->getBounds();
+ SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
+ return maxDim < 64.f && maxDim * maxScale < 256.f;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// padding around path bounds to allow for antialiased pixels
+static const SkScalar kAntiAliasPad = 1.0f;
+
+class AADistanceFieldPathBatch : public GrVertexBatch {
+public:
+ typedef GrAADistanceFieldPathRenderer::PathData PathData;
+ typedef SkTDynamicHash<PathData, PathData::Key> PathCache;
+ typedef GrAADistanceFieldPathRenderer::PathDataList PathDataList;
+
+ struct Geometry {
+ Geometry(const SkStrokeRec& stroke) : fStroke(stroke) {}
+ SkPath fPath;
+ SkStrokeRec fStroke;
+ bool fAntiAlias;
+ PathData* fPathData;
+ };
+
+ static GrDrawBatch* Create(const Geometry& geometry, GrColor color, const SkMatrix& viewMatrix,
+ GrBatchAtlas* atlas, PathCache* pathCache, PathDataList* pathList) {
+ return new AADistanceFieldPathBatch(geometry, color, viewMatrix, atlas, pathCache,
+ pathList);
+ }
+
+ const char* name() const override { return "AADistanceFieldPathBatch"; }
+
+ void getInvariantOutputColor(GrInitInvariantOutput* out) const override {
+ out->setKnownFourComponents(fBatch.fColor);
+ }
+
+ void getInvariantOutputCoverage(GrInitInvariantOutput* out) const override {
+ out->setUnknownSingleComponent();
+ }
+
+private:
+ void initBatchTracker(const GrPipelineOptimizations& opt) override {
+ // Handle any color overrides
+ if (!opt.readsColor()) {
+ fBatch.fColor = GrColor_ILLEGAL;
+ }
+ opt.getOverrideColorIfSet(&fBatch.fColor);
+
+ // setup batch properties
+ fBatch.fColorIgnored = !opt.readsColor();
+ fBatch.fUsesLocalCoords = opt.readsLocalCoords();
+ fBatch.fCoverageIgnored = !opt.readsCoverage();
+ }
+
+ struct FlushInfo {
+ SkAutoTUnref<const GrVertexBuffer> fVertexBuffer;
+ SkAutoTUnref<const GrIndexBuffer> fIndexBuffer;
+ int fVertexOffset;
+ int fInstancesToFlush;
+ };
+
+ void onPrepareDraws(Target* target) override {
+ int instanceCount = fGeoData.count();
+
+ SkMatrix invert;
+ if (this->usesLocalCoords() && !this->viewMatrix().invert(&invert)) {
+ SkDebugf("Could not invert viewmatrix\n");
+ return;
+ }
+
+ uint32_t flags = 0;
+ flags |= this->viewMatrix().isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
+
+ GrTextureParams params(SkShader::kRepeat_TileMode, GrTextureParams::kBilerp_FilterMode);
+
+ // Setup GrGeometryProcessor
+ GrBatchAtlas* atlas = fAtlas;
+ SkAutoTUnref<GrGeometryProcessor> dfProcessor(
+ GrDistanceFieldPathGeoProc::Create(this->color(),
+ this->viewMatrix(),
+ atlas->getTexture(),
+ params,
+ flags,
+ this->usesLocalCoords()));
+
+ target->initDraw(dfProcessor, this->pipeline());
+
+ FlushInfo flushInfo;
+
+ // allocate vertices
+ size_t vertexStride = dfProcessor->getVertexStride();
+ SkASSERT(vertexStride == 2 * sizeof(SkPoint));
+
+ const GrVertexBuffer* vertexBuffer;
+ void* vertices = target->makeVertexSpace(vertexStride,
+ kVerticesPerQuad * instanceCount,
+ &vertexBuffer,
+ &flushInfo.fVertexOffset);
+ flushInfo.fVertexBuffer.reset(SkRef(vertexBuffer));
+ flushInfo.fIndexBuffer.reset(target->resourceProvider()->refQuadIndexBuffer());
+ if (!vertices || !flushInfo.fIndexBuffer) {
+ SkDebugf("Could not allocate vertices\n");
+ return;
+ }
+
+ flushInfo.fInstancesToFlush = 0;
+ for (int i = 0; i < instanceCount; i++) {
+ Geometry& args = fGeoData[i];
+
+ // get mip level
+ SkScalar maxScale = this->viewMatrix().getMaxScale();
+ const SkRect& bounds = args.fPath.getBounds();
+ SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
+ SkScalar size = maxScale * maxDim;
+ uint32_t desiredDimension;
+ if (size <= kSmallMIP) {
+ desiredDimension = kSmallMIP;
+ } else if (size <= kMediumMIP) {
+ desiredDimension = kMediumMIP;
+ } else {
+ desiredDimension = kLargeMIP;
+ }
+
+ // check to see if path is cached
+ // TODO: handle stroked vs. filled version of same path
+ PathData::Key key = { args.fPath.getGenerationID(), desiredDimension };
+ args.fPathData = fPathCache->find(key);
+ if (nullptr == args.fPathData || !atlas->hasID(args.fPathData->fID)) {
+ // Remove the stale cache entry
+ if (args.fPathData) {
+ fPathCache->remove(args.fPathData->fKey);
+ fPathList->remove(args.fPathData);
+ delete args.fPathData;
+ }
+ SkScalar scale = desiredDimension/maxDim;
+ args.fPathData = new PathData;
+ if (!this->addPathToAtlas(target,
+ dfProcessor,
+ this->pipeline(),
+ &flushInfo,
+ atlas,
+ args.fPathData,
+ args.fPath,
+ args.fStroke,
+ args.fAntiAlias,
+ desiredDimension,
+ scale)) {
+ SkDebugf("Can't rasterize path\n");
+ return;
+ }
+ }
+
+ atlas->setLastUseToken(args.fPathData->fID, target->currentToken());
+
+ // Now set vertices
+ intptr_t offset = reinterpret_cast<intptr_t>(vertices);
+ offset += i * kVerticesPerQuad * vertexStride;
+ SkPoint* positions = reinterpret_cast<SkPoint*>(offset);
+ this->writePathVertices(target,
+ atlas,
+ this->pipeline(),
+ dfProcessor,
+ positions,
+ vertexStride,
+ this->viewMatrix(),
+ args.fPath,
+ args.fPathData);
+ flushInfo.fInstancesToFlush++;
+ }
+
+ this->flush(target, &flushInfo);
+ }
+
+ SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; }
+
+ AADistanceFieldPathBatch(const Geometry& geometry, GrColor color, const SkMatrix& viewMatrix,
+ GrBatchAtlas* atlas,
+ PathCache* pathCache, PathDataList* pathList) {
+ this->initClassID<AADistanceFieldPathBatch>();
+ fBatch.fColor = color;
+ fBatch.fViewMatrix = viewMatrix;
+ fGeoData.push_back(geometry);
+ fGeoData.back().fPathData = nullptr;
+
+ fAtlas = atlas;
+ fPathCache = pathCache;
+ fPathList = pathList;
+
+ // Compute bounds
+ fBounds = geometry.fPath.getBounds();
+ viewMatrix.mapRect(&fBounds);
+ }
+
+ bool addPathToAtlas(GrVertexBatch::Target* target,
+ const GrGeometryProcessor* dfProcessor,
+ const GrPipeline* pipeline,
+ FlushInfo* flushInfo,
+ GrBatchAtlas* atlas,
+ PathData* pathData,
+ const SkPath& path,
+ const SkStrokeRec&
+ stroke, bool antiAlias,
+ uint32_t dimension,
+ SkScalar scale) {
+ const SkRect& bounds = path.getBounds();
+
+ // generate bounding rect for bitmap draw
+ SkRect scaledBounds = bounds;
+ // scale to mip level size
+ scaledBounds.fLeft *= scale;
+ scaledBounds.fTop *= scale;
+ scaledBounds.fRight *= scale;
+ scaledBounds.fBottom *= scale;
+ // move the origin to an integer boundary (gives better results)
+ SkScalar dx = SkScalarFraction(scaledBounds.fLeft);
+ SkScalar dy = SkScalarFraction(scaledBounds.fTop);
+ scaledBounds.offset(-dx, -dy);
+ // get integer boundary
+ SkIRect devPathBounds;
+ scaledBounds.roundOut(&devPathBounds);
+ // pad to allow room for antialiasing
+ devPathBounds.outset(SkScalarCeilToInt(kAntiAliasPad), SkScalarCeilToInt(kAntiAliasPad));
+ // move origin to upper left corner
+ devPathBounds.offsetTo(0,0);
+
+ // draw path to bitmap
+ SkMatrix drawMatrix;
+ drawMatrix.setTranslate(-bounds.left(), -bounds.top());
+ drawMatrix.postScale(scale, scale);
+ drawMatrix.postTranslate(kAntiAliasPad, kAntiAliasPad);
+
+ // setup bitmap backing
+ // Now translate so the bound's UL corner is at the origin
+ drawMatrix.postTranslate(-devPathBounds.fLeft * SK_Scalar1,
+ -devPathBounds.fTop * SK_Scalar1);
+ SkIRect pathBounds = SkIRect::MakeWH(devPathBounds.width(),
+ devPathBounds.height());
+
+ SkAutoPixmapStorage dst;
+ if (!dst.tryAlloc(SkImageInfo::MakeA8(pathBounds.width(),
+ pathBounds.height()))) {
+ return false;
+ }
+ sk_bzero(dst.writable_addr(), dst.getSafeSize());
+
+ // rasterize path
+ SkPaint paint;
+ if (stroke.isHairlineStyle()) {
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setStrokeWidth(SK_Scalar1);
+ } else {
+ if (stroke.isFillStyle()) {
+ paint.setStyle(SkPaint::kFill_Style);
+ } else {
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setStrokeJoin(stroke.getJoin());
+ paint.setStrokeCap(stroke.getCap());
+ paint.setStrokeWidth(stroke.getWidth());
+ }
+ }
+ paint.setAntiAlias(antiAlias);
+
+ SkDraw draw;
+ sk_bzero(&draw, sizeof(draw));
+
+ SkRasterClip rasterClip;
+ rasterClip.setRect(pathBounds);
+ draw.fRC = &rasterClip;
+ draw.fClip = &rasterClip.bwRgn();
+ draw.fMatrix = &drawMatrix;
+ draw.fDst = dst;
+
+ draw.drawPathCoverage(path, paint);
+
+ // generate signed distance field
+ devPathBounds.outset(SK_DistanceFieldPad, SK_DistanceFieldPad);
+ int width = devPathBounds.width();
+ int height = devPathBounds.height();
+ // TODO We should really generate this directly into the plot somehow
+ SkAutoSMalloc<1024> dfStorage(width * height * sizeof(unsigned char));
+
+ // Generate signed distance field
+ SkGenerateDistanceFieldFromA8Image((unsigned char*)dfStorage.get(),
+ (const unsigned char*)dst.addr(),
+ dst.width(), dst.height(), dst.rowBytes());
+
+ // add to atlas
+ SkIPoint16 atlasLocation;
+ GrBatchAtlas::AtlasID id;
+ bool success = atlas->addToAtlas(&id, target, width, height, dfStorage.get(),
+ &atlasLocation);
+ if (!success) {
+ this->flush(target, flushInfo);
+ target->initDraw(dfProcessor, pipeline);
+
+ SkDEBUGCODE(success =) atlas->addToAtlas(&id, target, width, height,
+ dfStorage.get(), &atlasLocation);
+ SkASSERT(success);
+
+ }
+
+ // add to cache
+ pathData->fKey.fGenID = path.getGenerationID();
+ pathData->fKey.fDimension = dimension;
+ pathData->fScale = scale;
+ pathData->fID = id;
+ // change the scaled rect to match the size of the inset distance field
+ scaledBounds.fRight = scaledBounds.fLeft +
+ SkIntToScalar(devPathBounds.width() - 2*SK_DistanceFieldInset);
+ scaledBounds.fBottom = scaledBounds.fTop +
+ SkIntToScalar(devPathBounds.height() - 2*SK_DistanceFieldInset);
+ // shift the origin to the correct place relative to the distance field
+ // need to also restore the fractional translation
+ scaledBounds.offset(-SkIntToScalar(SK_DistanceFieldInset) - kAntiAliasPad + dx,
+ -SkIntToScalar(SK_DistanceFieldInset) - kAntiAliasPad + dy);
+ pathData->fBounds = scaledBounds;
+ // origin we render from is inset from distance field edge
+ atlasLocation.fX += SK_DistanceFieldInset;
+ atlasLocation.fY += SK_DistanceFieldInset;
+ pathData->fAtlasLocation = atlasLocation;
+
+ fPathCache->add(pathData);
+ fPathList->addToTail(pathData);
+#ifdef DF_PATH_TRACKING
+ ++g_NumCachedPaths;
+#endif
+ return true;
+ }
+
+ void writePathVertices(GrDrawBatch::Target* target,
+ GrBatchAtlas* atlas,
+ const GrPipeline* pipeline,
+ const GrGeometryProcessor* gp,
+ SkPoint* positions,
+ size_t vertexStride,
+ const SkMatrix& viewMatrix,
+ const SkPath& path,
+ const PathData* pathData) {
+ GrTexture* texture = atlas->getTexture();
+
+ SkScalar dx = pathData->fBounds.fLeft;
+ SkScalar dy = pathData->fBounds.fTop;
+ SkScalar width = pathData->fBounds.width();
+ SkScalar height = pathData->fBounds.height();
+
+ SkScalar invScale = 1.0f / pathData->fScale;
+ dx *= invScale;
+ dy *= invScale;
+ width *= invScale;
+ height *= invScale;
+
+ SkFixed tx = SkIntToFixed(pathData->fAtlasLocation.fX);
+ SkFixed ty = SkIntToFixed(pathData->fAtlasLocation.fY);
+ SkFixed tw = SkScalarToFixed(pathData->fBounds.width());
+ SkFixed th = SkScalarToFixed(pathData->fBounds.height());
+
+ // vertex positions
+ // TODO make the vertex attributes a struct
+ SkRect r = SkRect::MakeXYWH(dx, dy, width, height);
+ positions->setRectFan(r.left(), r.top(), r.right(), r.bottom(), vertexStride);
+
+ // vertex texture coords
+ SkPoint* textureCoords = positions + 1;
+ textureCoords->setRectFan(SkFixedToFloat(texture->texturePriv().normalizeFixedX(tx)),
+ SkFixedToFloat(texture->texturePriv().normalizeFixedY(ty)),
+ SkFixedToFloat(texture->texturePriv().normalizeFixedX(tx + tw)),
+ SkFixedToFloat(texture->texturePriv().normalizeFixedY(ty + th)),
+ vertexStride);
+ }
+
+ void flush(GrVertexBatch::Target* target, FlushInfo* flushInfo) {
+ GrVertices vertices;
+ int maxInstancesPerDraw = flushInfo->fIndexBuffer->maxQuads();
+ vertices.initInstanced(kTriangles_GrPrimitiveType, flushInfo->fVertexBuffer,
+ flushInfo->fIndexBuffer, flushInfo->fVertexOffset, kVerticesPerQuad,
+ kIndicesPerQuad, flushInfo->fInstancesToFlush, maxInstancesPerDraw);
+ target->draw(vertices);
+ flushInfo->fVertexOffset += kVerticesPerQuad * flushInfo->fInstancesToFlush;
+ flushInfo->fInstancesToFlush = 0;
+ }
+
+ GrColor color() const { return fBatch.fColor; }
+ const SkMatrix& viewMatrix() const { return fBatch.fViewMatrix; }
+ bool usesLocalCoords() const { return fBatch.fUsesLocalCoords; }
+
+ bool onCombineIfPossible(GrBatch* t, const GrCaps& caps) override {
+ AADistanceFieldPathBatch* that = t->cast<AADistanceFieldPathBatch>();
+ if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pipeline(),
+ that->bounds(), caps)) {
+ return false;
+ }
+
+ // TODO we could actually probably do a bunch of this work on the CPU, ie map viewMatrix,
+ // maybe upload color via attribute
+ if (this->color() != that->color()) {
+ return false;
+ }
+
+ if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
+ return false;
+ }
+
+ fGeoData.push_back_n(that->geoData()->count(), that->geoData()->begin());
+ this->joinBounds(that->bounds());
+ return true;
+ }
+
+ struct BatchTracker {
+ GrColor fColor;
+ SkMatrix fViewMatrix;
+ bool fUsesLocalCoords;
+ bool fColorIgnored;
+ bool fCoverageIgnored;
+ };
+
+ BatchTracker fBatch;
+ SkSTArray<1, Geometry, true> fGeoData;
+ GrBatchAtlas* fAtlas;
+ PathCache* fPathCache;
+ PathDataList* fPathList;
+};
+
+bool GrAADistanceFieldPathRenderer::onDrawPath(const DrawPathArgs& args) {
+ // we've already bailed on inverse filled paths, so this is safe
+ if (args.fPath->isEmpty()) {
+ return true;
+ }
+
+ if (!fAtlas) {
+ fAtlas = args.fResourceProvider->createAtlas(kAlpha_8_GrPixelConfig,
+ ATLAS_TEXTURE_WIDTH, ATLAS_TEXTURE_HEIGHT,
+ NUM_PLOTS_X, NUM_PLOTS_Y,
+ &GrAADistanceFieldPathRenderer::HandleEviction,
+ (void*)this);
+ if (!fAtlas) {
+ return false;
+ }
+ }
+
+ AADistanceFieldPathBatch::Geometry geometry(*args.fStroke);
+ geometry.fPath = *args.fPath;
+ geometry.fAntiAlias = args.fAntiAlias;
+
+ SkAutoTUnref<GrDrawBatch> batch(AADistanceFieldPathBatch::Create(geometry, args.fColor,
+ *args.fViewMatrix, fAtlas,
+ &fPathCache, &fPathList));
+ args.fTarget->drawBatch(*args.fPipelineBuilder, batch);
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+#ifdef GR_TEST_UTILS
+
+struct PathTestStruct {
+ typedef GrAADistanceFieldPathRenderer::PathCache PathCache;
+ typedef GrAADistanceFieldPathRenderer::PathData PathData;
+ typedef GrAADistanceFieldPathRenderer::PathDataList PathDataList;
+ PathTestStruct() : fContextID(SK_InvalidGenID), fAtlas(nullptr) {}
+ ~PathTestStruct() { this->reset(); }
+
+ void reset() {
+ PathDataList::Iter iter;
+ iter.init(fPathList, PathDataList::Iter::kHead_IterStart);
+ PathData* pathData;
+ while ((pathData = iter.get())) {
+ iter.next();
+ fPathList.remove(pathData);
+ delete pathData;
+ }
+ delete fAtlas;
+ fPathCache.reset();
+ }
+
+ static void HandleEviction(GrBatchAtlas::AtlasID id, void* pr) {
+ PathTestStruct* dfpr = (PathTestStruct*)pr;
+ // remove any paths that use this plot
+ PathDataList::Iter iter;
+ iter.init(dfpr->fPathList, PathDataList::Iter::kHead_IterStart);
+ PathData* pathData;
+ while ((pathData = iter.get())) {
+ iter.next();
+ if (id == pathData->fID) {
+ dfpr->fPathCache.remove(pathData->fKey);
+ dfpr->fPathList.remove(pathData);
+ delete pathData;
+ }
+ }
+ }
+
+ uint32_t fContextID;
+ GrBatchAtlas* fAtlas;
+ PathCache fPathCache;
+ PathDataList fPathList;
+};
+
+DRAW_BATCH_TEST_DEFINE(AADistanceFieldPathBatch) {
+ static PathTestStruct gTestStruct;
+
+ if (context->uniqueID() != gTestStruct.fContextID) {
+ gTestStruct.fContextID = context->uniqueID();
+ gTestStruct.reset();
+ gTestStruct.fAtlas =
+ context->resourceProvider()->createAtlas(kAlpha_8_GrPixelConfig,
+ ATLAS_TEXTURE_WIDTH, ATLAS_TEXTURE_HEIGHT,
+ NUM_PLOTS_X, NUM_PLOTS_Y,
+ &PathTestStruct::HandleEviction,
+ (void*)&gTestStruct);
+ }
+
+ SkMatrix viewMatrix = GrTest::TestMatrix(random);
+ GrColor color = GrRandomColor(random);
+
+ AADistanceFieldPathBatch::Geometry geometry(GrTest::TestStrokeRec(random));
+ geometry.fPath = GrTest::TestPath(random);
+ geometry.fAntiAlias = random->nextBool();
+
+ return AADistanceFieldPathBatch::Create(geometry, color, viewMatrix,
+ gTestStruct.fAtlas,
+ &gTestStruct.fPathCache,
+ &gTestStruct.fPathList);
+}
+
+#endif