blob: dbed7839f80b8c8b91da867388223ace3f12c4f8 [file] [log] [blame]
/*
* Copyright 2011 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "GrDefaultPathRenderer.h"
#include "GrContext.h"
#include "GrDrawState.h"
#include "GrPathUtils.h"
#include "SkString.h"
#include "SkStrokeRec.h"
#include "SkTrace.h"
GrDefaultPathRenderer::GrDefaultPathRenderer(bool separateStencilSupport,
bool stencilWrapOpsSupport)
: fSeparateStencil(separateStencilSupport)
, fStencilWrapOps(stencilWrapOpsSupport) {
}
////////////////////////////////////////////////////////////////////////////////
// Stencil rules for paths
////// Even/Odd
GR_STATIC_CONST_SAME_STENCIL(gEOStencilPass,
kInvert_StencilOp,
kKeep_StencilOp,
kAlwaysIfInClip_StencilFunc,
0xffff,
0xffff,
0xffff);
// ok not to check clip b/c stencil pass only wrote inside clip
GR_STATIC_CONST_SAME_STENCIL(gEOColorPass,
kZero_StencilOp,
kZero_StencilOp,
kNotEqual_StencilFunc,
0xffff,
0x0000,
0xffff);
// have to check clip b/c outside clip will always be zero.
GR_STATIC_CONST_SAME_STENCIL(gInvEOColorPass,
kZero_StencilOp,
kZero_StencilOp,
kEqualIfInClip_StencilFunc,
0xffff,
0x0000,
0xffff);
////// Winding
// when we have separate stencil we increment front faces / decrement back faces
// when we don't have wrap incr and decr we use the stencil test to simulate
// them.
GR_STATIC_CONST_STENCIL(gWindStencilSeparateWithWrap,
kIncWrap_StencilOp, kDecWrap_StencilOp,
kKeep_StencilOp, kKeep_StencilOp,
kAlwaysIfInClip_StencilFunc, kAlwaysIfInClip_StencilFunc,
0xffff, 0xffff,
0xffff, 0xffff,
0xffff, 0xffff);
// if inc'ing the max value, invert to make 0
// if dec'ing zero invert to make all ones.
// we can't avoid touching the stencil on both passing and
// failing, so we can't resctrict ourselves to the clip.
GR_STATIC_CONST_STENCIL(gWindStencilSeparateNoWrap,
kInvert_StencilOp, kInvert_StencilOp,
kIncClamp_StencilOp, kDecClamp_StencilOp,
kEqual_StencilFunc, kEqual_StencilFunc,
0xffff, 0xffff,
0xffff, 0x0000,
0xffff, 0xffff);
// When there are no separate faces we do two passes to setup the winding rule
// stencil. First we draw the front faces and inc, then we draw the back faces
// and dec. These are same as the above two split into the incrementing and
// decrementing passes.
GR_STATIC_CONST_SAME_STENCIL(gWindSingleStencilWithWrapInc,
kIncWrap_StencilOp,
kKeep_StencilOp,
kAlwaysIfInClip_StencilFunc,
0xffff,
0xffff,
0xffff);
GR_STATIC_CONST_SAME_STENCIL(gWindSingleStencilWithWrapDec,
kDecWrap_StencilOp,
kKeep_StencilOp,
kAlwaysIfInClip_StencilFunc,
0xffff,
0xffff,
0xffff);
GR_STATIC_CONST_SAME_STENCIL(gWindSingleStencilNoWrapInc,
kInvert_StencilOp,
kIncClamp_StencilOp,
kEqual_StencilFunc,
0xffff,
0xffff,
0xffff);
GR_STATIC_CONST_SAME_STENCIL(gWindSingleStencilNoWrapDec,
kInvert_StencilOp,
kDecClamp_StencilOp,
kEqual_StencilFunc,
0xffff,
0x0000,
0xffff);
// Color passes are the same whether we use the two-sided stencil or two passes
GR_STATIC_CONST_SAME_STENCIL(gWindColorPass,
kZero_StencilOp,
kZero_StencilOp,
kNonZeroIfInClip_StencilFunc,
0xffff,
0x0000,
0xffff);
GR_STATIC_CONST_SAME_STENCIL(gInvWindColorPass,
kZero_StencilOp,
kZero_StencilOp,
kEqualIfInClip_StencilFunc,
0xffff,
0x0000,
0xffff);
////// Normal render to stencil
// Sometimes the default path renderer can draw a path directly to the stencil
// buffer without having to first resolve the interior / exterior.
GR_STATIC_CONST_SAME_STENCIL(gDirectToStencil,
kZero_StencilOp,
kIncClamp_StencilOp,
kAlwaysIfInClip_StencilFunc,
0xffff,
0x0000,
0xffff);
////////////////////////////////////////////////////////////////////////////////
// Helpers for drawPath
#define STENCIL_OFF 0 // Always disable stencil (even when needed)
static inline bool single_pass_path(const SkPath& path, const SkStrokeRec& stroke) {
#if STENCIL_OFF
return true;
#else
if (!stroke.isHairlineStyle() && !path.isInverseFillType()) {
return path.isConvex();
}
return false;
#endif
}
GrPathRenderer::StencilSupport GrDefaultPathRenderer::onGetStencilSupport(
const SkPath& path,
const SkStrokeRec& stroke,
const GrDrawTarget*) const {
if (single_pass_path(path, stroke)) {
return GrPathRenderer::kNoRestriction_StencilSupport;
} else {
return GrPathRenderer::kStencilOnly_StencilSupport;
}
}
static inline void append_countour_edge_indices(bool hairLine,
uint16_t fanCenterIdx,
uint16_t edgeV0Idx,
uint16_t** indices) {
// when drawing lines we're appending line segments along
// the contour. When applying the other fill rules we're
// drawing triangle fans around fanCenterIdx.
if (!hairLine) {
*((*indices)++) = fanCenterIdx;
}
*((*indices)++) = edgeV0Idx;
*((*indices)++) = edgeV0Idx + 1;
}
bool GrDefaultPathRenderer::createGeom(const SkPath& path,
const SkStrokeRec& stroke,
SkScalar srcSpaceTol,
GrDrawTarget* target,
GrPrimitiveType* primType,
int* vertexCnt,
int* indexCnt,
GrDrawTarget::AutoReleaseGeometry* arg) {
{
SK_TRACE_EVENT0("GrDefaultPathRenderer::createGeom");
SkScalar srcSpaceTolSqd = SkScalarMul(srcSpaceTol, srcSpaceTol);
int contourCnt;
int maxPts = GrPathUtils::worstCasePointCount(path, &contourCnt,
srcSpaceTol);
if (maxPts <= 0) {
return false;
}
if (maxPts > ((int)SK_MaxU16 + 1)) {
GrPrintf("Path not rendered, too many verts (%d)\n", maxPts);
return false;
}
GrVertexLayout layout = 0;
bool indexed = contourCnt > 1;
const bool isHairline = stroke.isHairlineStyle();
int maxIdxs = 0;
if (isHairline) {
if (indexed) {
maxIdxs = 2 * maxPts;
*primType = kLines_GrPrimitiveType;
} else {
*primType = kLineStrip_GrPrimitiveType;
}
} else {
if (indexed) {
maxIdxs = 3 * maxPts;
*primType = kTriangles_GrPrimitiveType;
} else {
*primType = kTriangleFan_GrPrimitiveType;
}
}
if (!arg->set(target, layout, maxPts, maxIdxs)) {
return false;
}
uint16_t* idxBase = reinterpret_cast<uint16_t*>(arg->indices());
uint16_t* idx = idxBase;
uint16_t subpathIdxStart = 0;
GrPoint* base = reinterpret_cast<GrPoint*>(arg->vertices());
GrAssert(NULL != base);
GrPoint* vert = base;
GrPoint pts[4];
bool first = true;
int subpath = 0;
SkPath::Iter iter(path, false);
for (;;) {
GrPathCmd cmd = (GrPathCmd)iter.next(pts);
switch (cmd) {
case kMove_PathCmd:
if (!first) {
uint16_t currIdx = (uint16_t) (vert - base);
subpathIdxStart = currIdx;
++subpath;
}
*vert = pts[0];
vert++;
break;
case kLine_PathCmd:
if (indexed) {
uint16_t prevIdx = (uint16_t)(vert - base) - 1;
append_countour_edge_indices(isHairline, subpathIdxStart,
prevIdx, &idx);
}
*(vert++) = pts[1];
break;
case kQuadratic_PathCmd: {
// first pt of quad is the pt we ended on in previous step
uint16_t firstQPtIdx = (uint16_t)(vert - base) - 1;
uint16_t numPts = (uint16_t)
GrPathUtils::generateQuadraticPoints(
pts[0], pts[1], pts[2],
srcSpaceTolSqd, &vert,
GrPathUtils::quadraticPointCount(pts, srcSpaceTol));
if (indexed) {
for (uint16_t i = 0; i < numPts; ++i) {
append_countour_edge_indices(isHairline, subpathIdxStart,
firstQPtIdx + i, &idx);
}
}
break;
}
case kCubic_PathCmd: {
// first pt of cubic is the pt we ended on in previous step
uint16_t firstCPtIdx = (uint16_t)(vert - base) - 1;
uint16_t numPts = (uint16_t) GrPathUtils::generateCubicPoints(
pts[0], pts[1], pts[2], pts[3],
srcSpaceTolSqd, &vert,
GrPathUtils::cubicPointCount(pts, srcSpaceTol));
if (indexed) {
for (uint16_t i = 0; i < numPts; ++i) {
append_countour_edge_indices(isHairline, subpathIdxStart,
firstCPtIdx + i, &idx);
}
}
break;
}
case kClose_PathCmd:
break;
case kEnd_PathCmd:
// uint16_t currIdx = (uint16_t) (vert - base);
goto FINISHED;
}
first = false;
}
FINISHED:
GrAssert((vert - base) <= maxPts);
GrAssert((idx - idxBase) <= maxIdxs);
*vertexCnt = vert - base;
*indexCnt = idx - idxBase;
}
return true;
}
bool GrDefaultPathRenderer::internalDrawPath(const SkPath& path,
const SkStrokeRec& stroke,
GrDrawTarget* target,
bool stencilOnly) {
SkMatrix viewM = target->getDrawState().getViewMatrix();
SkScalar tol = SK_Scalar1;
tol = GrPathUtils::scaleToleranceToSrc(tol, viewM, path.getBounds());
int vertexCnt;
int indexCnt;
GrPrimitiveType primType;
GrDrawTarget::AutoReleaseGeometry arg;
if (!this->createGeom(path,
stroke,
tol,
target,
&primType,
&vertexCnt,
&indexCnt,
&arg)) {
return false;
}
GrAssert(NULL != target);
GrDrawTarget::AutoStateRestore asr(target, GrDrawTarget::kPreserve_ASRInit);
GrDrawState* drawState = target->drawState();
bool colorWritesWereDisabled = drawState->isColorWriteDisabled();
// face culling doesn't make sense here
GrAssert(GrDrawState::kBoth_DrawFace == drawState->getDrawFace());
int passCount = 0;
const GrStencilSettings* passes[3];
GrDrawState::DrawFace drawFace[3];
bool reverse = false;
bool lastPassIsBounds;
if (stroke.isHairlineStyle()) {
passCount = 1;
if (stencilOnly) {
passes[0] = &gDirectToStencil;
} else {
passes[0] = NULL;
}
lastPassIsBounds = false;
drawFace[0] = GrDrawState::kBoth_DrawFace;
} else {
if (single_pass_path(path, stroke)) {
passCount = 1;
if (stencilOnly) {
passes[0] = &gDirectToStencil;
} else {
passes[0] = NULL;
}
drawFace[0] = GrDrawState::kBoth_DrawFace;
lastPassIsBounds = false;
} else {
switch (path.getFillType()) {
case SkPath::kInverseEvenOdd_FillType:
reverse = true;
// fallthrough
case SkPath::kEvenOdd_FillType:
passes[0] = &gEOStencilPass;
if (stencilOnly) {
passCount = 1;
lastPassIsBounds = false;
} else {
passCount = 2;
lastPassIsBounds = true;
if (reverse) {
passes[1] = &gInvEOColorPass;
} else {
passes[1] = &gEOColorPass;
}
}
drawFace[0] = drawFace[1] = GrDrawState::kBoth_DrawFace;
break;
case SkPath::kInverseWinding_FillType:
reverse = true;
// fallthrough
case SkPath::kWinding_FillType:
if (fSeparateStencil) {
if (fStencilWrapOps) {
passes[0] = &gWindStencilSeparateWithWrap;
} else {
passes[0] = &gWindStencilSeparateNoWrap;
}
passCount = 2;
drawFace[0] = GrDrawState::kBoth_DrawFace;
} else {
if (fStencilWrapOps) {
passes[0] = &gWindSingleStencilWithWrapInc;
passes[1] = &gWindSingleStencilWithWrapDec;
} else {
passes[0] = &gWindSingleStencilNoWrapInc;
passes[1] = &gWindSingleStencilNoWrapDec;
}
// which is cw and which is ccw is arbitrary.
drawFace[0] = GrDrawState::kCW_DrawFace;
drawFace[1] = GrDrawState::kCCW_DrawFace;
passCount = 3;
}
if (stencilOnly) {
lastPassIsBounds = false;
--passCount;
} else {
lastPassIsBounds = true;
drawFace[passCount-1] = GrDrawState::kBoth_DrawFace;
if (reverse) {
passes[passCount-1] = &gInvWindColorPass;
} else {
passes[passCount-1] = &gWindColorPass;
}
}
break;
default:
GrAssert(!"Unknown path fFill!");
return false;
}
}
}
{
for (int p = 0; p < passCount; ++p) {
drawState->setDrawFace(drawFace[p]);
if (NULL != passes[p]) {
*drawState->stencil() = *passes[p];
}
if (lastPassIsBounds && (p == passCount-1)) {
if (!colorWritesWereDisabled) {
drawState->disableState(GrDrawState::kNoColorWrites_StateBit);
}
GrRect bounds;
GrDrawState::AutoDeviceCoordDraw adcd;
if (reverse) {
GrAssert(NULL != drawState->getRenderTarget());
// draw over the whole world.
bounds.setLTRB(0, 0,
SkIntToScalar(drawState->getRenderTarget()->width()),
SkIntToScalar(drawState->getRenderTarget()->height()));
SkMatrix vmi;
// mapRect through persp matrix may not be correct
if (!drawState->getViewMatrix().hasPerspective() &&
drawState->getViewInverse(&vmi)) {
vmi.mapRect(&bounds);
} else {
adcd.set(drawState);
}
} else {
bounds = path.getBounds();
}
GrDrawTarget::AutoGeometryPush agp(target);
target->drawSimpleRect(bounds, NULL);
} else {
if (passCount > 1) {
drawState->enableState(GrDrawState::kNoColorWrites_StateBit);
}
if (indexCnt) {
target->drawIndexed(primType, 0, 0,
vertexCnt, indexCnt);
} else {
target->drawNonIndexed(primType, 0, vertexCnt);
}
}
}
}
return true;
}
bool GrDefaultPathRenderer::canDrawPath(const SkPath& path,
const SkStrokeRec& stroke,
const GrDrawTarget* target,
bool antiAlias) const {
// this class can draw any path with any fill but doesn't do any anti-aliasing.
return (stroke.isFillStyle() || stroke.isHairlineStyle()) && !antiAlias;
}
bool GrDefaultPathRenderer::onDrawPath(const SkPath& path,
const SkStrokeRec& stroke,
GrDrawTarget* target,
bool antiAlias) {
return this->internalDrawPath(path,
stroke,
target,
false);
}
void GrDefaultPathRenderer::onStencilPath(const SkPath& path,
const SkStrokeRec& stroke,
GrDrawTarget* target) {
GrAssert(SkPath::kInverseEvenOdd_FillType != path.getFillType());
GrAssert(SkPath::kInverseWinding_FillType != path.getFillType());
this->internalDrawPath(path, stroke, target, true);
}