| 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;
|
| +}
|
| +
|
|
|