Add conic support to GrStrokeTessellateShader
Bug: chromium:1172543 Bug: skia:10419 Change-Id: Ie14ab2a3933b96541ee0afa3149c23964ff09ea5 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/364001 Commit-Queue: Chris Dalton <csmartdalton@google.com> Reviewed-by: Brian Salomon <bsalomon@google.com>
This commit is contained in:
parent
3bf14aaf11
commit
a7f6e08976
@ -125,13 +125,14 @@ void GrStrokeTessellateOp::prepareBuffers() {
|
||||
this->lineTo(pts[1]);
|
||||
break;
|
||||
case SkPathVerb::kQuad:
|
||||
this->quadraticTo(pts);
|
||||
this->conicTo(pts, 1);
|
||||
break;
|
||||
case SkPathVerb::kConic:
|
||||
this->conicTo(pts, *w);
|
||||
break;
|
||||
case SkPathVerb::kCubic:
|
||||
this->cubicTo(pts);
|
||||
break;
|
||||
case SkPathVerb::kConic:
|
||||
SkUNREACHABLE;
|
||||
case SkPathVerb::kClose:
|
||||
this->close();
|
||||
break;
|
||||
@ -173,8 +174,8 @@ void GrStrokeTessellateOp::lineTo(SkPoint pt, JoinType prevJoinType) {
|
||||
prevJoinType = JoinType::kNone;
|
||||
}
|
||||
|
||||
SkPoint asCubic[4] = {fCurrentPoint, fCurrentPoint, pt, pt};
|
||||
this->cubicToRaw(prevJoinType, asCubic);
|
||||
SkPoint asPatch[4] = {fCurrentPoint, fCurrentPoint, pt, pt};
|
||||
this->emitPatch(prevJoinType, asPatch, pt);
|
||||
}
|
||||
|
||||
static bool chop_pt_is_cusp(const SkPoint& prevControlPoint, const SkPoint& chopPoint,
|
||||
@ -184,28 +185,34 @@ static bool chop_pt_is_cusp(const SkPoint& prevControlPoint, const SkPoint& chop
|
||||
return (nextControlPoint - chopPoint).dot(chopPoint - prevControlPoint) <= 0;
|
||||
}
|
||||
|
||||
static bool quad_chop_is_cusp(const SkPoint chops[5]) {
|
||||
SkPoint chopPt = chops[2];
|
||||
SkPoint prevCtrlPt = (chops[1] != chopPt) ? chops[1] : chops[0];
|
||||
SkPoint nextCtrlPt = (chops[3] != chopPt) ? chops[3] : chops[4];
|
||||
static bool quad_chop_is_cusp(const SkPoint left[3], const SkPoint right[3]) {
|
||||
SkASSERT(left[2] == right[0]);
|
||||
SkPoint chopPt = left[2];
|
||||
SkPoint prevCtrlPt = (left[1] != chopPt) ? left[1] : left[0];
|
||||
SkPoint nextCtrlPt = (right[1] != chopPt) ? right[1] : right[2];
|
||||
return chop_pt_is_cusp(prevCtrlPt, chopPt, nextCtrlPt);
|
||||
}
|
||||
|
||||
void GrStrokeTessellateOp::quadraticTo(const SkPoint p[3], JoinType prevJoinType, int maxDepth) {
|
||||
void GrStrokeTessellateOp::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]) {
|
||||
if (p[1] == p[0] || p[1] == p[2] || w == 0) {
|
||||
this->lineTo(p[2], prevJoinType);
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert to a cubic.
|
||||
SkPoint asCubic[4];
|
||||
GrPathUtils::convertQuadToCubic(p, asCubic);
|
||||
// Convert to a patch.
|
||||
SkPoint asPatch[4];
|
||||
if (w == 1) {
|
||||
GrPathUtils::convertQuadToCubic(p, asPatch);
|
||||
} else {
|
||||
GrPathShader::WriteConicPatch(p, w, asPatch);
|
||||
}
|
||||
|
||||
// Ensure our hardware supports enough tessellation segments to render the curve. This early out
|
||||
// assumes a worst-case quadratic rotation of 180 degrees and a worst-case number of segments in
|
||||
@ -217,7 +224,7 @@ void GrStrokeTessellateOp::quadraticTo(const SkPoint p[3], JoinType prevJoinType
|
||||
GrWangsFormula::quadratic_pow4(fTolerances.fParametricIntolerance, p);
|
||||
if (numParametricSegments_pow4 <= fMaxParametricSegments180_pow4_withJoin &&
|
||||
prevJoinType != JoinType::kCusp) {
|
||||
this->cubicToRaw(prevJoinType, asCubic);
|
||||
this->emitPatch(prevJoinType, asPatch, p[2]);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -226,10 +233,10 @@ void GrStrokeTessellateOp::quadraticTo(const SkPoint p[3], JoinType prevJoinType
|
||||
prevJoinType == JoinType::kCusp) {
|
||||
// Either there aren't enough guaranteed segments to include the join in the quadratic's
|
||||
// patch, or we need a cusp. Emit a standalone patch for the join.
|
||||
this->joinTo(prevJoinType, asCubic);
|
||||
this->joinTo(prevJoinType, asPatch);
|
||||
prevJoinType = JoinType::kNone;
|
||||
}
|
||||
this->cubicToRaw(prevJoinType, asCubic);
|
||||
this->emitPatch(prevJoinType, asPatch, p[2]);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -250,18 +257,33 @@ void GrStrokeTessellateOp::quadraticTo(const SkPoint p[3], JoinType prevJoinType
|
||||
sk_float_nextlog2(numRadialSegments) + 1;
|
||||
maxDepth = std::max(maxDepth, 1);
|
||||
}
|
||||
SkPoint chops[5];
|
||||
if (numParametricSegments >= numRadialSegments) {
|
||||
SkChopQuadAtHalf(p, chops);
|
||||
if (w == 1) {
|
||||
SkPoint chops[5];
|
||||
if (numParametricSegments >= numRadialSegments) {
|
||||
SkChopQuadAtHalf(p, chops);
|
||||
} else {
|
||||
SkChopQuadAtMidTangent(p, chops);
|
||||
}
|
||||
this->conicTo(chops, 1, prevJoinType, maxDepth - 1);
|
||||
// If we chopped at a cusp then rotation is not continuous between the two curves.
|
||||
// Insert a cusp to make up for lost rotation.
|
||||
JoinType nextJoinType = (quad_chop_is_cusp(chops, chops + 2)) ?
|
||||
JoinType::kCusp : JoinType::kFromStroke;
|
||||
this->conicTo(chops + 2, 1, nextJoinType, maxDepth - 1);
|
||||
} else {
|
||||
SkChopQuadAtMidTangent(p, chops);
|
||||
SkConic conic(p, w);
|
||||
float chopT = (numParametricSegments >= numRadialSegments) ? .5f
|
||||
: conic.findMidTangent();
|
||||
SkConic chops[2];
|
||||
if (conic.chopAt(chopT, chops)) {
|
||||
this->conicTo(chops[0].fPts, chops[0].fW, prevJoinType, maxDepth - 1);
|
||||
// If we chopped at a cusp then rotation is not continuous between the two curves.
|
||||
// Insert a cusp to make up for lost rotation.
|
||||
JoinType nextJoinType = (quad_chop_is_cusp(chops[0].fPts, chops[1].fPts)) ?
|
||||
JoinType::kCusp : JoinType::kFromStroke;
|
||||
this->conicTo(chops[1].fPts, chops[1].fW, nextJoinType, maxDepth - 1);
|
||||
}
|
||||
}
|
||||
this->quadraticTo(chops, prevJoinType, maxDepth - 1);
|
||||
// If we chopped at a cusp then rotation is not continuous between the two curves. Insert a
|
||||
// cusp to make up for lost rotation.
|
||||
JoinType nextJoinType = (quad_chop_is_cusp(chops)) ?
|
||||
JoinType::kCusp : JoinType::kFromStroke;
|
||||
this->quadraticTo(chops + 2, nextJoinType, maxDepth - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -269,10 +291,10 @@ void GrStrokeTessellateOp::quadraticTo(const SkPoint p[3], JoinType prevJoinType
|
||||
prevJoinType == JoinType::kCusp) {
|
||||
// Either there aren't enough guaranteed segments to include the join in the quadratic's
|
||||
// patch, or we need a cusp. Emit a standalone patch for the join.
|
||||
this->joinTo(prevJoinType, asCubic);
|
||||
this->joinTo(prevJoinType, asPatch);
|
||||
prevJoinType = JoinType::kNone;
|
||||
}
|
||||
this->cubicToRaw(prevJoinType, asCubic);
|
||||
this->emitPatch(prevJoinType, asPatch, p[2]);
|
||||
}
|
||||
|
||||
static bool cubic_chop_is_cusp(const SkPoint chops[7]) {
|
||||
@ -304,7 +326,7 @@ void GrStrokeTessellateOp::cubicTo(const SkPoint p[4], JoinType prevJoinType,
|
||||
GrWangsFormula::cubic_pow4(fTolerances.fParametricIntolerance, p);
|
||||
if (numParametricSegments_pow4 <= fMaxParametricSegments360_pow4_withJoin &&
|
||||
prevJoinType != JoinType::kCusp) {
|
||||
this->cubicToRaw(prevJoinType, p);
|
||||
this->emitPatch(prevJoinType, p, p[3]);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -320,7 +342,7 @@ void GrStrokeTessellateOp::cubicTo(const SkPoint p[4], JoinType prevJoinType,
|
||||
this->joinTo(prevJoinType, p);
|
||||
prevJoinType = JoinType::kNone;
|
||||
}
|
||||
this->cubicToRaw(prevJoinType, p);
|
||||
this->emitPatch(prevJoinType, p, p[3]);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -385,7 +407,7 @@ void GrStrokeTessellateOp::cubicTo(const SkPoint p[4], JoinType prevJoinType,
|
||||
this->joinTo(prevJoinType, p);
|
||||
prevJoinType = JoinType::kNone;
|
||||
}
|
||||
this->cubicToRaw(prevJoinType, p);
|
||||
this->emitPatch(prevJoinType, p, p[3]);
|
||||
}
|
||||
|
||||
void GrStrokeTessellateOp::joinTo(JoinType joinType, SkPoint nextControlPoint, int maxDepth) {
|
||||
@ -432,7 +454,7 @@ void GrStrokeTessellateOp::joinTo(JoinType joinType, SkPoint nextControlPoint, i
|
||||
}
|
||||
}
|
||||
|
||||
this->joinToRaw(joinType, nextControlPoint);
|
||||
this->emitJoinPatch(joinType, nextControlPoint);
|
||||
}
|
||||
|
||||
void GrStrokeTessellateOp::close() {
|
||||
@ -539,13 +561,13 @@ void GrStrokeTessellateOp::cap() {
|
||||
SkDEBUGCODE(fHasCurrentPoint = false;)
|
||||
}
|
||||
|
||||
void GrStrokeTessellateOp::cubicToRaw(JoinType prevJoinType, const SkPoint pts[4]) {
|
||||
void GrStrokeTessellateOp::emitPatch(JoinType prevJoinType, const SkPoint pts[4], SkPoint endPt) {
|
||||
// Cusps can't be combined with a stroke patch. They need to have been written out already as
|
||||
// their own standalone patch.
|
||||
SkASSERT(prevJoinType != JoinType::kCusp);
|
||||
|
||||
SkPoint c1 = (pts[1] == pts[0]) ? pts[2] : pts[1];
|
||||
SkPoint c2 = (pts[2] == pts[3]) ? pts[1] : pts[2];
|
||||
SkPoint c2 = (pts[2] == endPt) ? pts[1] : pts[2];
|
||||
|
||||
if (!fHasLastControlPoint) {
|
||||
// The first stroke doesn't have a previous join (yet). If the current contour ends up
|
||||
@ -569,10 +591,10 @@ void GrStrokeTessellateOp::cubicToRaw(JoinType prevJoinType, const SkPoint pts[4
|
||||
}
|
||||
|
||||
fLastControlPoint = c2;
|
||||
fCurrentPoint = pts[3];
|
||||
fCurrentPoint = endPt;
|
||||
}
|
||||
|
||||
void GrStrokeTessellateOp::joinToRaw(JoinType joinType, SkPoint nextControlPoint) {
|
||||
void GrStrokeTessellateOp::emitJoinPatch(JoinType joinType, SkPoint nextControlPoint) {
|
||||
// We should never write out joins before the first curve.
|
||||
SkASSERT(fHasLastControlPoint);
|
||||
SkASSERT(fHasCurrentPoint);
|
||||
|
@ -48,8 +48,8 @@ private:
|
||||
void moveTo(SkPoint);
|
||||
void moveTo(SkPoint, SkPoint lastControlPoint);
|
||||
void lineTo(SkPoint, JoinType prevJoinType = JoinType::kFromStroke);
|
||||
void quadraticTo(const SkPoint[3], JoinType prevJoinType = JoinType::kFromStroke,
|
||||
int maxDepth = -1);
|
||||
void conicTo(const SkPoint[3], float w, JoinType prevJoinType = JoinType::kFromStroke,
|
||||
int maxDepth = -1);
|
||||
void cubicTo(const SkPoint[4], JoinType prevJoinType = JoinType::kFromStroke,
|
||||
Convex180Status = Convex180Status::kUnknown, int maxDepth = -1);
|
||||
void joinTo(JoinType joinType, const SkPoint nextCubic[]) {
|
||||
@ -61,8 +61,8 @@ private:
|
||||
void joinTo(JoinType, SkPoint nextControlPoint, int maxDepth = -1);
|
||||
void close();
|
||||
void cap();
|
||||
void cubicToRaw(JoinType prevJoinType, const SkPoint pts[4]);
|
||||
void joinToRaw(JoinType, SkPoint nextControlPoint);
|
||||
void emitPatch(JoinType prevJoinType, const SkPoint pts[4], SkPoint endPt);
|
||||
void emitJoinPatch(JoinType, SkPoint nextControlPoint);
|
||||
GrStrokeTessellateShader::Patch* reservePatch();
|
||||
void allocPatchChunkAtLeast(int minPatchAllocCount);
|
||||
|
||||
|
@ -34,11 +34,10 @@ private:
|
||||
auto* uniHandler = args.fUniformHandler;
|
||||
auto* v = args.fVertBuilder;
|
||||
|
||||
SkASSERT(!shader.fHasConics);
|
||||
|
||||
args.fVaryingHandler->emitAttributes(shader);
|
||||
|
||||
// uNumSegmentsInJoin, uWangsTermPow2, uNumRadialSegmentsPerRadian, uMiterLimitInvPow2.
|
||||
// uNumSegmentsInJoin, uParametricIntolerance, uNumRadialSegmentsPerRadian,
|
||||
// uMiterLimitInvPow2.
|
||||
fTessArgs1Uniform = uniHandler->addUniform(nullptr, kTessControl_GrShaderFlag,
|
||||
kFloat4_GrSLType, "tessArgs1", nullptr);
|
||||
// uJoinTolerancePow2, uStrokeRadius.
|
||||
@ -99,8 +98,17 @@ private:
|
||||
if (shader.fStroke.isHairlineStyle() && !shader.viewMatrix().isIdentity()) {
|
||||
// Hairline case. Transform the points before tessellation. We can still hold off on the
|
||||
// translate until the end; we just need to perform the scale and skew right now.
|
||||
if (shader.fHasConics) {
|
||||
v->codeAppend(R"(
|
||||
P[0] = uAffineMatrix * P[0];
|
||||
P[1] = uAffineMatrix * P[1];
|
||||
P[2] = uAffineMatrix * P[2];
|
||||
P[3] = isinf(P[3].y) ? P[3] : uAffineMatrix * P[3];)");
|
||||
} else {
|
||||
v->codeAppend(R"(
|
||||
P = uAffineMatrix * P;)");
|
||||
}
|
||||
v->codeAppend(R"(
|
||||
P = uAffineMatrix * P;
|
||||
prevControlPoint = uAffineMatrix * prevControlPoint;)");
|
||||
}
|
||||
v->codeAppendf(R"(
|
||||
@ -109,7 +117,7 @@ private:
|
||||
// Find the beginning and ending tangents. It's imperative that we compute these tangents
|
||||
// form the original input points or else the seams might crack.
|
||||
float2 tan0 = (P[1] == P[0]) ? P[2] - P[0] : P[1] - P[0];
|
||||
float2 tan1 = (P[3] == P[2]) ? P[3] - P[1] : P[3] - P[2];
|
||||
float2 tan1 = (P[3] == P[2] || isinf(P[3].y)) ? P[2] - P[1] : P[3] - P[2];
|
||||
|
||||
if (tan1 == float2(0)) {
|
||||
// [p0, p3, p3, p3] is a reserved pattern that means this patch is a join only.
|
||||
@ -224,8 +232,8 @@ private:
|
||||
innerTangents = float2x2(innerTangents[1], innerTangents[0]);
|
||||
}
|
||||
|
||||
// If the curve is a straight line or point, don't chop it into sections after all.
|
||||
if (P[0] == P[1] && P[2] == P[3]) {
|
||||
// If the curve is a straight line, point, or conic, don't chop it into sections after all.
|
||||
if ((P[0] == P[1] && P[2] == P[3]) || isinf(P[3].y)) {
|
||||
chopT = float2(0);
|
||||
innerTangents = float2x2(tan0, tan0);
|
||||
}
|
||||
@ -233,7 +241,7 @@ private:
|
||||
// Chop the curve at chopT[0] and chopT[1].
|
||||
float4 ab = unchecked_mix(P[0].xyxy, P[1].xyxy, chopT.sstt);
|
||||
float4 bc = unchecked_mix(P[1].xyxy, P[2].xyxy, chopT.sstt);
|
||||
float4 cd = unchecked_mix(P[2].xyxy, P[3].xyxy, chopT.sstt);
|
||||
float4 cd = isinf(P[3].y) ? P[2].xyxy : unchecked_mix(P[2].xyxy, P[3].xyxy, chopT.sstt);
|
||||
float4 abc = unchecked_mix(ab, bc, chopT.sstt);
|
||||
float4 bcd = unchecked_mix(bc, cd, chopT.sstt);
|
||||
float4 abcd = unchecked_mix(abc, bcd, chopT.sstt);
|
||||
@ -290,8 +298,7 @@ private:
|
||||
float miterLimit = shader.fStroke.getMiter();
|
||||
pdman.set4f(fTessArgs1Uniform,
|
||||
numSegmentsInJoin, // uNumSegmentsInJoin
|
||||
GrWangsFormula::length_term_pow2<3>(
|
||||
tolerances.fParametricIntolerance), // uWangsTermPow2
|
||||
tolerances.fParametricIntolerance, // uParametricIntolerance
|
||||
tolerances.fNumRadialSegmentsPerRadian, // uNumRadialSegmentsPerRadian
|
||||
1 / (miterLimit * miterLimit)); // uMiterLimitInvPow2
|
||||
float strokeRadius = (stroke.isHairlineStyle()) ? .5f : stroke.getWidth() * .5;
|
||||
@ -335,12 +342,22 @@ float length_pow2(float2 v) {
|
||||
// Calculates the number of evenly spaced (in the parametric sense) segments to chop a cubic into.
|
||||
// (See GrWangsFormula::cubic() for more documentation on this formula.) The final tessellated strip
|
||||
// will be a composition of these parametric segments as well as radial segments.
|
||||
static const char* kWangsFormulaCubicFn = R"(
|
||||
float wangs_formula_cubic(float4x2 P, float wangsTermPow2) {
|
||||
float m = max(length_pow2(fma(float2(-2), P[1], P[2]) + P[0]),
|
||||
length_pow2(fma(float2(-2), P[2], P[3]) + P[1])) * wangsTermPow2;
|
||||
return ceil(inversesqrt(inversesqrt(max(m, 1e-2))));
|
||||
})";
|
||||
static void append_wangs_formula_fn(SkString* code, bool hasConics) {
|
||||
code->appendf(R"(
|
||||
float wangs_formula(in float4x2 P, in float w, in float parametricIntolerance) {
|
||||
const float CUBIC_TERM_POW2 = %f;
|
||||
float l0 = length_pow2(fma(float2(-2), P[1], P[2]) + P[0]);
|
||||
float l1 = length_pow2(fma(float2(-2), P[2], P[3]) + P[1]);
|
||||
float m = CUBIC_TERM_POW2 * max(l0, l1);)", GrWangsFormula::length_term_pow2<3>(1));
|
||||
if (hasConics) {
|
||||
code->appendf(R"(
|
||||
const float QUAD_TERM_POW2 = %f;
|
||||
m = (w > 0) ? QUAD_TERM_POW2 * l0 : m;)", GrWangsFormula::length_term_pow2<2>(1));
|
||||
}
|
||||
code->append(R"(
|
||||
return max(ceil(sqrt(parametricIntolerance * sqrt(m))), 1);
|
||||
})");
|
||||
}
|
||||
|
||||
// Extends the middle radius to either the miter point, or the bevel edge if we surpassed the miter
|
||||
// limit and need to revert to a bevel join.
|
||||
@ -374,7 +391,7 @@ SkString GrStrokeTessellateShader::getTessControlShaderGLSL(
|
||||
const char* tessArgs1Name = impl->getTessArgs1UniformName(uniformHandler);
|
||||
code.appendf("uniform vec4 %s;\n", tessArgs1Name);
|
||||
code.appendf("#define uNumSegmentsInJoin %s.x\n", tessArgs1Name);
|
||||
code.appendf("#define uWangsTermPow2 %s.y\n", tessArgs1Name);
|
||||
code.appendf("#define uParametricIntolerance %s.y\n", tessArgs1Name);
|
||||
code.appendf("#define uNumRadialSegmentsPerRadian %s.z\n", tessArgs1Name);
|
||||
code.appendf("#define uMiterLimitInvPow2 %s.w\n", tessArgs1Name);
|
||||
|
||||
@ -404,7 +421,7 @@ SkString GrStrokeTessellateShader::getTessControlShaderGLSL(
|
||||
|
||||
code.append(kAtan2Fn);
|
||||
code.append(kLengthPow2Fn);
|
||||
code.append(kWangsFormulaCubicFn);
|
||||
append_wangs_formula_fn(&code, fHasConics);
|
||||
code.append(kMiterExtentFn);
|
||||
|
||||
code.append(R"(
|
||||
@ -432,7 +449,8 @@ SkString GrStrokeTessellateShader::getTessControlShaderGLSL(
|
||||
|
||||
// Calculate the number of parametric segments. The final tessellated strip will be a
|
||||
// composition of these parametric segments as well as radial segments.
|
||||
float numParametricSegments = wangs_formula_cubic(P, uWangsTermPow2);
|
||||
float w = isinf(P[3].y) ? P[3].x : -1; // w<0 means the curve is an integral cubic.
|
||||
float numParametricSegments = wangs_formula(P, w, uParametricIntolerance);
|
||||
if (P[0] == P[1] && P[2] == P[3]) {
|
||||
// This is how the patch builder articulates lineTos but Wang's formula returns
|
||||
// >>1 segment in this scenario. Assign 1 parametric segment.
|
||||
@ -452,7 +470,8 @@ SkString GrStrokeTessellateShader::getTessControlShaderGLSL(
|
||||
// Adjust sign of rotation to match the direction the curve turns.
|
||||
// NOTE: Since the curve is not allowed to inflect, we can just check F'(.5) x F''(.5).
|
||||
// NOTE: F'(.5) x F''(.5) has the same sign as (P2 - P0) x (P3 - P1)
|
||||
float turn = cross(P[2] - P[0], P[3] - P[1]);
|
||||
float turn = isinf(P[3].y) ? cross(P[1] - P[0], P[2] - P[1])
|
||||
: cross(P[2] - P[0], P[3] - P[1]);
|
||||
if (turn == 0) { // This will be the case for joins and cusps where points are co-located.
|
||||
turn = determinant(tangents);
|
||||
}
|
||||
@ -561,15 +580,9 @@ float2 unchecked_mix(float2 a, float2 b, float T) {
|
||||
// "combinedEdgeID", where combinedEdgeID is the sorted-order index of parametric and radial edges.
|
||||
static void append_eval_stroke_edge_fn(SkString* code, bool hasConics) {
|
||||
code->append(R"(
|
||||
void eval_stroke_edge(in float4x2 P, )");
|
||||
if (hasConics) {
|
||||
code->append(R"(
|
||||
in float w, )");
|
||||
}
|
||||
code->append(R"(
|
||||
in float numParametricSegments, in float combinedEdgeID, in float2 tan0,
|
||||
in float radsPerSegment, in float angle0, out float2 tangent,
|
||||
out float2 position) {
|
||||
void eval_stroke_edge(in float4x2 P, in float w, in float numParametricSegments,
|
||||
in float combinedEdgeID, in float2 tan0, in float radsPerSegment,
|
||||
in float angle0, out float2 tangent, out float2 position) {
|
||||
// Start by finding the tangent function's power basis coefficients. These define a tangent
|
||||
// direction (scaled by some uniform value) as:
|
||||
// |T^2|
|
||||
@ -766,7 +779,7 @@ SkString GrStrokeTessellateShader::getTessEvaluationShaderGLSL(
|
||||
uniform vec4 sk_RTAdjust;)");
|
||||
|
||||
code.append(kUncheckedMixFn);
|
||||
append_eval_stroke_edge_fn(&code, false/*hasConics*/);
|
||||
append_eval_stroke_edge_fn(&code, fHasConics);
|
||||
|
||||
code.append(R"(
|
||||
void main() {
|
||||
@ -814,8 +827,19 @@ SkString GrStrokeTessellateShader::getTessEvaluationShaderGLSL(
|
||||
float angle0 = tessellationArgs.y;
|
||||
float radsPerSegment = tessellationArgs.z;
|
||||
|
||||
float w = -1; // w<0 means the curve is an integral cubic.)");
|
||||
|
||||
if (fHasConics) {
|
||||
code.append(R"(
|
||||
if (isinf(P[3].y)) {
|
||||
w = P[3].x; // The curve is actually a conic.
|
||||
P[3] = P[2]; // Setting p3 equal to p2 works for the remaining rotational logic.
|
||||
})");
|
||||
}
|
||||
|
||||
code.append(R"(
|
||||
float2 tangent, position;
|
||||
eval_stroke_edge(P, numParametricSegments, localEdgeID, tan0, radsPerSegment, angle0,
|
||||
eval_stroke_edge(P, w, numParametricSegments, localEdgeID, tan0, radsPerSegment, angle0,
|
||||
tangent, position);
|
||||
|
||||
if (localEdgeID == 0) {
|
||||
@ -829,7 +853,7 @@ SkString GrStrokeTessellateShader::getTessEvaluationShaderGLSL(
|
||||
// The final edge of the quad strip always uses the provided endPt and endTan. This
|
||||
// ensures crack-free seaming between patches.
|
||||
tangent = tcsEndPtEndTan.zw;
|
||||
position = tcsEndPtEndTan.xy;
|
||||
position = P[3];
|
||||
}
|
||||
|
||||
// Determine how far to outset our vertex orthogonally from the curve.
|
||||
@ -868,16 +892,13 @@ class GrStrokeTessellateShader::IndirectImpl : public GrGLSLGeometryProcessor {
|
||||
args.fVertBuilder->defineConstant("MAX_PARAMETRIC_SEGMENTS_LOG2",
|
||||
GrTessellationPathRenderer::kMaxResolveLevel);
|
||||
args.fVertBuilder->defineConstant("float", "PI", "3.141592653589793238");
|
||||
args.fVertBuilder->defineConstant("QUAD_TERM_POW2",
|
||||
GrWangsFormula::length_term_pow2<2>(1));
|
||||
args.fVertBuilder->defineConstant("CUBIC_TERM_POW2",
|
||||
GrWangsFormula::length_term_pow2<3>(1));
|
||||
|
||||
// Helper functions.
|
||||
args.fVertBuilder->insertFunction(kAtan2Fn);
|
||||
args.fVertBuilder->insertFunction(kLengthPow2Fn);
|
||||
args.fVertBuilder->insertFunction(kMiterExtentFn);
|
||||
args.fVertBuilder->insertFunction(kUncheckedMixFn);
|
||||
append_wangs_formula_fn(&args.fVertBuilder->functions(), shader.fHasConics);
|
||||
append_eval_stroke_edge_fn(&args.fVertBuilder->functions(), shader.fHasConics);
|
||||
args.fVertBuilder->insertFunction(R"(
|
||||
float cosine_between_vectors(float2 a, float2 b) {
|
||||
@ -911,10 +932,10 @@ class GrStrokeTessellateShader::IndirectImpl : public GrGLSLGeometryProcessor {
|
||||
// Tessellation code.
|
||||
args.fVertBuilder->codeAppend(R"(
|
||||
float4x2 P = float4x2(pts01, pts23);
|
||||
float2 lastControlPoint = args.xy;)");
|
||||
float2 lastControlPoint = args.xy;
|
||||
float w = -1; // w<0 means the curve is an integral cubic.)");
|
||||
if (shader.fHasConics) {
|
||||
args.fVertBuilder->codeAppend(R"(
|
||||
float w = -1; // w<0 means the curve is an integral cubic.
|
||||
if (isinf(P[3].y)) {
|
||||
w = P[3].x; // The curve is actually a conic.
|
||||
P[3] = P[2]; // Setting p3 equal to p2 works for the remaining rotational logic.
|
||||
@ -930,18 +951,9 @@ class GrStrokeTessellateShader::IndirectImpl : public GrGLSLGeometryProcessor {
|
||||
args.fVertBuilder->codeAppend(R"(
|
||||
float numTotalEdges = abs(args.z);
|
||||
|
||||
// Use wang's formula to find how many parametric segments this stroke requires.
|
||||
float l0 = length_pow2(fma(float2(-2), P[1], P[2]) + P[0]);
|
||||
float l1 = length_pow2(fma(float2(-2), P[2], P[3]) + P[1]);)");
|
||||
|
||||
args.fVertBuilder->codeAppendf(R"(
|
||||
float m =%s CUBIC_TERM_POW2 * max(l0, l1);)",
|
||||
(shader.fHasConics) ? " (w >= 0) ? QUAD_TERM_POW2 * l0 :" : "");
|
||||
|
||||
args.fVertBuilder->codeAppend(R"(
|
||||
float numParametricSegments = ceil(sqrt(uParametricIntolerance * sqrt(m)));
|
||||
numParametricSegments = clamp(numParametricSegments,
|
||||
1, float(1 << MAX_PARAMETRIC_SEGMENTS_LOG2));
|
||||
// Find how many parametric segments this stroke requires.
|
||||
float numParametricSegments = min(wangs_formula(P, w, uParametricIntolerance),
|
||||
float(1 << MAX_PARAMETRIC_SEGMENTS_LOG2));
|
||||
if (P[0] == P[1] && P[2] == P[3]) {
|
||||
// This is how we describe lines, but Wang's formula does not return 1 in this case.
|
||||
numParametricSegments = 1;
|
||||
@ -1058,13 +1070,7 @@ class GrStrokeTessellateShader::IndirectImpl : public GrGLSLGeometryProcessor {
|
||||
|
||||
args.fVertBuilder->codeAppendf(R"(
|
||||
float2 tangent, strokeCoord;
|
||||
eval_stroke_edge(P,)");
|
||||
if (shader.fHasConics) {
|
||||
args.fVertBuilder->codeAppend(R"(
|
||||
w,)");
|
||||
}
|
||||
args.fVertBuilder->codeAppend(R"(
|
||||
numParametricSegments, combinedEdgeID, tan0, radsPerSegment, angle0,
|
||||
eval_stroke_edge(P, w, numParametricSegments, combinedEdgeID, tan0, radsPerSegment, angle0,
|
||||
tangent, strokeCoord);)");
|
||||
|
||||
args.fVertBuilder->codeAppend(R"(
|
||||
|
@ -218,8 +218,7 @@ static GrOp::Owner make_op(GrRecordingContext* rContext, const GrSurfaceContext*
|
||||
// either, so if the paint uses varyings we need to use indirect draws.
|
||||
if (shaderCaps.tessellationSupport() &&
|
||||
path.countVerbs() > 50 &&
|
||||
!paint.usesVaryingCoords() &&
|
||||
!SkPathPriv::ConicWeightCnt(path)) {
|
||||
!paint.usesVaryingCoords()) {
|
||||
return GrOp::Make<GrStrokeTessellateOp>(rContext, aaType, viewMatrix, stroke, path,
|
||||
std::move(paint));
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user