Remove GrStrokeHardwareTessellator::fCurrentPoint

This is a step toward making the class less stateful.

Bug: chromium:1172543
Change-Id: I1fbcff53fa823c49dcca757fa970dcebc9a9aa25
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/372286
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: John Stiles <johnstiles@google.com>
This commit is contained in:
Chris Dalton 2021-02-19 11:40:50 -07:00 committed by Skia Commit-Bot
parent c505e435d4
commit d235b9178b
2 changed files with 63 additions and 81 deletions

View File

@ -131,29 +131,27 @@ void GrStrokeHardwareTessellator::prepare(GrMeshDrawOp::Target* target,
fDynamicColor.set(pathStroke.fColor, wideColor);
}
const SkPath& path = pathStroke.fPath;
fHasLastControlPoint = false;
SkDEBUGCODE(fHasCurrentPoint = false;)
SkPathVerb previousVerb = SkPathVerb::kClose;
for (auto [verb, p, w] : SkPathPriv::Iterate(pathStroke.fPath)) {
for (auto [verb, p, w] : SkPathPriv::Iterate(path)) {
switch (verb) {
case SkPathVerb::kMove:
// "A subpath ... consisting of a single moveto shall not be stroked."
// https://www.w3.org/TR/SVG11/painting.html#StrokeProperties
if (previousVerb != SkPathVerb::kMove && previousVerb != SkPathVerb::kClose) {
this->cap();
this->cap(p[-1]);
}
this->moveTo(p[0]);
break;
case SkPathVerb::kLine:
SkASSERT(fHasCurrentPoint);
SkASSERT(p[0] == fCurrentPoint);
this->lineTo(p[1]);
this->lineTo(p[0], p[1]);
break;
case SkPathVerb::kQuad:
if (conic_has_cusp(p)) {
SkPoint cusp = SkEvalQuadAt(p, SkFindQuadMidTangent(p));
this->lineTo(cusp);
this->lineTo(p[2], JoinType::kBowtie);
this->lineTo(p[0], cusp);
this->lineTo(cusp, p[2], JoinType::kBowtie);
} else {
this->conicTo(p, 1);
}
@ -162,8 +160,8 @@ void GrStrokeHardwareTessellator::prepare(GrMeshDrawOp::Target* target,
if (conic_has_cusp(p)) {
SkConic conic(p, *w);
SkPoint cusp = conic.evalAt(conic.findMidTangent());
this->lineTo(cusp);
this->lineTo(p[2], JoinType::kBowtie);
this->lineTo(p[0], cusp);
this->lineTo(cusp, p[2], JoinType::kBowtie);
} else {
this->conicTo(p, *w);
}
@ -178,13 +176,14 @@ void GrStrokeHardwareTessellator::prepare(GrMeshDrawOp::Target* target,
}
break;
case SkPathVerb::kClose:
this->close();
this->close(p[0]);
break;
}
previousVerb = verb;
}
if (previousVerb != SkPathVerb::kMove && previousVerb != SkPathVerb::kClose) {
this->cap();
const SkPoint* p = SkPathPriv::PointData(path);
this->cap(p[path.countPoints() - 1]);
}
}
@ -197,47 +196,40 @@ void GrStrokeHardwareTessellator::prepare(GrMeshDrawOp::Target* target,
}
void GrStrokeHardwareTessellator::moveTo(SkPoint pt) {
fCurrentPoint = fCurrContourStartPoint = pt;
fCurrContourStartPoint = pt;
fHasLastControlPoint = false;
SkDEBUGCODE(fHasCurrentPoint = true;)
}
void GrStrokeHardwareTessellator::moveTo(SkPoint pt, SkPoint lastControlPoint) {
fCurrentPoint = fCurrContourStartPoint = pt;
fCurrContourStartPoint = pt;
fCurrContourFirstControlPoint = fLastControlPoint = lastControlPoint;
fHasLastControlPoint = true;
SkDEBUGCODE(fHasCurrentPoint = true;)
}
void GrStrokeHardwareTessellator::lineTo(SkPoint pt, JoinType prevJoinType) {
SkASSERT(fHasCurrentPoint);
void GrStrokeHardwareTessellator::lineTo(SkPoint p0, SkPoint p1, JoinType prevJoinType) {
// Zero-length paths need special treatment because they are spec'd to behave differently.
if (pt == fCurrentPoint) {
if (p0 == p1) {
return;
}
if (fMaxCombinedSegments_withJoin < 1) {
// The stroke has extremely thick round joins and there aren't enough guaranteed segments to
// always combine a join with a line patch. Emit the join in its own separate patch.
this->joinTo(prevJoinType, pt);
this->joinTo(prevJoinType, p0, p1);
prevJoinType = JoinType::kNone;
}
SkPoint asPatch[4] = {fCurrentPoint, fCurrentPoint, pt, pt};
this->emitPatch(prevJoinType, asPatch, pt);
SkPoint asPatch[4] = {p0, p0, p1, p1};
this->emitPatch(prevJoinType, asPatch, p1);
}
void GrStrokeHardwareTessellator::conicTo(const SkPoint p[3], float w, JoinType prevJoinType,
int maxDepth) {
SkASSERT(fHasCurrentPoint);
SkASSERT(p[0] == fCurrentPoint);
// Zero-length paths need special treatment because they are spec'd to behave differently. If
// the control point is colocated on an endpoint then this might end up being the case. Fall
// back on a lineTo and let it make the final check.
if (p[1] == p[0] || p[1] == p[2] || w == 0) {
this->lineTo(p[2], prevJoinType);
this->lineTo(p[0], p[2], prevJoinType);
return;
}
@ -323,13 +315,10 @@ void GrStrokeHardwareTessellator::conicTo(const SkPoint p[3], float w, JoinType
void GrStrokeHardwareTessellator::cubicTo(const SkPoint p[4], JoinType prevJoinType,
Convex180Status convex180Status, int maxDepth) {
SkASSERT(fHasCurrentPoint);
SkASSERT(p[0] == fCurrentPoint);
// The stroke tessellation shader assigns special meaning to p0==p1==p2 and p1==p2==p3. If this
// is the case then we need to rewrite the cubic.
if (p[1] == p[2] && (p[1] == p[0] || p[1] == p[3])) {
this->lineTo(p[3], prevJoinType);
this->lineTo(p[0], p[3], prevJoinType);
return;
}
@ -428,9 +417,9 @@ void GrStrokeHardwareTessellator::cubicConvex180SegmentsTo(const SkPoint p[4],
SkChopCubicAt(p, chops, chopT[0], chopT[1]);
// Two cusps are only possible on a flat line with two 180-degree turnarounds.
if (areCusps) {
this->lineTo(chops[3], prevJoinType);
this->lineTo(chops[6], JoinType::kBowtie);
this->lineTo(chops[9], JoinType::kBowtie);
this->lineTo(chops[0], chops[3], prevJoinType);
this->lineTo(chops[3], chops[6], JoinType::kBowtie);
this->lineTo(chops[6], chops[9], JoinType::kBowtie);
return;
}
this->cubicTo(chops, prevJoinType, Convex180Status::kYes, maxDepth);
@ -439,10 +428,8 @@ void GrStrokeHardwareTessellator::cubicConvex180SegmentsTo(const SkPoint p[4],
}
}
void GrStrokeHardwareTessellator::joinTo(JoinType joinType, SkPoint nextControlPoint,
int maxDepth) {
SkASSERT(fHasCurrentPoint);
void GrStrokeHardwareTessellator::joinTo(JoinType joinType, SkPoint junctionPoint,
SkPoint nextControlPoint, int maxDepth) {
if (!fHasLastControlPoint) {
// The first stroke doesn't have a previous join.
return;
@ -450,8 +437,8 @@ void GrStrokeHardwareTessellator::joinTo(JoinType joinType, SkPoint nextControlP
if (!fSoloRoundJoinAlwaysFitsInPatch && maxDepth != 0 &&
(fStroke->getJoin() == SkPaint::kRound_Join || joinType == JoinType::kBowtie)) {
SkVector tan0 = fCurrentPoint - fLastControlPoint;
SkVector tan1 = nextControlPoint - fCurrentPoint;
SkVector tan0 = junctionPoint - fLastControlPoint;
SkVector tan1 = nextControlPoint - junctionPoint;
float rotation = SkMeasureAngleBetweenVectors(tan0, tan1);
float numRadialSegments = rotation * fTolerances.fNumRadialSegmentsPerRadian;
if (numRadialSegments > fMaxTessellationSegments) {
@ -468,28 +455,28 @@ void GrStrokeHardwareTessellator::joinTo(JoinType joinType, SkPoint nextControlP
// c0 will be the "next" control point for the first join half, and c1 will be the
// "previous" control point for the second join half.
SkPoint c0, c1;
// FIXME: This hack ensures "c0 - fCurrentPoint" gives the exact same ieee fp32 vector
// as "-(c1 - fCurrentPoint)". If our current strategy of join chopping sticks, we may
// want to think of a cleaner method to avoid T-junctions when we chop joins.
// FIXME(skia:11347): This hack ensures "c0 - junctionPoint" gives the exact same ieee
// fp32 vector as "-(c1 - junctionPoint)". Tessellated stroking is becoming less
// experimental, so t's time to think of a cleaner method to avoid T-junctions when we
// chop joins.
int maxAttempts = 10;
do {
bisector = (fCurrentPoint + bisector) - (fCurrentPoint - bisector);
c0 = fCurrentPoint + bisector;
c1 = fCurrentPoint - bisector;
} while (c0 - fCurrentPoint != -(c1 - fCurrentPoint) && --maxAttempts);
this->joinTo(joinType, c0, maxDepth - 1); // First join half.
bisector = (junctionPoint + bisector) - (junctionPoint - bisector);
c0 = junctionPoint + bisector;
c1 = junctionPoint - bisector;
} while (c0 - junctionPoint != -(c1 - junctionPoint) && --maxAttempts);
this->joinTo(joinType, junctionPoint, c0, maxDepth - 1); // First join half.
fLastControlPoint = c1;
this->joinTo(joinType, nextControlPoint, maxDepth - 1); // Second join half.
// Second join half.
this->joinTo(joinType, junctionPoint, nextControlPoint, maxDepth - 1);
return;
}
}
this->emitJoinPatch(joinType, nextControlPoint);
this->emitJoinPatch(joinType, junctionPoint, nextControlPoint);
}
void GrStrokeHardwareTessellator::close() {
SkASSERT(fHasCurrentPoint);
void GrStrokeHardwareTessellator::close(SkPoint contourEndpoint) {
if (!fHasLastControlPoint) {
// Draw caps instead of closing if the subpath is zero length:
//
@ -498,22 +485,20 @@ void GrStrokeHardwareTessellator::close() {
//
// (https://www.w3.org/TR/SVG11/painting.html#StrokeProperties)
//
this->cap();
this->cap(contourEndpoint);
return;
}
// Draw a line back to the beginning. (This will be discarded if
// fCurrentPoint == fCurrContourStartPoint.)
this->lineTo(fCurrContourStartPoint);
this->joinTo(JoinType::kFromStroke, fCurrContourFirstControlPoint);
// contourEndpoint == fCurrContourStartPoint.)
this->lineTo(contourEndpoint, fCurrContourStartPoint);
this->joinTo(JoinType::kFromStroke, fCurrContourStartPoint, fCurrContourFirstControlPoint);
fHasLastControlPoint = false;
SkDEBUGCODE(fHasCurrentPoint = false;)
}
void GrStrokeHardwareTessellator::cap() {
void GrStrokeHardwareTessellator::cap(SkPoint contourEndpoint) {
SkASSERT(fViewMatrix);
SkASSERT(fHasCurrentPoint);
if (!fHasLastControlPoint) {
// We don't have any control points to orient the caps. In this case, square and round caps
@ -545,8 +530,8 @@ void GrStrokeHardwareTessellator::cap() {
}
fCurrContourFirstControlPoint = fCurrContourStartPoint - outset;
fLastControlPoint = fCurrContourStartPoint + outset;
fCurrentPoint = fCurrContourStartPoint;
fHasLastControlPoint = true;
contourEndpoint = fCurrContourStartPoint;
}
switch (fStroke->getCap()) {
@ -557,14 +542,15 @@ void GrStrokeHardwareTessellator::cap() {
// If our join type isn't round we can alternatively use a bowtie.
JoinType roundCapJoinType = (fStroke->getJoin() == SkPaint::kRound_Join)
? JoinType::kFromStroke : JoinType::kBowtie;
this->joinTo(roundCapJoinType, fLastControlPoint);
this->joinTo(roundCapJoinType, contourEndpoint, fLastControlPoint);
this->moveTo(fCurrContourStartPoint, fCurrContourFirstControlPoint);
this->joinTo(roundCapJoinType, fCurrContourFirstControlPoint);
this->joinTo(roundCapJoinType, fCurrContourStartPoint,
fCurrContourFirstControlPoint);
break;
}
case SkPaint::kSquare_Cap: {
// A square cap is the same as appending lineTos.
SkVector lastTangent = fCurrentPoint - fLastControlPoint;
SkVector lastTangent = contourEndpoint - fLastControlPoint;
if (!fStroke->isHairlineStyle()) {
// Extend the cap by 1/2 stroke width.
lastTangent *= (.5f * fStroke->getWidth()) / lastTangent.length();
@ -573,7 +559,7 @@ void GrStrokeHardwareTessellator::cap() {
lastTangent *=
.5f / fViewMatrix->mapVector(lastTangent.fX, lastTangent.fY).length();
}
this->lineTo(fCurrentPoint + lastTangent);
this->lineTo(contourEndpoint, contourEndpoint + lastTangent);
this->moveTo(fCurrContourStartPoint, fCurrContourFirstControlPoint);
SkVector firstTangent = fCurrContourFirstControlPoint - fCurrContourStartPoint;
if (!fStroke->isHairlineStyle()) {
@ -584,13 +570,12 @@ void GrStrokeHardwareTessellator::cap() {
firstTangent *=
-.5f / fViewMatrix->mapVector(firstTangent.fX, firstTangent.fY).length();
}
this->lineTo(fCurrContourStartPoint + firstTangent);
this->lineTo(fCurrContourStartPoint, fCurrContourStartPoint + firstTangent);
break;
}
}
fHasLastControlPoint = false;
SkDEBUGCODE(fHasCurrentPoint = false;)
}
void GrStrokeHardwareTessellator::emitPatch(JoinType prevJoinType, const SkPoint p[4],
@ -603,7 +588,7 @@ void GrStrokeHardwareTessellator::emitPatch(JoinType prevJoinType, const SkPoint
// TODO: Investigate if an optimization like "x < fCosRadiansPerSegment" would be worth it.
float rotation = SkMeasureAngleBetweenVectors(p[0] - fLastControlPoint, c1 - p[0]);
if (rotation * fTolerances.fNumRadialSegmentsPerRadian > 1) {
this->joinTo(prevJoinType, c1);
this->joinTo(prevJoinType, p[0], c1);
prevJoinType = JoinType::kNone;
}
}
@ -631,16 +616,15 @@ void GrStrokeHardwareTessellator::emitPatch(JoinType prevJoinType, const SkPoint
}
fLastControlPoint = c2;
fCurrentPoint = endPt;
}
void GrStrokeHardwareTessellator::emitJoinPatch(JoinType joinType, SkPoint nextControlPoint) {
void GrStrokeHardwareTessellator::emitJoinPatch(JoinType joinType, SkPoint junctionPoint,
SkPoint nextControlPoint) {
// We should never write out joins before the first curve.
SkASSERT(fHasLastControlPoint);
SkASSERT(fHasCurrentPoint);
if (this->reservePatch()) {
fPatchWriter.write(fLastControlPoint, fCurrentPoint);
fPatchWriter.write(fLastControlPoint, junctionPoint);
if (joinType == JoinType::kFromStroke) {
// [p0, p3, p3, p3] is a reserved pattern that means this patch is a join only (no cubic
// sections in the patch).
@ -648,7 +632,7 @@ void GrStrokeHardwareTessellator::emitJoinPatch(JoinType joinType, SkPoint nextC
} else {
SkASSERT(joinType == JoinType::kBowtie);
// [p0, p0, p0, p3] is a reserved pattern that means this patch is a bowtie.
fPatchWriter.write(fCurrentPoint, fCurrentPoint);
fPatchWriter.write(junctionPoint, junctionPoint);
}
fPatchWriter.write(nextControlPoint);
this->emitDynamicAttribs();

View File

@ -54,7 +54,7 @@ private:
void moveTo(SkPoint);
void moveTo(SkPoint, SkPoint lastControlPoint);
void lineTo(SkPoint, JoinType prevJoinType = JoinType::kFromStroke);
void lineTo(SkPoint p0, SkPoint p1, JoinType prevJoinType = JoinType::kFromStroke);
void conicTo(const SkPoint[3], float w, JoinType prevJoinType = JoinType::kFromStroke,
int maxDepth = -1);
void cubicTo(const SkPoint[4], JoinType prevJoinType = JoinType::kFromStroke,
@ -67,13 +67,13 @@ private:
const SkPoint& nextCtrlPt = (nextCubic[1] == nextCubic[0]) ? nextCubic[2] : nextCubic[1];
// The caller should have culled out curves where p0==p1==p2 by this point.
SkASSERT(nextCtrlPt != nextCubic[0]);
this->joinTo(joinType, nextCtrlPt);
this->joinTo(joinType, nextCubic[0], nextCtrlPt);
}
void joinTo(JoinType, SkPoint nextControlPoint, int maxDepth = -1);
void close();
void cap();
void joinTo(JoinType, SkPoint junctionPoint, SkPoint nextControlPoint, int maxDepth = -1);
void close(SkPoint contourEndpoint);
void cap(SkPoint contourEndpoint);
void emitPatch(JoinType prevJoinType, const SkPoint pts[4], SkPoint endPt);
void emitJoinPatch(JoinType, SkPoint nextControlPoint);
void emitJoinPatch(JoinType, SkPoint junctionPoint, SkPoint nextControlPoint);
void emitDynamicAttribs();
bool reservePatch();
void allocPatchChunkAtLeast(int minPatchAllocCount);
@ -122,11 +122,9 @@ private:
// Variables related to the specific contour that we are currently iterating during
// prepareBuffers().
bool fHasLastControlPoint = false;
SkDEBUGCODE(bool fHasCurrentPoint = false;)
SkPoint fCurrContourStartPoint;
SkPoint fCurrContourFirstControlPoint;
SkPoint fLastControlPoint;
SkPoint fCurrentPoint;
// Stateful values for the dynamic state (if any) that will get written out with each patch.
GrStrokeTessellateShader::DynamicStroke fDynamicStroke;