Use a prioritized list of path renderers in Gr.
http://codereview.appspot.com/4867058
git-svn-id: http://skia.googlecode.com/svn/trunk@2143 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gpu/src/GrDefaultPathRenderer.cpp b/gpu/src/GrDefaultPathRenderer.cpp
new file mode 100644
index 0000000..b8b7f62
--- /dev/null
+++ b/gpu/src/GrDefaultPathRenderer.cpp
@@ -0,0 +1,562 @@
+
+/*
+ * 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 "GrPathUtils.h"
+#include "SkTrace.h"
+
+
+GrDefaultPathRenderer::GrDefaultPathRenderer(bool separateStencilSupport,
+ bool stencilWrapOpsSupport)
+ : fSeparateStencil(separateStencilSupport)
+ , fStencilWrapOps(stencilWrapOpsSupport)
+ , fSubpathCount(0)
+ , fSubpathVertCount(0)
+ , fPreviousSrcTol(-GR_Scalar1)
+ , fPreviousStages(-1) {
+ fTarget = NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Stencil rules for paths
+
+////// Even/Odd
+
+static const GrStencilSettings gEOStencilPass = {
+ kInvert_StencilOp, kInvert_StencilOp,
+ kKeep_StencilOp, kKeep_StencilOp,
+ kAlwaysIfInClip_StencilFunc, kAlwaysIfInClip_StencilFunc,
+ 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff
+};
+
+// ok not to check clip b/c stencil pass only wrote inside clip
+static const GrStencilSettings gEOColorPass = {
+ kZero_StencilOp, kZero_StencilOp,
+ kZero_StencilOp, kZero_StencilOp,
+ kNotEqual_StencilFunc, kNotEqual_StencilFunc,
+ 0xffffffff, 0xffffffff,
+ 0x0, 0x0,
+ 0xffffffff, 0xffffffff
+};
+
+// have to check clip b/c outside clip will always be zero.
+static const GrStencilSettings gInvEOColorPass = {
+ kZero_StencilOp, kZero_StencilOp,
+ kZero_StencilOp, kZero_StencilOp,
+ kEqualIfInClip_StencilFunc, kEqualIfInClip_StencilFunc,
+ 0xffffffff, 0xffffffff,
+ 0x0, 0x0,
+ 0xffffffff, 0xffffffff
+};
+
+////// 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.
+
+static const GrStencilSettings gWindStencilSeparateWithWrap = {
+ kIncWrap_StencilOp, kDecWrap_StencilOp,
+ kKeep_StencilOp, kKeep_StencilOp,
+ kAlwaysIfInClip_StencilFunc, kAlwaysIfInClip_StencilFunc,
+ 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff
+};
+
+// 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.
+static const GrStencilSettings gWindStencilSeparateNoWrap = {
+ kInvert_StencilOp, kInvert_StencilOp,
+ kIncClamp_StencilOp, kDecClamp_StencilOp,
+ kEqual_StencilFunc, kEqual_StencilFunc,
+ 0xffffffff, 0xffffffff,
+ 0xffffffff, 0x0,
+ 0xffffffff, 0xffffffff
+};
+
+// 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.
+static const GrStencilSettings gWindSingleStencilWithWrapInc = {
+ kIncWrap_StencilOp, kIncWrap_StencilOp,
+ kKeep_StencilOp, kKeep_StencilOp,
+ kAlwaysIfInClip_StencilFunc, kAlwaysIfInClip_StencilFunc,
+ 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff
+};
+static const GrStencilSettings gWindSingleStencilWithWrapDec = {
+ kDecWrap_StencilOp, kDecWrap_StencilOp,
+ kKeep_StencilOp, kKeep_StencilOp,
+ kAlwaysIfInClip_StencilFunc, kAlwaysIfInClip_StencilFunc,
+ 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff
+};
+static const GrStencilSettings gWindSingleStencilNoWrapInc = {
+ kInvert_StencilOp, kInvert_StencilOp,
+ kIncClamp_StencilOp, kIncClamp_StencilOp,
+ kEqual_StencilFunc, kEqual_StencilFunc,
+ 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff,
+ 0xffffffff, 0xffffffff
+};
+static const GrStencilSettings gWindSingleStencilNoWrapDec = {
+ kInvert_StencilOp, kInvert_StencilOp,
+ kDecClamp_StencilOp, kDecClamp_StencilOp,
+ kEqual_StencilFunc, kEqual_StencilFunc,
+ 0xffffffff, 0xffffffff,
+ 0x0, 0x0,
+ 0xffffffff, 0xffffffff
+};
+
+static const GrStencilSettings gWindColorPass = {
+ kZero_StencilOp, kZero_StencilOp,
+ kZero_StencilOp, kZero_StencilOp,
+ kNonZeroIfInClip_StencilFunc, kNonZeroIfInClip_StencilFunc,
+ 0xffffffff, 0xffffffff,
+ 0x0, 0x0,
+ 0xffffffff, 0xffffffff
+};
+
+static const GrStencilSettings gInvWindColorPass = {
+ kZero_StencilOp, kZero_StencilOp,
+ kZero_StencilOp, kZero_StencilOp,
+ kEqualIfInClip_StencilFunc, kEqualIfInClip_StencilFunc,
+ 0xffffffff, 0xffffffff,
+ 0x0, 0x0,
+ 0xffffffff, 0xffffffff
+};
+
+////// 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.
+static const GrStencilSettings gDirectToStencil = {
+ kZero_StencilOp, kZero_StencilOp,
+ kIncClamp_StencilOp, kIncClamp_StencilOp,
+ kAlwaysIfInClip_StencilFunc, kAlwaysIfInClip_StencilFunc,
+ 0xffffffff, 0xffffffff,
+ 0x0, 0x0,
+ 0xffffffff, 0xffffffff
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// Helpers for drawPath
+
+static GrConvexHint getConvexHint(const SkPath& path) {
+ return path.isConvex() ? kConvex_ConvexHint : kConcave_ConvexHint;
+}
+
+#define STENCIL_OFF 0 // Always disable stencil (even when needed)
+
+static inline bool single_pass_path(const GrDrawTarget& target,
+ const GrPath& path,
+ GrPathFill fill) {
+#if STENCIL_OFF
+ return true;
+#else
+ if (kEvenOdd_PathFill == fill) {
+ GrConvexHint hint = getConvexHint(path);
+ return hint == kConvex_ConvexHint ||
+ hint == kNonOverlappingConvexPieces_ConvexHint;
+ } else if (kWinding_PathFill == fill) {
+ GrConvexHint hint = getConvexHint(path);
+ return hint == kConvex_ConvexHint ||
+ hint == kNonOverlappingConvexPieces_ConvexHint ||
+ (hint == kSameWindingConvexPieces_ConvexHint &&
+ target.canDisableBlend() && !target.isDitherState());
+
+ }
+ return false;
+#endif
+}
+
+bool GrDefaultPathRenderer::requiresStencilPass(const GrDrawTarget* target,
+ const GrPath& path,
+ GrPathFill fill) const {
+ return !single_pass_path(*target, path, fill);
+}
+
+void GrDefaultPathRenderer::pathWillClear() {
+ fSubpathVertCount.realloc(0);
+ fTarget->resetVertexSource();
+ if (fUseIndexedDraw) {
+ fTarget->resetIndexSource();
+ }
+ fPreviousSrcTol = -GR_Scalar1;
+ fPreviousStages = -1;
+}
+
+static inline void append_countour_edge_indices(GrPathFill fillType,
+ 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 (kHairLine_PathFill != fillType) {
+ *((*indices)++) = fanCenterIdx;
+ }
+ *((*indices)++) = edgeV0Idx;
+ *((*indices)++) = edgeV0Idx + 1;
+}
+
+bool GrDefaultPathRenderer::createGeom(GrScalar srcSpaceTol,
+ GrDrawTarget::StageBitfield stages) {
+ {
+ SK_TRACE_EVENT0("GrDefaultPathRenderer::createGeom");
+
+ GrScalar srcSpaceTolSqd = GrMul(srcSpaceTol, srcSpaceTol);
+ int maxPts = GrPathUtils::worstCasePointCount(*fPath, &fSubpathCount,
+ 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;
+ for (int s = 0; s < GrDrawTarget::kNumStages; ++s) {
+ if ((1 << s) & stages) {
+ layout |= GrDrawTarget::StagePosAsTexCoordVertexLayoutBit(s);
+ }
+ }
+
+ fUseIndexedDraw = fSubpathCount > 1;
+
+ int maxIdxs = 0;
+ if (kHairLine_PathFill == fFill) {
+ if (fUseIndexedDraw) {
+ maxIdxs = 2 * maxPts;
+ fPrimitiveType = kLines_PrimitiveType;
+ } else {
+ fPrimitiveType = kLineStrip_PrimitiveType;
+ }
+ } else {
+ if (fUseIndexedDraw) {
+ maxIdxs = 3 * maxPts;
+ fPrimitiveType = kTriangles_PrimitiveType;
+ } else {
+ fPrimitiveType = kTriangleFan_PrimitiveType;
+ }
+ }
+
+ GrPoint* base;
+ if (!fTarget->reserveVertexSpace(layout, maxPts, (void**)&base)) {
+ return false;
+ }
+ GrAssert(NULL != base);
+ GrPoint* vert = base;
+
+ uint16_t* idxBase = NULL;
+ uint16_t* idx = NULL;
+ uint16_t subpathIdxStart = 0;
+ if (fUseIndexedDraw) {
+ if (!fTarget->reserveIndexSpace(maxIdxs, (void**)&idxBase)) {
+ fTarget->resetVertexSource();
+ return false;
+ }
+ GrAssert(NULL != idxBase);
+ idx = idxBase;
+ }
+
+ fSubpathVertCount.realloc(fSubpathCount);
+
+ GrPoint pts[4];
+
+ bool first = true;
+ int subpath = 0;
+
+ SkPath::Iter iter(*fPath, false);
+
+ for (;;) {
+ GrPathCmd cmd = (GrPathCmd)iter.next(pts);
+ switch (cmd) {
+ case kMove_PathCmd:
+ if (!first) {
+ uint16_t currIdx = (uint16_t) (vert - base);
+ fSubpathVertCount[subpath] = currIdx - subpathIdxStart;
+ subpathIdxStart = currIdx;
+ ++subpath;
+ }
+ *vert = pts[0];
+ vert++;
+ break;
+ case kLine_PathCmd:
+ if (fUseIndexedDraw) {
+ uint16_t prevIdx = (uint16_t)(vert - base) - 1;
+ append_countour_edge_indices(fFill, 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 (fUseIndexedDraw) {
+ for (uint16_t i = 0; i < numPts; ++i) {
+ append_countour_edge_indices(fFill, 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 (fUseIndexedDraw) {
+ for (uint16_t i = 0; i < numPts; ++i) {
+ append_countour_edge_indices(fFill, subpathIdxStart,
+ firstCPtIdx + i, &idx);
+ }
+ }
+ break;
+ }
+ case kClose_PathCmd:
+ break;
+ case kEnd_PathCmd:
+ uint16_t currIdx = (uint16_t) (vert - base);
+ fSubpathVertCount[subpath] = currIdx - subpathIdxStart;
+ goto FINISHED;
+ }
+ first = false;
+ }
+FINISHED:
+ GrAssert((vert - base) <= maxPts);
+ GrAssert((idx - idxBase) <= maxIdxs);
+
+ fVertexCnt = vert - base;
+ fIndexCnt = idx - idxBase;
+
+ if (fTranslate.fX || fTranslate.fY) {
+ int count = vert - base;
+ for (int i = 0; i < count; i++) {
+ base[i].offset(fTranslate.fX, fTranslate.fY);
+ }
+ }
+ }
+ // set these at the end so if we failed on first drawPath inside a
+ // setPath/clearPath block we won't assume geom was created on a subsequent
+ // drawPath in the same block.
+ fPreviousSrcTol = srcSpaceTol;
+ fPreviousStages = stages;
+ return true;
+}
+
+void GrDefaultPathRenderer::onDrawPath(GrDrawTarget::StageBitfield stages,
+ bool stencilOnly) {
+
+ SK_TRACE_EVENT1("GrDefaultPathRenderer::onDrawPath",
+ "points", SkStringPrintf("%i", path.countPoints()).c_str());
+
+ GrMatrix viewM = fTarget->getViewMatrix();
+ // In order to tesselate the path we get a bound on how much the matrix can
+ // stretch when mapping to screen coordinates.
+ GrScalar stretch = viewM.getMaxStretch();
+ bool useStretch = stretch > 0;
+ GrScalar tol = fCurveTolerance;
+
+ if (!useStretch) {
+ // TODO: deal with perspective in some better way.
+ tol /= 10;
+ } else {
+ tol = GrScalarDiv(tol, stretch);
+ }
+ // FIXME: It's really dumb that we recreate the verts for a new vertex
+ // layout. We only do that because the GrDrawTarget API doesn't allow
+ // us to change the vertex layout after reserveVertexSpace(). We won't
+ // actually change the vertex data when the layout changes since all the
+ // stages reference the positions (rather than having separate tex coords)
+ // and we don't ever have per-vert colors. In practice our call sites
+ // won't change the stages in use inside a setPath / removePath pair. But
+ // it is a silly limitation of the GrDrawTarget design that should be fixed.
+ if (tol != fPreviousSrcTol ||
+ stages != fPreviousStages) {
+ if (!this->createGeom(tol, stages)) {
+ return;
+ }
+ }
+
+ GrAssert(NULL != fTarget);
+ GrDrawTarget::AutoStateRestore asr(fTarget);
+ bool colorWritesWereDisabled = fTarget->isColorWriteDisabled();
+ // face culling doesn't make sense here
+ GrAssert(GrDrawTarget::kBoth_DrawFace == fTarget->getDrawFace());
+
+ int passCount = 0;
+ const GrStencilSettings* passes[3];
+ GrDrawTarget::DrawFace drawFace[3];
+ bool reverse = false;
+ bool lastPassIsBounds;
+
+ if (kHairLine_PathFill == fFill) {
+ passCount = 1;
+ if (stencilOnly) {
+ passes[0] = &gDirectToStencil;
+ } else {
+ passes[0] = NULL;
+ }
+ lastPassIsBounds = false;
+ drawFace[0] = GrDrawTarget::kBoth_DrawFace;
+ } else {
+ if (single_pass_path(*fTarget, *fPath, fFill)) {
+ passCount = 1;
+ if (stencilOnly) {
+ passes[0] = &gDirectToStencil;
+ } else {
+ passes[0] = NULL;
+ }
+ drawFace[0] = GrDrawTarget::kBoth_DrawFace;
+ lastPassIsBounds = false;
+ } else {
+ switch (fFill) {
+ case kInverseEvenOdd_PathFill:
+ reverse = true;
+ // fallthrough
+ case kEvenOdd_PathFill:
+ 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] = GrDrawTarget::kBoth_DrawFace;
+ break;
+
+ case kInverseWinding_PathFill:
+ reverse = true;
+ // fallthrough
+ case kWinding_PathFill:
+ if (fSeparateStencil) {
+ if (fStencilWrapOps) {
+ passes[0] = &gWindStencilSeparateWithWrap;
+ } else {
+ passes[0] = &gWindStencilSeparateNoWrap;
+ }
+ passCount = 2;
+ drawFace[0] = GrDrawTarget::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] = GrDrawTarget::kCW_DrawFace;
+ drawFace[1] = GrDrawTarget::kCCW_DrawFace;
+ passCount = 3;
+ }
+ if (stencilOnly) {
+ lastPassIsBounds = false;
+ --passCount;
+ } else {
+ lastPassIsBounds = true;
+ drawFace[passCount-1] = GrDrawTarget::kBoth_DrawFace;
+ if (reverse) {
+ passes[passCount-1] = &gInvWindColorPass;
+ } else {
+ passes[passCount-1] = &gWindColorPass;
+ }
+ }
+ break;
+ default:
+ GrAssert(!"Unknown path fFill!");
+ return;
+ }
+ }
+ }
+
+ {
+ SK_TRACE_EVENT1("GrDefaultPathRenderer::onDrawPath::renderPasses",
+ "verts", SkStringPrintf("%i", vert - base).c_str());
+ for (int p = 0; p < passCount; ++p) {
+ fTarget->setDrawFace(drawFace[p]);
+ if (NULL != passes[p]) {
+ fTarget->setStencil(*passes[p]);
+ }
+
+ if (lastPassIsBounds && (p == passCount-1)) {
+ if (!colorWritesWereDisabled) {
+ fTarget->disableState(GrDrawTarget::kNoColorWrites_StateBit);
+ }
+ GrRect bounds;
+ if (reverse) {
+ GrAssert(NULL != fTarget->getRenderTarget());
+ // draw over the whole world.
+ bounds.setLTRB(0, 0,
+ GrIntToScalar(fTarget->getRenderTarget()->width()),
+ GrIntToScalar(fTarget->getRenderTarget()->height()));
+ GrMatrix vmi;
+ if (fTarget->getViewInverse(&vmi)) {
+ vmi.mapRect(&bounds);
+ }
+ } else {
+ bounds = fPath->getBounds();
+ bounds.offset(fTranslate);
+ }
+ GrDrawTarget::AutoGeometryPush agp(fTarget);
+ fTarget->drawSimpleRect(bounds, NULL, stages);
+ } else {
+ if (passCount > 1) {
+ fTarget->enableState(GrDrawTarget::kNoColorWrites_StateBit);
+ }
+ if (fUseIndexedDraw) {
+ fTarget->drawIndexed(fPrimitiveType, 0, 0,
+ fVertexCnt, fIndexCnt);
+ } else {
+ int baseVertex = 0;
+ for (int sp = 0; sp < fSubpathCount; ++sp) {
+ fTarget->drawNonIndexed(fPrimitiveType, baseVertex,
+ fSubpathVertCount[sp]);
+ baseVertex += fSubpathVertCount[sp];
+ }
+ }
+ }
+ }
+ }
+}
+
+void GrDefaultPathRenderer::drawPath(GrDrawTarget::StageBitfield stages) {
+ this->onDrawPath(stages, false);
+}
+
+void GrDefaultPathRenderer::drawPathToStencil() {
+ GrAssert(kInverseEvenOdd_PathFill != fFill);
+ GrAssert(kInverseWinding_PathFill != fFill);
+ this->onDrawPath(0, true);
+}