
/*
 * 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 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()
        || path.isVolatile() || SkStrokeRec::kFill_Style != stroke.getStyle()) {
        return false;
    }

    // currently don't support perspective
    const GrDrawState& drawState = target->getDrawState();
    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 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);

    // get mip level
    const GrDrawState& drawState = target->getDrawState();
    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(path, pathData, target);
}

// 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(const SkPath& path,
                                                     const PathData* pathData,
                                                     GrDrawTarget* target) {
    GrTexture* texture = fAtlas->getTexture();
    GrDrawState* drawState = target->drawState();
    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, 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(kTriangles_GrPrimitiveType, 1, 4, 6, &r);
    target->resetVertexSource();
    
    return true;
}

