blob: 5e0410fd751994459bf392561b962413f0d05652 [file] [log] [blame]
/*
* 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 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)
SK_CONF_DECLARE(bool, c_DumpPathCache, "gpu.dumpPathCache", false,
"Dump the contents of the path cache before every purge.");
#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;
////////////////////////////////////////////////////////////////////////////////
GrAADistanceFieldPathRenderer::GrAADistanceFieldPathRenderer(GrContext* context)
: fContext(context)
, fAtlas(NULL)
, fEffectFlags(kInvalid_DistanceFieldEffectFlag) {
}
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);
#ifdef DF_PATH_TRACKING
SkDebugf("Cached paths: %d, freed paths: %d\n", g_NumCachedPaths, g_NumFreedPaths);
#endif
}
////////////////////////////////////////////////////////////////////////////////
bool GrAADistanceFieldPathRenderer::canDrawPath(const GrDrawTarget* target,
const GrDrawState* drawState,
const SkPath& path,
const SkStrokeRec& stroke,
bool antiAlias) const {
// TODO: Support inverse fill
// TODO: Support strokes
if (!target->caps()->shaderDerivativeSupport() || !antiAlias || path.isInverseFillType()
|| path.isVolatile() || SkStrokeRec::kFill_Style != stroke.getStyle()) {
return false;
}
// currently don't support perspective
const SkMatrix& vm = drawState->getViewMatrix();
if (vm.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 = vm.getMaxScale();
const SkRect& bounds = path.getBounds();
SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height());
return maxDim < 64.f && maxDim*maxScale < 256.f;
}
GrPathRenderer::StencilSupport
GrAADistanceFieldPathRenderer::onGetStencilSupport(const GrDrawTarget*,
const GrDrawState*,
const SkPath&,
const SkStrokeRec&) 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(GrDrawTarget* target,
GrDrawState* drawState,
const SkPath& path,
const SkStrokeRec& stroke,
bool antiAlias) {
// we've already bailed on inverse filled paths, so this is safe
if (path.isEmpty()) {
return true;
}
SkASSERT(fContext);
// get mip level
const SkMatrix& vm = drawState->getViewMatrix();
SkScalar maxScale = vm.getMaxScale();
const SkRect& bounds = path.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 = { path.getGenerationID(), desiredDimension };
PathData* pathData = fPathCache.find(key);
if (NULL == pathData) {
SkScalar scale = desiredDimension/maxDim;
pathData = this->addPathToAtlas(path, stroke, antiAlias, desiredDimension, scale);
if (NULL == pathData) {
return false;
}
}
// use signed distance field to render
return this->internalDrawPath(target, drawState, path, pathData);
}
// 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,
uint32_t dimension,
SkScalar scale) {
// 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_GrSurfaceFlags, 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 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);
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->fKey.fGenID = path.getGenerationID();
pathData->fKey.fDimension = dimension;
pathData->fScale = scale;
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);
#ifdef DF_PATH_TRACKING
++g_NumCachedPaths;
#endif
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->fKey);
fPathList.remove(pathData);
SkDELETE(pathData);
#ifdef DF_PATH_TRACKING
++g_NumFreedPaths;
#endif
}
}
// tell the atlas to free the plot
GrAtlas::RemovePlot(&fPlotUsage, plot);
return true;
}
bool GrAADistanceFieldPathRenderer::internalDrawPath(GrDrawTarget* target,
GrDrawState* drawState,
const SkPath& path,
const PathData* pathData) {
GrTexture* texture = fAtlas->getTexture();
GrDrawState::AutoRestoreEffects are(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;
bool success = target->reserveVertexAndIndexSpace(4, drawState->getVertexStride(), 0, &vertices,
NULL);
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/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
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);
// 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);
if (flags != fEffectFlags) {
fCachedGeometryProcessor.reset(GrDistanceFieldNoGammaTextureEffect::Create(texture,
params,
flags));
fEffectFlags = flags;
}
drawState->setGeometryProcessor(fCachedGeometryProcessor.get());
vm.mapRect(&r);
target->setIndexSourceToBuffer(fContext->getQuadIndexBuffer());
target->drawIndexedInstances(drawState, kTriangles_GrPrimitiveType, 1, 4, 6, &r);
target->resetVertexSource();
return true;
}