Index: src/gpu/GrAADistanceFieldPathRenderer.cpp |
diff --git a/src/gpu/GrAADistanceFieldPathRenderer.cpp b/src/gpu/GrAADistanceFieldPathRenderer.cpp |
new file mode 100755 |
index 0000000000000000000000000000000000000000..364217c5768eba348f9dcad255eff548c22609be |
--- /dev/null |
+++ b/src/gpu/GrAADistanceFieldPathRenderer.cpp |
@@ -0,0 +1,340 @@ |
+ |
+/* |
+ * 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 "GrAtlas.h" |
+#include "GrContext.h" |
+#include "GrDrawState.h" |
+#include "GrSurfacePriv.h" |
+#include "GrSWMaskHelper.h" |
+#include "GrTexturePriv.h" |
+#include "effects/GrDistanceFieldTextureEffect.h" |
+ |
+#include "SkDistanceFieldGen.h" |
+#include "SkRTConf.h" |
+ |
+#define ATLAS_TEXTURE_WIDTH 1024 |
+#define ATLAS_TEXTURE_HEIGHT 1024 |
+ |
+#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) |
+ |
+SK_CONF_DECLARE(bool, c_DumpPathCache, "gpu.dumpPathCache", false, |
+ "Dump the contents of the path cache before every purge."); |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+GrAADistanceFieldPathRenderer::~GrAADistanceFieldPathRenderer() { |
+ PathDataList::Iter iter; |
+ iter.init(fPathList, PathDataList::Iter::kHead_IterStart); |
+ PathData* pathData; |
+ while ((pathData = iter.get())) { |
+ iter.next(); |
+ fPathList.remove(pathData); |
+ SkDELETE(pathData); |
+ } |
+ |
+ SkDELETE(fAtlas); |
+} |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+bool GrAADistanceFieldPathRenderer::canDrawPath(const SkPath& path, |
+ const SkStrokeRec& stroke, |
+ const GrDrawTarget* target, |
+ bool antiAlias) const { |
+ // TODO: Support inverse fill |
+ // TODO: Support strokes |
+ if (!target->caps()->shaderDerivativeSupport() || !antiAlias || path.isInverseFillType() |
+ || SkStrokeRec::kFill_Style != stroke.getStyle()) { |
+ return false; |
+ } |
+ |
+ // currently don't support perspective or scaling more than 3x |
+ const GrDrawState& drawState = target->getDrawState(); |
+ const SkMatrix& vm = drawState.getViewMatrix(); |
+ if (vm.hasPerspective() || vm.getMaxScale() > 3.0f) { |
+ return false; |
+ } |
+ |
+ // only support paths smaller than 64 x 64 |
+ const SkRect& bounds = path.getBounds(); |
+ return bounds.width() < 64.f && bounds.height() < 64.f; |
+} |
+ |
+ |
+GrPathRenderer::StencilSupport GrAADistanceFieldPathRenderer::onGetStencilSupport( |
+ const SkPath&, |
+ const SkStrokeRec&, |
+ const GrDrawTarget*) const { |
+ return GrPathRenderer::kNoSupport_StencilSupport; |
+} |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+ |
+// position + texture coord |
+extern const GrVertexAttrib gSDFPathVertexAttribs[] = { |
+ { kVec2f_GrVertexAttribType, 0, kPosition_GrVertexAttribBinding }, |
+ { kVec2f_GrVertexAttribType, sizeof(SkPoint), kGeometryProcessor_GrVertexAttribBinding } |
+}; |
+static const size_t kSDFPathVASize = 2 * sizeof(SkPoint); |
+ |
+bool GrAADistanceFieldPathRenderer::onDrawPath(const SkPath& path, |
+ const SkStrokeRec& stroke, |
+ GrDrawTarget* target, |
+ bool antiAlias) { |
+ // we've already bailed on inverse filled paths, so this is safe |
+ if (path.isEmpty()) { |
+ return true; |
+ } |
+ |
+ SkASSERT(fContext); |
+ |
+ // check to see if path is cached |
+ // TODO: handle stroked vs. filled version of same path |
+ PathData* pathData = fPathCache.find(path.getGenerationID()); |
+ if (NULL == pathData) { |
+ pathData = this->addPathToAtlas(path, stroke, antiAlias); |
+ if (NULL == pathData) { |
+ return false; |
+ } |
+ } |
+ |
+ // use signed distance field to render |
+ return this->internalDrawPath(path, pathData, target); |
+} |
+ |
+// factor used to scale the path prior to building distance field |
+const SkScalar kScaleFactor = 2.0f; |
+// padding around path bounds to allow for antialiased pixels |
+const SkScalar kAntiAliasPad = 1.0f; |
+ |
+GrAADistanceFieldPathRenderer::PathData* GrAADistanceFieldPathRenderer::addPathToAtlas( |
+ const SkPath& path, |
+ const SkStrokeRec& stroke, |
+ bool antiAlias) { |
+ |
+ // generate distance field and add to atlas |
+ if (NULL == fAtlas) { |
+ SkISize textureSize = SkISize::Make(ATLAS_TEXTURE_WIDTH, ATLAS_TEXTURE_HEIGHT); |
+ fAtlas = SkNEW_ARGS(GrAtlas, (fContext->getGpu(), kAlpha_8_GrPixelConfig, |
+ kNone_GrTextureFlags, textureSize, |
+ NUM_PLOTS_X, NUM_PLOTS_Y, false)); |
+ if (NULL == fAtlas) { |
+ return NULL; |
+ } |
+ } |
+ |
+ const SkRect& bounds = path.getBounds(); |
+ |
+ // generate bounding rect for bitmap draw |
+ SkRect scaledBounds = bounds; |
+ // scale up to improve maxification range |
+ scaledBounds.fLeft *= kScaleFactor; |
+ scaledBounds.fTop *= kScaleFactor; |
+ scaledBounds.fRight *= kScaleFactor; |
+ scaledBounds.fBottom *= kScaleFactor; |
+ // 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(kScaleFactor, kScaleFactor); |
+ drawMatrix.postTranslate(kAntiAliasPad, kAntiAliasPad); |
+ GrSWMaskHelper helper(fContext); |
+ |
+ if (!helper.init(devPathBounds, &drawMatrix)) { |
+ return NULL; |
+ } |
+ helper.draw(path, stroke, SkRegion::kReplace_Op, antiAlias, 0xFF); |
+ |
+ // generate signed distance field |
+ devPathBounds.outset(SK_DistanceFieldPad, SK_DistanceFieldPad); |
+ int width = devPathBounds.width(); |
+ int height = devPathBounds.height(); |
+ SkAutoSMalloc<1024> dfStorage(width*height*sizeof(unsigned char)); |
+ helper.toSDF((unsigned char*) dfStorage.get()); |
+ |
+ // add to atlas |
+ SkIPoint16 atlasLocation; |
+ GrPlot* plot = fAtlas->addToAtlas(&fPlotUsage, width, height, dfStorage.get(), |
+ &atlasLocation); |
+ |
+ // if atlas full |
+ if (NULL == plot) { |
+ if (this->freeUnusedPlot()) { |
+ plot = fAtlas->addToAtlas(&fPlotUsage, width, height, dfStorage.get(), |
+ &atlasLocation); |
+ if (plot) { |
+ goto HAS_ATLAS; |
+ } |
+ } |
+ |
+ if (c_DumpPathCache) { |
+#ifdef SK_DEVELOPER |
+ GrTexture* texture = fAtlas->getTexture(); |
+ texture->surfacePriv().savePixels("pathcache.png"); |
+#endif |
+ } |
+ |
+ // before we purge the cache, we must flush any accumulated draws |
+ fContext->flush(); |
+ |
+ if (this->freeUnusedPlot()) { |
+ plot = fAtlas->addToAtlas(&fPlotUsage, width, height, dfStorage.get(), |
+ &atlasLocation); |
+ if (plot) { |
+ goto HAS_ATLAS; |
+ } |
+ } |
+ |
+ return NULL; |
+ } |
+ |
+HAS_ATLAS: |
+ // add to cache |
+ PathData* pathData = SkNEW(PathData); |
+ pathData->fGenID = path.getGenerationID(); |
+ pathData->fPlot = plot; |
+ // 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); |
+ |
+ return pathData; |
+} |
+ |
+bool GrAADistanceFieldPathRenderer::freeUnusedPlot() { |
+ // find an unused plot |
+ GrPlot* plot = fAtlas->getUnusedPlot(); |
+ if (NULL == plot) { |
+ return false; |
+ } |
+ plot->resetRects(); |
+ |
+ // remove any paths that use this plot |
+ PathDataList::Iter iter; |
+ iter.init(fPathList, PathDataList::Iter::kHead_IterStart); |
+ PathData* pathData; |
+ while ((pathData = iter.get())) { |
+ iter.next(); |
+ if (plot == pathData->fPlot) { |
+ fPathCache.remove(pathData->fGenID); |
+ fPathList.remove(pathData); |
+ SkDELETE(pathData); |
+ } |
+ } |
+ |
+ // tell the atlas to free the plot |
+ GrAtlas::RemovePlot(&fPlotUsage, plot); |
+ |
+ return true; |
+} |
+ |
+bool GrAADistanceFieldPathRenderer::internalDrawPath(const SkPath& path, |
+ const PathData* pathData, |
+ GrDrawTarget* target) { |
+ |
+ GrTexture* texture = fAtlas->getTexture(); |
+ GrDrawState* drawState = target->drawState(); |
+ |
+ SkASSERT(pathData->fPlot); |
+ GrDrawTarget::DrawToken drawToken = target->getCurrentDrawToken(); |
+ pathData->fPlot->setDrawToken(drawToken); |
+ |
+ // make me some vertices |
+ drawState->setVertexAttribs<gSDFPathVertexAttribs>(SK_ARRAY_COUNT(gSDFPathVertexAttribs), |
+ kSDFPathVASize); |
+ void* vertices = NULL; |
+ void* indices = NULL; |
+ bool success = target->reserveVertexAndIndexSpace(4, 6, &vertices, &indices); |
+ GrAlwaysAssert(success); |
+ |
+ SkScalar dx = pathData->fBounds.fLeft; |
+ SkScalar dy = pathData->fBounds.fTop; |
+ SkScalar width = pathData->fBounds.width(); |
+ SkScalar height = pathData->fBounds.height(); |
+ |
+ SkScalar invScale = 1.0f/kScaleFactor; |
+ 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 |
+ SkRect r = SkRect::MakeXYWH(dx, dy, width, height); |
+ size_t vertSize = 2 * sizeof(SkPoint); |
+ SkPoint* positions = reinterpret_cast<SkPoint*>(vertices); |
+ positions->setRectFan(r.left(), r.top(), r.right(), r.bottom(), vertSize); |
+ |
+ // vertex texture coords |
+ intptr_t intPtr = reinterpret_cast<intptr_t>(positions); |
+ SkPoint* textureCoords = reinterpret_cast<SkPoint*>(intPtr + vertSize - sizeof(SkPoint)); |
+ textureCoords->setRectFan(SkFixedToFloat(texture->texturePriv().normalizeFixedX(tx)), |
+ SkFixedToFloat(texture->texturePriv().normalizeFixedY(ty)), |
+ SkFixedToFloat(texture->texturePriv().normalizeFixedX(tx + tw)), |
+ SkFixedToFloat(texture->texturePriv().normalizeFixedY(ty + th)), |
+ vertSize); |
+ |
+ uint16_t* indexPtr = reinterpret_cast<uint16_t*>(indices); |
+ *indexPtr++ = 0; |
+ *indexPtr++ = 1; |
+ *indexPtr++ = 2; |
+ *indexPtr++ = 0; |
+ *indexPtr++ = 2; |
+ *indexPtr++ = 3; |
+ |
+ // set up any flags |
+ uint32_t flags = 0; |
+ const SkMatrix& vm = drawState->getViewMatrix(); |
+ flags |= vm.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0; |
+ |
+ GrTextureParams params(SkShader::kRepeat_TileMode, GrTextureParams::kBilerp_FilterMode); |
+ drawState->setGeometryProcessor(GrDistanceFieldNoGammaTextureEffect::Create(texture, |
+ params, |
+ flags))->unref(); |
+ |
+ |
+ vm.mapRect(&r); |
+ target->drawIndexedInstances(kTriangles_GrPrimitiveType, 1, 4, 6, &r); |
+ target->resetVertexSource(); |
+ target->resetIndexSource(); |
+ |
+ return true; |
+} |
+ |