// Returns whether or not the gpu can fast path the dash line effect. static bool can_fast_path_dash(const SkPoint pts[2], const GrStrokeInfo& strokeInfo, const GrDrawTarget& target, const SkMatrix& viewMatrix) { if (target.getDrawState().getRenderTarget()->isMultisampled()) { return false; } // Pts must be either horizontal or vertical in src space if (pts[0].fX != pts[1].fX && pts[0].fY != pts[1].fY) { return false; } // May be able to relax this to include skew. As of now cannot do perspective // because of the non uniform scaling of bloating a rect if (!viewMatrix.preservesRightAngles()) { return false; } if (!strokeInfo.isDashed() || 2 != strokeInfo.dashCount()) { return false; } const SkPathEffect::DashInfo& info = strokeInfo.getDashInfo(); if (0 == info.fIntervals[0] && 0 == info.fIntervals[1]) { return false; } SkPaint::Cap cap = strokeInfo.getStrokeRec().getCap(); // Current we do don't handle Round or Square cap dashes if (SkPaint::kRound_Cap == cap && info.fIntervals[0] != 0.f) { return false; } return true; }
TessellatingPathBatch(const GrColor& color, const SkPath& path, const GrStrokeInfo& stroke, const SkMatrix& viewMatrix, const SkRect& clipBounds) : INHERITED(ClassID()) , fColor(color) , fPath(path) , fStroke(stroke) , fViewMatrix(viewMatrix) { const SkRect& pathBounds = path.getBounds(); fClipBounds = clipBounds; // Because the clip bounds are used to add a contour for inverse fills, they must also // include the path bounds. fClipBounds.join(pathBounds); if (path.isInverseFillType()) { fBounds = fClipBounds; } else { fBounds = path.getBounds(); } if (!stroke.isFillStyle()) { SkScalar radius = SkScalarHalf(stroke.getWidth()); if (stroke.getJoin() == SkPaint::kMiter_Join) { SkScalar scale = stroke.getMiter(); if (scale > SK_Scalar1) { radius = SkScalarMul(radius, scale); } } fBounds.outset(radius, radius); } viewMatrix.mapRect(&fBounds); }
bool GrStencilAndCoverPathRenderer::canDrawPath(const GrDrawTarget* target, const GrPipelineBuilder* pipelineBuilder, const SkMatrix& viewMatrix, const SkPath& path, const GrStrokeInfo& stroke, bool antiAlias) const { return !stroke.getStrokeRec().isHairlineStyle() && !stroke.isDashed() && !antiAlias && // doesn't do per-path AA, relies on the target having MSAA pipelineBuilder->getStencil().isDisabled(); }
bool GrAADistanceFieldPathRenderer::canDrawPath(const GrDrawTarget* target, const GrPipelineBuilder* pipelineBuilder, const SkMatrix& viewMatrix, const SkPath& path, const GrStrokeInfo& stroke, bool antiAlias) const { // TODO: Support inverse fill // TODO: Support strokes if (!target->caps()->shaderCaps()->shaderDerivativeSupport() || !antiAlias || path.isInverseFillType() || path.isVolatile() || !stroke.isFillStyle()) { return false; } // currently don't support perspective if (viewMatrix.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 = viewMatrix.getMaxScale(); const SkRect& bounds = path.getBounds(); SkScalar maxDim = SkMaxScalar(bounds.width(), bounds.height()); return maxDim < 64.f && maxDim * maxScale < 256.f; }
bool GrAAConvexPathRenderer::canDrawPath(const GrDrawTarget* target, const GrPipelineBuilder*, const SkMatrix& viewMatrix, const SkPath& path, const GrStrokeInfo& stroke, bool antiAlias) const { return (target->caps()->shaderCaps()->shaderDerivativeSupport() && antiAlias && stroke.isFillStyle() && !path.isInverseFillType() && path.isConvex()); }
void GrStencilAndCoverPathRenderer::onStencilPath(GrDrawTarget* target, GrPipelineBuilder* pipelineBuilder, const SkMatrix& viewMatrix, const SkPath& path, const GrStrokeInfo& stroke) { SkASSERT(!path.isInverseFillType()); SkAutoTUnref<GrPathProcessor> pp(GrPathProcessor::Create(GrColor_WHITE, viewMatrix)); SkAutoTUnref<GrPath> p(get_gr_path(fGpu, path, stroke.getStrokeRec())); target->stencilPath(pipelineBuilder, pp, p, convert_skpath_filltype(path.getFillType())); }
bool GrDefaultPathRenderer::canDrawPath(const GrDrawTarget* target, const GrPipelineBuilder* pipelineBuilder, const SkMatrix& viewMatrix, const SkPath& path, const GrStrokeInfo& stroke, bool antiAlias) const { // this class can draw any path with any fill but doesn't do any anti-aliasing. return !antiAlias && (stroke.isFillStyle() || IsStrokeHairlineOrEquivalent(stroke, viewMatrix, NULL)); }
GrPathRenderer::StencilSupport GrDefaultPathRenderer::onGetStencilSupport(const GrDrawTarget*, const GrPipelineBuilder*, const SkPath& path, const GrStrokeInfo& stroke) const { if (single_pass_path(path, stroke.getStrokeRec())) { return GrPathRenderer::kNoRestriction_StencilSupport; } else { return GrPathRenderer::kStencilOnly_StencilSupport; } }
bool GrAALinearizingConvexPathRenderer::onDrawPath(GrDrawTarget* target, GrPipelineBuilder* pipelineBuilder, GrColor color, const SkMatrix& vm, const SkPath& path, const GrStrokeInfo& stroke, bool antiAlias) { if (path.isEmpty()) { return true; } AAFlatteningConvexPathBatch::Geometry geometry; geometry.fColor = color; geometry.fViewMatrix = vm; geometry.fPath = path; geometry.fStrokeWidth = stroke.isFillStyle() ? -1.0f : stroke.getWidth(); geometry.fJoin = stroke.isFillStyle() ? SkPaint::Join::kMiter_Join : stroke.getJoin(); geometry.fMiterLimit = stroke.getMiter(); SkAutoTUnref<GrBatch> batch(AAFlatteningConvexPathBatch::Create(geometry)); target->drawBatch(pipelineBuilder, batch); return true; }
bool GrSoftwarePathRenderer::canDrawPath(const GrDrawTarget*, const GrPipelineBuilder*, const SkMatrix& viewMatrix, const SkPath&, const GrStrokeInfo& stroke, bool antiAlias) const { if (NULL == fContext) { return false; } if (stroke.isDashed()) { return false; } return true; }
void GrGLPath::InitPathObjectStroke(GrGLGpu* gpu, GrGLuint pathID, const GrStrokeInfo& stroke) { SkASSERT(stroke.needToApply()); SkASSERT(!stroke.isDashed()); SkASSERT(!stroke.isHairlineStyle()); GR_GL_CALL(gpu->glInterface(), PathParameterf(pathID, GR_GL_PATH_STROKE_WIDTH, SkScalarToFloat(stroke.getWidth()))); GR_GL_CALL(gpu->glInterface(), PathParameterf(pathID, GR_GL_PATH_MITER_LIMIT, SkScalarToFloat(stroke.getMiter()))); GrGLenum join = join_to_gl_join(stroke.getJoin()); GR_GL_CALL(gpu->glInterface(), PathParameteri(pathID, GR_GL_PATH_JOIN_STYLE, join)); GrGLenum cap = cap_to_gl_cap(stroke.getCap()); GR_GL_CALL(gpu->glInterface(), PathParameteri(pathID, GR_GL_PATH_END_CAPS, cap)); GR_GL_CALL(gpu->glInterface(), PathParameterf(pathID, GR_GL_PATH_STROKE_BOUND, 0.02f)); }
bool GrAALinearizingConvexPathRenderer::canDrawPath(const GrDrawTarget* target, const GrPipelineBuilder*, const SkMatrix& viewMatrix, const SkPath& path, const GrStrokeInfo& stroke, bool antiAlias) const { if (!antiAlias) { return false; } if (path.isInverseFillType()) { return false; } if (!path.isConvex()) { return false; } if (stroke.getStyle() == SkStrokeRec::kStroke_Style) { return viewMatrix.isSimilarity() && stroke.getWidth() >= 1.0f && stroke.getWidth() <= kMaxStrokeWidth && !stroke.isDashed() && SkPathPriv::LastVerbIsClose(path) && stroke.getJoin() != SkPaint::Join::kRound_Join; } return stroke.getStyle() == SkStrokeRec::kFill_Style; }
bool GrStencilAndCoverPathRenderer::onDrawPath(GrDrawTarget* target, GrPipelineBuilder* pipelineBuilder, GrColor color, const SkMatrix& viewMatrix, const SkPath& path, const GrStrokeInfo& stroke, bool antiAlias) { SkASSERT(!antiAlias); SkASSERT(!stroke.getStrokeRec().isHairlineStyle()); SkASSERT(!stroke.isDashed()); SkASSERT(pipelineBuilder->getStencil().isDisabled()); SkAutoTUnref<GrPath> p(get_gr_path(fGpu, path, stroke.getStrokeRec())); if (path.isInverseFillType()) { GR_STATIC_CONST_SAME_STENCIL(kInvertedStencilPass, kZero_StencilOp, kZero_StencilOp, // We know our rect will hit pixels outside the clip and the user bits will be 0 // outside the clip. So we can't just fill where the user bits are 0. We also need to // check that the clip bit is set. kEqualIfInClip_StencilFunc, 0xffff, 0x0000, 0xffff); pipelineBuilder->setStencil(kInvertedStencilPass); // fake inverse with a stencil and cover SkAutoTUnref<GrPathProcessor> pp(GrPathProcessor::Create(GrColor_WHITE, viewMatrix)); target->stencilPath(pipelineBuilder, pp, p, convert_skpath_filltype(path.getFillType())); SkMatrix invert = SkMatrix::I(); SkRect bounds = SkRect::MakeLTRB(0, 0, SkIntToScalar(pipelineBuilder->getRenderTarget()->width()), SkIntToScalar(pipelineBuilder->getRenderTarget()->height())); SkMatrix vmi; // mapRect through persp matrix may not be correct if (!viewMatrix.hasPerspective() && viewMatrix.invert(&vmi)) { vmi.mapRect(&bounds); // theoretically could set bloat = 0, instead leave it because of matrix inversion // precision. SkScalar bloat = viewMatrix.getMaxScale() * SK_ScalarHalf; bounds.outset(bloat, bloat); } else { if (!viewMatrix.invert(&invert)) { return false; } } const SkMatrix& viewM = viewMatrix.hasPerspective() ? SkMatrix::I() : viewMatrix; target->drawRect(pipelineBuilder, color, viewM, bounds, NULL, &invert); } else { GR_STATIC_CONST_SAME_STENCIL(kStencilPass, kZero_StencilOp, kZero_StencilOp, kNotEqual_StencilFunc, 0xffff, 0x0000, 0xffff); pipelineBuilder->setStencil(kStencilPass); SkAutoTUnref<GrPathProcessor> pp(GrPathProcessor::Create(color, viewMatrix)); target->drawPath(pipelineBuilder, pp, p, convert_skpath_filltype(path.getFillType())); } pipelineBuilder->stencil()->setDisabled(); return true; }
bool GrDashingEffect::DrawDashLine(const SkPoint pts[2], const GrPaint& paint, const GrStrokeInfo& strokeInfo, GrGpu* gpu, GrDrawTarget* target, const SkMatrix& vm) { if (!can_fast_path_dash(pts, strokeInfo, *target, vm)) { return false; } const SkPathEffect::DashInfo& info = strokeInfo.getDashInfo(); SkPaint::Cap cap = strokeInfo.getStrokeRec().getCap(); SkScalar srcStrokeWidth = strokeInfo.getStrokeRec().getWidth(); // the phase should be normalized to be [0, sum of all intervals) SkASSERT(info.fPhase >= 0 && info.fPhase < info.fIntervals[0] + info.fIntervals[1]); SkScalar srcPhase = info.fPhase; // Rotate the src pts so they are aligned horizontally with pts[0].fX < pts[1].fX SkMatrix srcRotInv; SkPoint ptsRot[2]; if (pts[0].fY != pts[1].fY || pts[0].fX > pts[1].fX) { SkMatrix rotMatrix; align_to_x_axis(pts, &rotMatrix, ptsRot); if(!rotMatrix.invert(&srcRotInv)) { GrPrintf("Failed to create invertible rotation matrix!\n"); return false; } } else { srcRotInv.reset(); memcpy(ptsRot, pts, 2 * sizeof(SkPoint)); } bool useAA = paint.isAntiAlias(); // Scale corrections of intervals and stroke from view matrix SkScalar parallelScale; SkScalar perpScale; calc_dash_scaling(¶llelScale, &perpScale, vm, ptsRot); bool hasCap = SkPaint::kButt_Cap != cap && 0 != srcStrokeWidth; // We always want to at least stroke out half a pixel on each side in device space // so 0.5f / perpScale gives us this min in src space SkScalar halfSrcStroke = SkMaxScalar(srcStrokeWidth * 0.5f, 0.5f / perpScale); SkScalar strokeAdj; if (!hasCap) { strokeAdj = 0.f; } else { strokeAdj = halfSrcStroke; } SkScalar startAdj = 0; SkMatrix combinedMatrix = srcRotInv; combinedMatrix.postConcat(vm); bool lineDone = false; SkRect startRect; bool hasStartRect = false; // If we are using AA, check to see if we are drawing a partial dash at the start. If so // draw it separately here and adjust our start point accordingly if (useAA) { if (srcPhase > 0 && srcPhase < info.fIntervals[0]) { SkPoint startPts[2]; startPts[0] = ptsRot[0]; startPts[1].fY = startPts[0].fY; startPts[1].fX = SkMinScalar(startPts[0].fX + info.fIntervals[0] - srcPhase, ptsRot[1].fX); startRect.set(startPts, 2); startRect.outset(strokeAdj, halfSrcStroke); hasStartRect = true; startAdj = info.fIntervals[0] + info.fIntervals[1] - srcPhase; } } // adjustments for start and end of bounding rect so we only draw dash intervals // contained in the original line segment. startAdj += calc_start_adjustment(info); if (startAdj != 0) { ptsRot[0].fX += startAdj; srcPhase = 0; } SkScalar endingInterval = 0; SkScalar endAdj = calc_end_adjustment(info, ptsRot, srcPhase, &endingInterval); ptsRot[1].fX -= endAdj; if (ptsRot[0].fX >= ptsRot[1].fX) { lineDone = true; } SkRect endRect; bool hasEndRect = false; // If we are using AA, check to see if we are drawing a partial dash at then end. If so // draw it separately here and adjust our end point accordingly if (useAA && !lineDone) { // If we adjusted the end then we will not be drawing a partial dash at the end. // If we didn't adjust the end point then we just need to make sure the ending // dash isn't a full dash if (0 == endAdj && endingInterval != info.fIntervals[0]) { SkPoint endPts[2]; endPts[1] = ptsRot[1]; endPts[0].fY = endPts[1].fY; endPts[0].fX = endPts[1].fX - endingInterval; endRect.set(endPts, 2); endRect.outset(strokeAdj, halfSrcStroke); hasEndRect = true; endAdj = endingInterval + info.fIntervals[1]; ptsRot[1].fX -= endAdj; if (ptsRot[0].fX >= ptsRot[1].fX) { lineDone = true; } } } if (startAdj != 0) { srcPhase = 0; } // Change the dashing info from src space into device space SkScalar devIntervals[2]; devIntervals[0] = info.fIntervals[0] * parallelScale; devIntervals[1] = info.fIntervals[1] * parallelScale; SkScalar devPhase = srcPhase * parallelScale; SkScalar strokeWidth = srcStrokeWidth * perpScale; if ((strokeWidth < 1.f && !useAA) || 0.f == strokeWidth) { strokeWidth = 1.f; } SkScalar halfDevStroke = strokeWidth * 0.5f; if (SkPaint::kSquare_Cap == cap && 0 != srcStrokeWidth) { // add cap to on interveal and remove from off interval devIntervals[0] += strokeWidth; devIntervals[1] -= strokeWidth; } SkScalar startOffset = devIntervals[1] * 0.5f + devPhase; SkScalar bloatX = useAA ? 0.5f / parallelScale : 0.f; SkScalar bloatY = useAA ? 0.5f / perpScale : 0.f; SkScalar devBloat = useAA ? 0.5f : 0.f; GrDrawState* drawState = target->drawState(); if (devIntervals[1] <= 0.f && useAA) { // Case when we end up drawing a solid AA rect // Reset the start rect to draw this single solid rect // but it requires to upload a new intervals uniform so we can mimic // one giant dash ptsRot[0].fX -= hasStartRect ? startAdj : 0; ptsRot[1].fX += hasEndRect ? endAdj : 0; startRect.set(ptsRot, 2); startRect.outset(strokeAdj, halfSrcStroke); hasStartRect = true; hasEndRect = false; lineDone = true; SkPoint devicePts[2]; vm.mapPoints(devicePts, ptsRot, 2); SkScalar lineLength = SkPoint::Distance(devicePts[0], devicePts[1]); if (hasCap) { lineLength += 2.f * halfDevStroke; } devIntervals[0] = lineLength; } if (devIntervals[1] > 0.f || useAA) { SkPathEffect::DashInfo devInfo; devInfo.fPhase = devPhase; devInfo.fCount = 2; devInfo.fIntervals = devIntervals; GrEffectEdgeType edgeType= useAA ? kFillAA_GrEffectEdgeType : kFillBW_GrEffectEdgeType; bool isRoundCap = SkPaint::kRound_Cap == cap; GrDashingEffect::DashCap capType = isRoundCap ? GrDashingEffect::kRound_DashCap : GrDashingEffect::kNonRound_DashCap; drawState->addCoverageEffect( GrDashingEffect::Create(edgeType, devInfo, strokeWidth, capType), 1)->unref(); } // Set up the vertex data for the line and start/end dashes drawState->setVertexAttribs<gDashLineVertexAttribs>(SK_ARRAY_COUNT(gDashLineVertexAttribs)); int totalRectCnt = 0; totalRectCnt += !lineDone ? 1 : 0; totalRectCnt += hasStartRect ? 1 : 0; totalRectCnt += hasEndRect ? 1 : 0; GrDrawTarget::AutoReleaseGeometry geo(target, totalRectCnt * 4, 0); if (!geo.succeeded()) { GrPrintf("Failed to get space for vertices!\n"); return false; } DashLineVertex* verts = reinterpret_cast<DashLineVertex*>(geo.vertices()); int curVIdx = 0; if (SkPaint::kRound_Cap == cap && 0 != srcStrokeWidth) { // need to adjust this for round caps to correctly set the dashPos attrib on vertices startOffset -= halfDevStroke; } // Draw interior part of dashed line if (!lineDone) { SkPoint devicePts[2]; vm.mapPoints(devicePts, ptsRot, 2); SkScalar lineLength = SkPoint::Distance(devicePts[0], devicePts[1]); if (hasCap) { lineLength += 2.f * halfDevStroke; } SkRect bounds; bounds.set(ptsRot[0].fX, ptsRot[0].fY, ptsRot[1].fX, ptsRot[1].fY); bounds.outset(bloatX + strokeAdj, bloatY + halfSrcStroke); setup_dashed_rect(bounds, verts, curVIdx, combinedMatrix, startOffset, devBloat, lineLength, halfDevStroke); curVIdx += 4; } if (hasStartRect) { SkASSERT(useAA); // so that we know bloatX and bloatY have been set startRect.outset(bloatX, bloatY); setup_dashed_rect(startRect, verts, curVIdx, combinedMatrix, startOffset, devBloat, devIntervals[0], halfDevStroke); curVIdx += 4; } if (hasEndRect) { SkASSERT(useAA); // so that we know bloatX and bloatY have been set endRect.outset(bloatX, bloatY); setup_dashed_rect(endRect, verts, curVIdx, combinedMatrix, startOffset, devBloat, devIntervals[0], halfDevStroke); } target->setIndexSourceToBuffer(gpu->getContext()->getQuadIndexBuffer()); target->drawIndexedInstances(kTriangles_GrPrimitiveType, totalRectCnt, 4, 6); target->resetIndexSource(); return true; }
void GrGLPath::InitPathObject(GrGLGpu* gpu, GrGLuint pathID, const SkPath& skPath, const GrStrokeInfo& stroke) { SkASSERT(!stroke.isDashed()); if (!skPath.isEmpty()) { int verbCnt = skPath.countVerbs(); int pointCnt = skPath.countPoints(); int minCoordCnt = pointCnt * 2; SkSTArray<16, GrGLubyte, true> pathCommands(verbCnt); SkSTArray<16, GrGLfloat, true> pathCoords(minCoordCnt); SkDEBUGCODE(int numCoords = 0); if ((skPath.getSegmentMasks() & SkPath::kConic_SegmentMask) == 0) { // This branch does type punning, converting SkPoint* to GrGLfloat*. SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(GrGLfloat) * 2, sk_point_not_two_floats); // This branch does not convert with SkScalarToFloat. #ifndef SK_SCALAR_IS_FLOAT #error Need SK_SCALAR_IS_FLOAT. #endif pathCommands.resize_back(verbCnt); pathCoords.resize_back(minCoordCnt); skPath.getPoints(reinterpret_cast<SkPoint*>(&pathCoords[0]), pointCnt); skPath.getVerbs(&pathCommands[0], verbCnt); for (int i = 0; i < verbCnt; ++i) { SkPath::Verb v = static_cast<SkPath::Verb>(pathCommands[i]); pathCommands[i] = verb_to_gl_path_cmd(v); SkDEBUGCODE(numCoords += num_coords(v)); } } else { SkPoint points[4]; SkPath::RawIter iter(skPath); SkPath::Verb verb; while ((verb = iter.next(points)) != SkPath::kDone_Verb) { pathCommands.push_back(verb_to_gl_path_cmd(verb)); GrGLfloat coords[6]; int coordsForVerb; switch (verb) { case SkPath::kMove_Verb: points_to_coords(points, 0, 1, coords); coordsForVerb = 2; break; case SkPath::kLine_Verb: points_to_coords(points, 1, 1, coords); coordsForVerb = 2; break; case SkPath::kConic_Verb: points_to_coords(points, 1, 2, coords); coords[4] = SkScalarToFloat(iter.conicWeight()); coordsForVerb = 5; break; case SkPath::kQuad_Verb: points_to_coords(points, 1, 2, coords); coordsForVerb = 4; break; case SkPath::kCubic_Verb: points_to_coords(points, 1, 3, coords); coordsForVerb = 6; break; case SkPath::kClose_Verb: continue; default: SkASSERT(false); // Not reached. continue; } SkDEBUGCODE(numCoords += num_coords(verb)); pathCoords.push_back_n(coordsForVerb, coords); } } SkASSERT(verbCnt == pathCommands.count()); SkASSERT(numCoords == pathCoords.count()); GR_GL_CALL(gpu->glInterface(), PathCommands(pathID, pathCommands.count(), &pathCommands[0], pathCoords.count(), GR_GL_FLOAT, &pathCoords[0])); } else { GR_GL_CALL(gpu->glInterface(), PathCommands(pathID, 0, NULL, 0, GR_GL_FLOAT, NULL)); } if (stroke.needToApply()) { SkASSERT(!stroke.isHairlineStyle()); GR_GL_CALL(gpu->glInterface(), PathParameterf(pathID, GR_GL_PATH_STROKE_WIDTH, SkScalarToFloat(stroke.getWidth()))); GR_GL_CALL(gpu->glInterface(), PathParameterf(pathID, GR_GL_PATH_MITER_LIMIT, SkScalarToFloat(stroke.getMiter()))); GrGLenum join = join_to_gl_join(stroke.getJoin()); GR_GL_CALL(gpu->glInterface(), PathParameteri(pathID, GR_GL_PATH_JOIN_STYLE, join)); GrGLenum cap = cap_to_gl_cap(stroke.getCap()); GR_GL_CALL(gpu->glInterface(), PathParameteri(pathID, GR_GL_PATH_END_CAPS, cap)); GR_GL_CALL(gpu->glInterface(), PathParameterf(pathID, GR_GL_PATH_STROKE_BOUND, 0.02f)); } }