diff options
author | 2015-09-01 06:50:55 -0700 | |
---|---|---|
committer | 2015-09-01 06:50:55 -0700 | |
commit | 6335a729769b06f0d6a1450c7c02e03f95bbb049 (patch) | |
tree | 86463f9ac9a06a6cc7d333e3cfcdfe602245f8b7 /src/gpu/batches/GrAADistanceFieldPathRenderer.cpp | |
parent | fbccb5995dcda645a3a8af51805639cb0c0d2300 (diff) |
Move PathRenderers to batches folder
BUG=skia:
Review URL: https://codereview.chromium.org/1306143005
Diffstat (limited to 'src/gpu/batches/GrAADistanceFieldPathRenderer.cpp')
-rw-r--r-- | src/gpu/batches/GrAADistanceFieldPathRenderer.cpp | 626 |
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 |