// 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; }
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; }