in src/gpu/ganesh/ops/DashOp.cpp [377:665]
void onPrepareDraws(GrMeshDrawTarget* target) override {
int instanceCount = fLines.size();
SkPaint::Cap cap = this->cap();
DashCap capType = (SkPaint::kRound_Cap == cap) ? kRound_DashCap : kNonRound_DashCap;
if (!fProgramInfo) {
this->createProgramInfo(target);
if (!fProgramInfo) {
return;
}
}
// useAA here means Edge AA or MSAA
bool useAA = this->aaMode() != AAMode::kNone;
bool fullDash = this->fullDash();
// We do two passes over all of the dashes. First we setup the start, end, and bounds,
// rectangles. We preserve all of this work in the rects / draws arrays below. Then we
// iterate again over these decomposed dashes to generate vertices
static const int kNumStackDashes = 128;
STArray<kNumStackDashes, SkRect, true> rects;
STArray<kNumStackDashes, DashDraw, true> draws;
SkSafeMath safeMath;
int totalRectCount = 0;
int rectOffset = 0;
rects.push_back_n(3 * instanceCount);
for (int i = 0; i < instanceCount; i++) {
const LineData& args = fLines[i];
DashDraw& draw = draws.push_back(args);
bool hasCap = SkPaint::kButt_Cap != cap;
SkScalar halfSrcStroke = args.fSrcStrokeWidth * 0.5f;
if (halfSrcStroke == 0.0f || this->aaMode() != AAMode::kCoverageWithMSAA) {
// In the non-MSAA case, we always want to at least stroke out half a pixel on each
// side in device space. 0.5f / fPerpendicularScale gives us this min in src space.
// This is also necessary when the stroke width is zero, to allow hairlines to draw.
halfSrcStroke = std::max(halfSrcStroke, 0.5f / args.fPerpendicularScale);
}
SkScalar strokeAdj = hasCap ? halfSrcStroke : 0.0f;
SkScalar startAdj = 0;
bool lineDone = false;
// Too simplify the algorithm, we always push back rects for start and end rect.
// Otherwise we'd have to track start / end rects for each individual geometry
SkRect& bounds = rects[rectOffset++];
SkRect& startRect = rects[rectOffset++];
SkRect& endRect = rects[rectOffset++];
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 (draw.fPhase > 0 && draw.fPhase < draw.fIntervals[0]) {
SkPoint startPts[2];
startPts[0] = draw.fPtsRot[0];
startPts[1].fY = startPts[0].fY;
startPts[1].fX = std::min(startPts[0].fX + draw.fIntervals[0] - draw.fPhase,
draw.fPtsRot[1].fX);
startRect.setBounds(startPts, 2);
startRect.outset(strokeAdj, halfSrcStroke);
hasStartRect = true;
startAdj = draw.fIntervals[0] + draw.fIntervals[1] - draw.fPhase;
}
}
// adjustments for start and end of bounding rect so we only draw dash intervals
// contained in the original line segment.
startAdj += calc_start_adjustment(draw.fIntervals, draw.fPhase);
if (startAdj != 0) {
draw.fPtsRot[0].fX += startAdj;
draw.fPhase = 0;
}
SkScalar endingInterval = 0;
SkScalar endAdj = calc_end_adjustment(draw.fIntervals, draw.fPtsRot, draw.fPhase,
&endingInterval);
draw.fPtsRot[1].fX -= endAdj;
if (draw.fPtsRot[0].fX >= draw.fPtsRot[1].fX) {
lineDone = true;
}
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 != draw.fIntervals[0]) {
SkPoint endPts[2];
endPts[1] = draw.fPtsRot[1];
endPts[0].fY = endPts[1].fY;
endPts[0].fX = endPts[1].fX - endingInterval;
endRect.setBounds(endPts, 2);
endRect.outset(strokeAdj, halfSrcStroke);
hasEndRect = true;
endAdj = endingInterval + draw.fIntervals[1];
draw.fPtsRot[1].fX -= endAdj;
if (draw.fPtsRot[0].fX >= draw.fPtsRot[1].fX) {
lineDone = true;
}
}
}
if (draw.fPtsRot[0].fX == draw.fPtsRot[1].fX &&
(0 != endAdj || 0 == startAdj) &&
hasCap) {
// At this point the fPtsRot[0]/[1] represent the start and end of the inner rect of
// dashes that we want to draw. The only way they can be equal is if the on interval
// is zero (or an edge case if the end of line ends at a full off interval, but this
// is handled as well). Thus if the on interval is zero then we need to draw a cap
// at this position if the stroke has caps. The spec says we only draw this point if
// point lies between [start of line, end of line). Thus we check if we are at the
// end (but not the start), and if so we don't draw the cap.
lineDone = false;
}
if (startAdj != 0) {
draw.fPhase = 0;
}
// Change the dashing info from src space into device space
SkScalar* devIntervals = draw.fIntervals;
devIntervals[0] = draw.fIntervals[0] * args.fParallelScale;
devIntervals[1] = draw.fIntervals[1] * args.fParallelScale;
SkScalar devPhase = draw.fPhase * args.fParallelScale;
SkScalar strokeWidth = args.fSrcStrokeWidth * args.fPerpendicularScale;
if ((strokeWidth < 1.f && !useAA) || 0.f == strokeWidth) {
strokeWidth = 1.f;
}
SkScalar halfDevStroke = strokeWidth * 0.5f;
if (SkPaint::kSquare_Cap == cap) {
// add cap to on interval and remove from off interval
devIntervals[0] += strokeWidth;
devIntervals[1] -= strokeWidth;
}
SkScalar startOffset = devIntervals[1] * 0.5f + devPhase;
SkScalar devBloatX = 0.0f;
SkScalar devBloatY = 0.0f;
switch (this->aaMode()) {
case AAMode::kNone:
break;
case AAMode::kCoverage:
// For EdgeAA, we bloat in X & Y for both square and round caps.
devBloatX = 0.5f;
devBloatY = 0.5f;
break;
case AAMode::kCoverageWithMSAA:
// For MSAA, we only bloat in Y for round caps.
devBloatY = (cap == SkPaint::kRound_Cap) ? 0.5f : 0.0f;
break;
}
SkScalar bloatX = devBloatX / args.fParallelScale;
SkScalar bloatY = devBloatY / args.fPerpendicularScale;
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
draw.fPtsRot[0].fX -= hasStartRect ? startAdj : 0;
draw.fPtsRot[1].fX += hasEndRect ? endAdj : 0;
startRect.setBounds(draw.fPtsRot, 2);
startRect.outset(strokeAdj, halfSrcStroke);
hasStartRect = true;
hasEndRect = false;
lineDone = true;
SkPoint devicePts[2];
args.fSrcRotInv.mapPoints(devicePts, draw.fPtsRot, 2);
SkScalar lineLength = SkPoint::Distance(devicePts[0], devicePts[1]);
if (hasCap) {
lineLength += 2.f * halfDevStroke;
}
devIntervals[0] = lineLength;
}
totalRectCount = safeMath.addInt(totalRectCount, !lineDone ? 1 : 0);
totalRectCount = safeMath.addInt(totalRectCount, hasStartRect ? 1 : 0);
totalRectCount = safeMath.addInt(totalRectCount, hasEndRect ? 1 : 0);
if (SkPaint::kRound_Cap == cap && 0 != args.fSrcStrokeWidth) {
// need to adjust this for round caps to correctly set the dashPos attrib on
// vertices
startOffset -= halfDevStroke;
}
if (!lineDone) {
SkPoint devicePts[2];
args.fSrcRotInv.mapPoints(devicePts, draw.fPtsRot, 2);
draw.fLineLength = SkPoint::Distance(devicePts[0], devicePts[1]);
if (hasCap) {
draw.fLineLength += 2.f * halfDevStroke;
}
bounds.setLTRB(draw.fPtsRot[0].fX, draw.fPtsRot[0].fY,
draw.fPtsRot[1].fX, draw.fPtsRot[1].fY);
bounds.outset(bloatX + strokeAdj, bloatY + halfSrcStroke);
}
if (hasStartRect) {
SkASSERT(useAA); // so that we know bloatX and bloatY have been set
startRect.outset(bloatX, bloatY);
}
if (hasEndRect) {
SkASSERT(useAA); // so that we know bloatX and bloatY have been set
endRect.outset(bloatX, bloatY);
}
draw.fStartOffset = startOffset;
draw.fDevBloatX = devBloatX;
draw.fPerpendicularScale = args.fPerpendicularScale;
draw.fStrokeWidth = strokeWidth;
draw.fHasStartRect = hasStartRect;
draw.fLineDone = lineDone;
draw.fHasEndRect = hasEndRect;
}
if (!totalRectCount || !safeMath) {
return;
}
QuadHelper helper(target, fProgramInfo->geomProc().vertexStride(), totalRectCount);
VertexWriter vertices{ helper.vertices() };
if (!vertices) {
return;
}
int rectIndex = 0;
for (int i = 0; i < instanceCount; i++) {
const LineData& geom = fLines[i];
if (!draws[i].fLineDone) {
if (fullDash) {
setup_dashed_rect(rects[rectIndex], vertices, geom.fSrcRotInv,
draws[i].fStartOffset, draws[i].fDevBloatX,
draws[i].fLineLength, draws[i].fIntervals[0],
draws[i].fIntervals[1], draws[i].fStrokeWidth,
draws[i].fPerpendicularScale,
capType);
} else {
vertices.writeQuad(GrQuad::MakeFromRect(rects[rectIndex], geom.fSrcRotInv));
}
}
rectIndex++;
if (draws[i].fHasStartRect) {
if (fullDash) {
setup_dashed_rect(rects[rectIndex], vertices, geom.fSrcRotInv,
draws[i].fStartOffset, draws[i].fDevBloatX,
draws[i].fIntervals[0], draws[i].fIntervals[0],
draws[i].fIntervals[1], draws[i].fStrokeWidth,
draws[i].fPerpendicularScale, capType);
} else {
vertices.writeQuad(GrQuad::MakeFromRect(rects[rectIndex], geom.fSrcRotInv));
}
}
rectIndex++;
if (draws[i].fHasEndRect) {
if (fullDash) {
setup_dashed_rect(rects[rectIndex], vertices, geom.fSrcRotInv,
draws[i].fStartOffset, draws[i].fDevBloatX,
draws[i].fIntervals[0], draws[i].fIntervals[0],
draws[i].fIntervals[1], draws[i].fStrokeWidth,
draws[i].fPerpendicularScale, capType);
} else {
vertices.writeQuad(GrQuad::MakeFromRect(rects[rectIndex], geom.fSrcRotInv));
}
}
rectIndex++;
}
fMesh = helper.mesh();
}