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 GrStencilAndCoverTextContext::TextRun::draw(GrContext* ctx, GrDrawContext* drawContext, const GrPaint& grPaint, const GrClip& clip, const SkMatrix& viewMatrix, const SkSurfaceProps& props, SkScalar x, SkScalar y, const SkIRect& clipBounds, GrAtlasTextContext* fallbackTextContext, const SkPaint& originalSkPaint) const { SkASSERT(fInstanceData); SkASSERT(drawContext->isStencilBufferMultisampled() || !grPaint.isAntiAlias()); if (fInstanceData->count()) { static constexpr GrUserStencilSettings kCoverPass( GrUserStencilSettings::StaticInit< 0x0000, GrUserStencilTest::kNotEqual, // Stencil pass accounts for clip. 0xffff, GrUserStencilOp::kZero, GrUserStencilOp::kKeep, 0xffff>() ); SkAutoTUnref<GrPathRange> glyphs(this->createGlyphs(ctx)); if (fLastDrawnGlyphsID != glyphs->uniqueID()) { // Either this is the first draw or the glyphs object was purged since last draw. glyphs->loadPathsIfNeeded(fInstanceData->indices(), fInstanceData->count()); fLastDrawnGlyphsID = glyphs->uniqueID(); } // Don't compute a bounding box. For dst copy texture, we'll opt instead for it to just copy // the entire dst. Realistically this is a moot point, because any context that supports // NV_path_rendering will also support NV_blend_equation_advanced. // For clipping we'll just skip any optimizations based on the bounds. This does, however, // hurt batching. const SkRect bounds = SkRect::MakeIWH(drawContext->width(), drawContext->height()); SkAutoTUnref<GrDrawBatch> batch( GrDrawPathRangeBatch::Create(viewMatrix, fTextRatio, fTextInverseRatio * x, fTextInverseRatio * y, grPaint.getColor(), GrPathRendering::kWinding_FillType, glyphs, fInstanceData, bounds)); GrPipelineBuilder pipelineBuilder(grPaint); pipelineBuilder.setState(GrPipelineBuilder::kHWAntialias_Flag, grPaint.isAntiAlias()); pipelineBuilder.setUserStencil(&kCoverPass); drawContext->drawBatch(pipelineBuilder, clip, batch); } if (fFallbackTextBlob) { SkPaint fallbackSkPaint(originalSkPaint); fStyle.strokeRec().applyToPaint(&fallbackSkPaint); if (!fStyle.isSimpleFill()) { fallbackSkPaint.setStrokeWidth(fStyle.strokeRec().getWidth() * fTextRatio); } fallbackTextContext->drawTextBlob(ctx, drawContext, clip, fallbackSkPaint, viewMatrix, props, fFallbackTextBlob.get(), x, y, nullptr, clipBounds); } }