Add GrAASmallPathRenderer.
Uses cached signed distance fields to render scaled and rotated versions
of small paths.
BUG=skia:2935
Review URL: https://codereview.chromium.org/589103004
diff --git a/src/gpu/GrAADistanceFieldPathRenderer.cpp b/src/gpu/GrAADistanceFieldPathRenderer.cpp
new file mode 100755
index 0000000..364217c
--- /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;
+}
+
diff --git a/src/gpu/GrAADistanceFieldPathRenderer.h b/src/gpu/GrAADistanceFieldPathRenderer.h
new file mode 100755
index 0000000..4c09f12
--- /dev/null
+++ b/src/gpu/GrAADistanceFieldPathRenderer.h
@@ -0,0 +1,77 @@
+
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrAADistanceFieldPathRenderer_DEFINED
+#define GrAADistanceFieldPathRenderer_DEFINED
+
+#include "GrAllocPool.h"
+#include "GrAtlas.h"
+#include "GrPathRenderer.h"
+#include "GrRect.h"
+
+#include "SkChecksum.h"
+
+class GrContext;
+class GrPlot;
+
+class GrAADistanceFieldPathRenderer : public GrPathRenderer {
+public:
+ GrAADistanceFieldPathRenderer(GrContext* context)
+ : fContext(context)
+ , fAtlas(NULL) {
+ }
+
+ virtual ~GrAADistanceFieldPathRenderer();
+
+ virtual bool canDrawPath(const SkPath& path,
+ const SkStrokeRec& stroke,
+ const GrDrawTarget* target,
+ bool antiAlias) const SK_OVERRIDE;
+
+protected:
+ virtual StencilSupport onGetStencilSupport(const SkPath&,
+ const SkStrokeRec&,
+ const GrDrawTarget*) const SK_OVERRIDE;
+
+ virtual bool onDrawPath(const SkPath& path,
+ const SkStrokeRec& stroke,
+ GrDrawTarget* target,
+ bool antiAlias) SK_OVERRIDE;
+
+private:
+ struct PathData {
+ uint32_t fGenID;
+ GrPlot* fPlot;
+ SkRect fBounds;
+ SkIPoint16 fAtlasLocation;
+ SK_DECLARE_INTERNAL_LLIST_INTERFACE(PathData);
+
+ static inline const uint32_t& GetKey(const PathData& data) {
+ return data.fGenID;
+ }
+
+ static inline uint32_t Hash(uint32_t key) {
+ return SkChecksum::Murmur3(&key, sizeof(key));
+ }
+ };
+ typedef SkTInternalLList<PathData> PathDataList;
+
+ GrContext* fContext;
+ GrAtlas* fAtlas;
+ GrAtlas::ClientPlotUsage fPlotUsage;
+ SkTDynamicHash<PathData, uint32_t> fPathCache;
+ PathDataList fPathList;
+
+ bool internalDrawPath(const SkPath& path, const PathData* pathData, GrDrawTarget* target);
+ PathData* addPathToAtlas(const SkPath& path, const SkStrokeRec& stroke, bool antiAlias);
+ bool freeUnusedPlot();
+
+ typedef GrPathRenderer INHERITED;
+};
+
+#endif
diff --git a/src/gpu/GrAddPathRenderers_default.cpp b/src/gpu/GrAddPathRenderers_default.cpp
index 4f17243..1b5f085 100644
--- a/src/gpu/GrAddPathRenderers_default.cpp
+++ b/src/gpu/GrAddPathRenderers_default.cpp
@@ -10,6 +10,7 @@
#include "GrStencilAndCoverPathRenderer.h"
#include "GrAAHairLinePathRenderer.h"
#include "GrAAConvexPathRenderer.h"
+#include "GrAADistanceFieldPathRenderer.h"
#if GR_STROKE_PATH_RENDERING
#include "../../experimental/StrokePathRenderer/GrStrokePathRenderer.h"
#endif
@@ -31,4 +32,7 @@
chain->addPathRenderer(pr)->unref();
}
chain->addPathRenderer(SkNEW(GrAAConvexPathRenderer))->unref();
+#ifndef SK_LEGACY_NO_DISTANCE_FIELD_PATHS
+ chain->addPathRenderer(SkNEW_ARGS(GrAADistanceFieldPathRenderer, (ctx)))->unref();
+#endif
}
diff --git a/src/gpu/GrSWMaskHelper.cpp b/src/gpu/GrSWMaskHelper.cpp
index c80a13c..90aab90 100644
--- a/src/gpu/GrSWMaskHelper.cpp
+++ b/src/gpu/GrSWMaskHelper.cpp
@@ -12,6 +12,7 @@
#include "GrGpu.h"
#include "SkData.h"
+#include "SkDistanceFieldGen.h"
#include "SkStrokeRec.h"
// TODO: try to remove this #include
@@ -306,6 +307,16 @@
}
}
+/**
+ * Convert mask generation results to a signed distance field
+ */
+void GrSWMaskHelper::toSDF(unsigned char* sdf) {
+ SkAutoLockPixels alp(fBM);
+
+ SkGenerateDistanceFieldFromA8Image(sdf, (const unsigned char*)fBM.getPixels(),
+ fBM.width(), fBM.height(), fBM.rowBytes());
+}
+
////////////////////////////////////////////////////////////////////////////////
/**
* Software rasterizes path to A8 mask (possibly using the context's matrix)
diff --git a/src/gpu/GrSWMaskHelper.h b/src/gpu/GrSWMaskHelper.h
index f8cce8b..b23ee2c 100644
--- a/src/gpu/GrSWMaskHelper.h
+++ b/src/gpu/GrSWMaskHelper.h
@@ -68,13 +68,16 @@
// Move the mask generation results from the internal bitmap to the gpu.
void toTexture(GrTexture* texture);
+ // Convert mask generation results to a signed distance field
+ void toSDF(unsigned char* sdf);
+
// Reset the internal bitmap
void clear(uint8_t alpha) {
fBM.eraseColor(SkColorSetARGB(alpha, alpha, alpha, alpha));
}
// Canonical usage utility that draws a single path and uploads it
- // to the GPU. The result is returned in "result".
+ // to the GPU. The result is returned.
static GrTexture* DrawPathMaskToTexture(GrContext* context,
const SkPath& path,
const SkStrokeRec& stroke,
diff --git a/src/gpu/effects/GrDistanceFieldTextureEffect.cpp b/src/gpu/effects/GrDistanceFieldTextureEffect.cpp
index 245a035..265f112 100755
--- a/src/gpu/effects/GrDistanceFieldTextureEffect.cpp
+++ b/src/gpu/effects/GrDistanceFieldTextureEffect.cpp
@@ -86,7 +86,7 @@
fsBuilder->codeAppend("\tfloat afwidth;\n");
if (dfTexEffect.getFlags() & kSimilarity_DistanceFieldEffectFlag) {
// this gives us a smooth step across approximately one fragment
- fsBuilder->codeAppend("\tafwidth = " SK_DistanceFieldAAFactor "*dFdx(st.x);\n");
+ fsBuilder->codeAppend("\tafwidth = abs(" SK_DistanceFieldAAFactor "*dFdx(st.x));\n");
} else {
fsBuilder->codeAppend("\tvec2 Jdx = dFdx(st);\n");
fsBuilder->codeAppend("\tvec2 Jdy = dFdy(st);\n");
@@ -206,8 +206,7 @@
fFlags == cte.fFlags;
}
-void GrDistanceFieldTextureEffect::onComputeInvariantOutput(
- InvariantOutput* inout) const {
+void GrDistanceFieldTextureEffect::onComputeInvariantOutput(InvariantOutput* inout) const {
if (inout->isOpaque() && GrPixelConfigIsOpaque(this->texture(0)->config())) {
inout->fValidFlags = kA_GrColorComponentFlag;
} else {
@@ -261,6 +260,177 @@
///////////////////////////////////////////////////////////////////////////////
+class GrGLDistanceFieldNoGammaTextureEffect : public GrGLGeometryProcessor {
+public:
+ GrGLDistanceFieldNoGammaTextureEffect(const GrBackendProcessorFactory& factory,
+ const GrProcessor& effect)
+ : INHERITED(factory)
+ , fTextureSize(SkISize::Make(-1, -1)) {}
+
+ virtual void emitCode(GrGLFullProgramBuilder* builder,
+ const GrGeometryProcessor& effect,
+ const GrProcessorKey& key,
+ const char* outputColor,
+ const char* inputColor,
+ const TransformedCoordsArray&,
+ const TextureSamplerArray& samplers) SK_OVERRIDE {
+ const GrDistanceFieldNoGammaTextureEffect& dfTexEffect =
+ effect.cast<GrDistanceFieldNoGammaTextureEffect>();
+ SkASSERT(1 == dfTexEffect.getVertexAttribs().count());
+
+ GrGLProcessorFragmentShaderBuilder* fsBuilder = builder->getFragmentShaderBuilder();
+ SkAssertResult(fsBuilder->enableFeature(
+ GrGLFragmentShaderBuilder::kStandardDerivatives_GLSLFeature));
+
+ SkString fsCoordName;
+ const char* vsCoordName;
+ const char* fsCoordNamePtr;
+ builder->addVarying(kVec2f_GrSLType, "textureCoords", &vsCoordName, &fsCoordNamePtr);
+ fsCoordName = fsCoordNamePtr;
+
+ GrGLVertexShaderBuilder* vsBuilder = builder->getVertexShaderBuilder();
+ vsBuilder->codeAppendf("%s = %s;", vsCoordName, dfTexEffect.inTextureCoords().c_str());
+
+ const char* textureSizeUniName = NULL;
+ fTextureSizeUni = builder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
+ kVec2f_GrSLType, "TextureSize",
+ &textureSizeUniName);
+
+ fsBuilder->codeAppend("vec4 texColor = ");
+ fsBuilder->appendTextureLookup(samplers[0],
+ fsCoordName.c_str(),
+ kVec2f_GrSLType);
+ fsBuilder->codeAppend(";");
+ fsBuilder->codeAppend("float distance = "
+ SK_DistanceFieldMultiplier "*(texColor.r - " SK_DistanceFieldThreshold ");");
+
+ // we adjust for the effect of the transformation on the distance by using
+ // the length of the gradient of the texture coordinates. We use st coordinates
+ // to ensure we're mapping 1:1 from texel space to pixel space.
+ fsBuilder->codeAppendf("vec2 uv = %s;", fsCoordName.c_str());
+ fsBuilder->codeAppendf("vec2 st = uv*%s;", textureSizeUniName);
+ fsBuilder->codeAppend("float afwidth;");
+ if (dfTexEffect.getFlags() & kSimilarity_DistanceFieldEffectFlag) {
+ // this gives us a smooth step across approximately one fragment
+ fsBuilder->codeAppend("afwidth = abs(" SK_DistanceFieldAAFactor "*dFdx(st.x));");
+ } else {
+ fsBuilder->codeAppend("vec2 Jdx = dFdx(st);");
+ fsBuilder->codeAppend("vec2 Jdy = dFdy(st);");
+
+ fsBuilder->codeAppend("vec2 uv_grad;");
+ if (builder->ctxInfo().caps()->dropsTileOnZeroDivide()) {
+ // this is to compensate for the Adreno, which likes to drop tiles on division by 0
+ fsBuilder->codeAppend("float uv_len2 = dot(uv, uv);");
+ fsBuilder->codeAppend("if (uv_len2 < 0.0001) {");
+ fsBuilder->codeAppend("uv_grad = vec2(0.7071, 0.7071);");
+ fsBuilder->codeAppend("} else {");
+ fsBuilder->codeAppend("uv_grad = uv*inversesqrt(uv_len2);");
+ fsBuilder->codeAppend("}");
+ } else {
+ fsBuilder->codeAppend("uv_grad = normalize(uv);");
+ }
+ fsBuilder->codeAppend("vec2 grad = vec2(uv_grad.x*Jdx.x + uv_grad.y*Jdy.x,");
+ fsBuilder->codeAppend(" uv_grad.x*Jdx.y + uv_grad.y*Jdy.y);");
+
+ // this gives us a smooth step across approximately one fragment
+ fsBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);");
+ }
+ fsBuilder->codeAppend("float val = smoothstep(-afwidth, afwidth, distance);");
+
+ fsBuilder->codeAppendf("%s = %s;", outputColor,
+ (GrGLSLExpr4(inputColor) * GrGLSLExpr1("val")).c_str());
+ }
+
+ virtual void setData(const GrGLProgramDataManager& pdman,
+ const GrProcessor& effect) SK_OVERRIDE {
+ SkASSERT(fTextureSizeUni.isValid());
+
+ GrTexture* texture = effect.texture(0);
+ if (texture->width() != fTextureSize.width() ||
+ texture->height() != fTextureSize.height()) {
+ fTextureSize = SkISize::Make(texture->width(), texture->height());
+ pdman.set2f(fTextureSizeUni,
+ SkIntToScalar(fTextureSize.width()),
+ SkIntToScalar(fTextureSize.height()));
+ }
+ }
+
+ static inline void GenKey(const GrProcessor& effect, const GrGLCaps&,
+ GrProcessorKeyBuilder* b) {
+ const GrDistanceFieldNoGammaTextureEffect& dfTexEffect =
+ effect.cast<GrDistanceFieldNoGammaTextureEffect>();
+
+ b->add32(dfTexEffect.getFlags());
+ }
+
+private:
+ GrGLProgramDataManager::UniformHandle fTextureSizeUni;
+ SkISize fTextureSize;
+
+ typedef GrGLGeometryProcessor INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrDistanceFieldNoGammaTextureEffect::GrDistanceFieldNoGammaTextureEffect(GrTexture* texture,
+ const GrTextureParams& params,
+ uint32_t flags)
+ : fTextureAccess(texture, params)
+ , fFlags(flags & kNonLCD_DistanceFieldEffectMask)
+ , fInTextureCoords(this->addVertexAttrib(GrShaderVar("inTextureCoords",
+ kVec2f_GrSLType,
+ GrShaderVar::kAttribute_TypeModifier))) {
+ SkASSERT(!(flags & ~kNonLCD_DistanceFieldEffectMask));
+ this->addTextureAccess(&fTextureAccess);
+}
+
+bool GrDistanceFieldNoGammaTextureEffect::onIsEqual(const GrProcessor& other) const {
+ const GrDistanceFieldNoGammaTextureEffect& cte =
+ other.cast<GrDistanceFieldNoGammaTextureEffect>();
+ return fTextureAccess == cte.fTextureAccess && fFlags == cte.fFlags;
+}
+
+void GrDistanceFieldNoGammaTextureEffect::onComputeInvariantOutput(InvariantOutput* inout) const {
+ if (inout->isOpaque() && GrPixelConfigIsOpaque(this->texture(0)->config())) {
+ inout->fValidFlags = kA_GrColorComponentFlag;
+ } else {
+ inout->fValidFlags = 0;
+ }
+ inout->fIsSingleComponent = false;
+}
+
+const GrBackendGeometryProcessorFactory& GrDistanceFieldNoGammaTextureEffect::getFactory() const {
+ return GrTBackendGeometryProcessorFactory<GrDistanceFieldNoGammaTextureEffect>::getInstance();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldNoGammaTextureEffect);
+
+GrGeometryProcessor* GrDistanceFieldNoGammaTextureEffect::TestCreate(SkRandom* random,
+ GrContext*,
+ const GrDrawTargetCaps&,
+ GrTexture* textures[]) {
+ int texIdx = random->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx
+ : GrProcessorUnitTest::kAlphaTextureIdx;
+ static const SkShader::TileMode kTileModes[] = {
+ SkShader::kClamp_TileMode,
+ SkShader::kRepeat_TileMode,
+ SkShader::kMirror_TileMode,
+ };
+ SkShader::TileMode tileModes[] = {
+ kTileModes[random->nextULessThan(SK_ARRAY_COUNT(kTileModes))],
+ kTileModes[random->nextULessThan(SK_ARRAY_COUNT(kTileModes))],
+ };
+ GrTextureParams params(tileModes, random->nextBool() ? GrTextureParams::kBilerp_FilterMode
+ : GrTextureParams::kNone_FilterMode);
+
+ return GrDistanceFieldNoGammaTextureEffect::Create(textures[texIdx], params,
+ random->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
class GrGLDistanceFieldLCDTextureEffect : public GrGLGeometryProcessor {
public:
GrGLDistanceFieldLCDTextureEffect(const GrBackendProcessorFactory& factory,
@@ -347,7 +517,7 @@
fsBuilder->codeAppend("\tfloat afwidth;\n");
if (isUniformScale) {
// this gives us a smooth step across approximately one fragment
- fsBuilder->codeAppend("\tafwidth = " SK_DistanceFieldAAFactor "*dx;\n");
+ fsBuilder->codeAppend("\tafwidth = abs(" SK_DistanceFieldAAFactor "*dx);\n");
} else {
fsBuilder->codeAppend("\tvec2 uv_grad;\n");
if (builder->ctxInfo().caps()->dropsTileOnZeroDivide()) {
@@ -476,8 +646,7 @@
fFlags == cte.fFlags);
}
-void GrDistanceFieldLCDTextureEffect::onComputeInvariantOutput(
- InvariantOutput* inout) const {
+void GrDistanceFieldLCDTextureEffect::onComputeInvariantOutput(InvariantOutput* inout) const {
if (inout->isOpaque() && GrPixelConfigIsOpaque(this->texture(0)->config())) {
inout->fValidFlags = kA_GrColorComponentFlag;
} else {
diff --git a/src/gpu/effects/GrDistanceFieldTextureEffect.h b/src/gpu/effects/GrDistanceFieldTextureEffect.h
index efd622e..a49102d 100644
--- a/src/gpu/effects/GrDistanceFieldTextureEffect.h
+++ b/src/gpu/effects/GrDistanceFieldTextureEffect.h
@@ -12,6 +12,7 @@
#include "GrGeometryProcessor.h"
class GrGLDistanceFieldTextureEffect;
+class GrGLDistanceFieldNoGammaTextureEffect;
class GrGLDistanceFieldLCDTextureEffect;
enum GrDistanceFieldEffectFlags {
@@ -89,7 +90,49 @@
GR_DECLARE_GEOMETRY_PROCESSOR_TEST;
- typedef GrFragmentProcessor INHERITED;
+ typedef GrGeometryProcessor INHERITED;
+};
+
+
+/**
+* The output color of this effect is a modulation of the input color and a sample from a
+* distance field texture (using a smoothed step function near 0.5).
+* It allows explicit specification of the filtering and wrap modes (GrTextureParams). The input
+* coords are a custom attribute. No gamma correct blending is applied.
+*/
+class GrDistanceFieldNoGammaTextureEffect : public GrGeometryProcessor {
+public:
+ static GrGeometryProcessor* Create(GrTexture* tex, const GrTextureParams& params,
+ uint32_t flags) {
+ return SkNEW_ARGS(GrDistanceFieldNoGammaTextureEffect, (tex, params, flags));
+ }
+
+ virtual ~GrDistanceFieldNoGammaTextureEffect() {}
+
+ static const char* Name() { return "DistanceFieldTexture"; }
+
+ const GrShaderVar& inTextureCoords() const { return fInTextureCoords; }
+ uint32_t getFlags() const { return fFlags; }
+
+ typedef GrGLDistanceFieldNoGammaTextureEffect GLProcessor;
+
+ virtual const GrBackendGeometryProcessorFactory& getFactory() const SK_OVERRIDE;
+
+private:
+ GrDistanceFieldNoGammaTextureEffect(GrTexture* texture, const GrTextureParams& params,
+ uint32_t flags);
+
+ virtual bool onIsEqual(const GrProcessor& other) const SK_OVERRIDE;
+
+ virtual void onComputeInvariantOutput(InvariantOutput* inout) const SK_OVERRIDE;
+
+ GrTextureAccess fTextureAccess;
+ uint32_t fFlags;
+ const GrShaderVar& fInTextureCoords;
+
+ GR_DECLARE_GEOMETRY_PROCESSOR_TEST;
+
+ typedef GrGeometryProcessor INHERITED;
};
/**
@@ -137,7 +180,7 @@
GR_DECLARE_GEOMETRY_PROCESSOR_TEST;
- typedef GrFragmentProcessor INHERITED;
+ typedef GrGeometryProcessor INHERITED;
};
#endif