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