Implement indirect stroking for conics
Just like for fills, we use the normal Wang's formula on the conic's
down-projected control points until we can formalize on a better
formula.
Bug: skia:10419
Change-Id: Ifd735534a2e793f79f4f5d5b7e7acf50db81fe5e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/341156
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
Reviewed-by: Tyler Denniston <tdenniston@google.com>
diff --git a/gm/trickycubicstrokes.cpp b/gm/trickycubicstrokes.cpp
index 2e905f0..c551cec 100644
--- a/gm/trickycubicstrokes.cpp
+++ b/gm/trickycubicstrokes.cpp
@@ -26,7 +26,7 @@
static constexpr float kStrokeWidth = 30;
static constexpr int kCellSize = 200;
static constexpr int kNumCols = 5;
-static constexpr int kNumRows = 4;
+static constexpr int kNumRows = 5;
static constexpr int kTestWidth = kNumCols * kCellSize;
static constexpr int kTestHeight = kNumRows * kCellSize;
@@ -64,8 +64,12 @@
CellFillMode::kStretch}, // Flat line with no turns
{{{0.5f,0}, {0,0}, {20,0}, {10,0}}, 4, CellFillMode::kStretch}, // Flat line with 2 180s
{{{10,0}, {0,0}, {10,0}, {10,0}}, 4, CellFillMode::kStretch}, // Flat line with a 180
- {{{1,1}, {2,1}, {1,1}, {std::numeric_limits<float>::quiet_NaN(), 0}}, 3,
- CellFillMode::kStretch}, // Flat QUAD with a 180
+ {{{1,1}, {2,1}, {1,1}, {1, std::numeric_limits<float>::quiet_NaN()}}, 3,
+ CellFillMode::kStretch}, // Flat QUAD with a cusp
+ {{{1,1}, {100,1}, {25,1}, {.3f, std::numeric_limits<float>::quiet_NaN()}}, 3,
+ CellFillMode::kStretch}, // Flat CONIC with a cusp
+ {{{1,1}, {100,1}, {25,1}, {1.5f, std::numeric_limits<float>::quiet_NaN()}}, 3,
+ CellFillMode::kStretch}, // Flat CONIC with a cusp
};
static SkRect calc_tight_cubic_bounds(const SkPoint P[4], int depth=5) {
@@ -113,6 +117,7 @@
for (int j = 0; j < numPts; ++j) {
p[j] *= scale;
}
+ float w = originalPts[3].fX;
auto cellRect = SkRect::MakeXYWH((i % kNumCols) * kCellSize, (i / kNumCols) * kCellSize,
kCellSize, kCellSize);
@@ -143,9 +148,12 @@
SkPath path = SkPath().moveTo(p[0]);
if (numPts == 4) {
path.cubicTo(p[1], p[2], p[3]);
- } else {
+ } else if (w == 1) {
SkASSERT(numPts == 3);
path.quadTo(p[1], p[2]);
+ } else {
+ SkASSERT(numPts == 3);
+ path.conicTo(p[1], p[2], w);
}
canvas->drawPath(path, strokePaint);
}
diff --git a/samplecode/SampleCCPRGeometry.cpp b/samplecode/SampleCCPRGeometry.cpp
index 89ed0f8..399a453 100644
--- a/samplecode/SampleCCPRGeometry.cpp
+++ b/samplecode/SampleCCPRGeometry.cpp
@@ -422,10 +422,10 @@
return true;
}
float* valueToScale = nullptr;
- if (fDoStroke) {
- valueToScale = &fStrokeWidth;
- } else if (PrimitiveType::kConics == fPrimitiveType) {
+ if (PrimitiveType::kConics == fPrimitiveType) {
valueToScale = &fConicWeight;
+ } else if (fDoStroke) {
+ valueToScale = &fStrokeWidth;
}
if (valueToScale) {
if (unichar == '+') {
diff --git a/src/core/SkGeometry.cpp b/src/core/SkGeometry.cpp
index ff56d1c..eab8227 100644
--- a/src/core/SkGeometry.cpp
+++ b/src/core/SkGeometry.cpp
@@ -588,6 +588,25 @@
return SkNx_fma(f, Sk4f(m), a);
}
+// Finds the root nearest 0.5. Returns 0.5 if the roots are undefined or outside 0..1.
+static float solve_quadratic_equation_for_midtangent(float a, float b, float c, float discr) {
+ // Quadratic formula from Numerical Recipes in C:
+ float q = -.5f * (b + copysignf(sqrtf(discr), b));
+ // The roots are q/a and c/q. Pick the midtangent closer to T=.5.
+ float _5qa = -.5f*q*a;
+ float T = fabsf(q*q + _5qa) < fabsf(a*c + _5qa) ? sk_ieee_float_divide(q,a)
+ : sk_ieee_float_divide(c,q);
+ if (!(T > 0 && T < 1)) { // Use "!(positive_logic)" so T=NaN will take this branch.
+ // Either the curve is a flat line with no rotation or FP precision failed us. Chop at .5.
+ T = .5;
+ }
+ return T;
+}
+
+static float solve_quadratic_equation_for_midtangent(float a, float b, float c) {
+ return solve_quadratic_equation_for_midtangent(a, b, c, b*b - 4*a*c);
+}
+
float SkFindCubicMidTangent(const SkPoint src[4]) {
// Tangents point in the direction of increasing T, so tan0 and -tan1 both point toward the
// midtangent. The bisector of tan0 and -tan1 is orthogonal to the midtangent:
@@ -623,13 +642,7 @@
float a=coeffs[0], b=coeffs[1], c=coeffs[2];
float discr = b*b - 4*a*c;
if (discr > 0) { // This will only be false if the curve is a line.
- // Quadratic formula from Numerical Recipes in C:
- float q = -.5f * (b + copysignf(std::sqrt(discr), b));
- // The roots are q/a and c/q. Pick the midtangent closer to T=.5.
- float qa_5 = .5f*q*a;
- if (a != 0 || q != 0) {
- T = std::abs(q*q - qa_5) < std::abs(a*c - qa_5) ? q/a : c/q;
- }
+ return solve_quadratic_equation_for_midtangent(a, b, c, discr);
} else {
// This is a 0- or 360-degree flat line. It doesn't have single points of midtangent.
// (tangent == midtangent at every point on the curve except the cusp points.)
@@ -651,13 +664,13 @@
// -b / (2*a)
T = -b / (2*a);
}
+ if (!(T > 0 && T < 1)) { // Use "!(positive_logic)" so T=NaN will take this branch.
+ // Either the curve is a flat line with no rotation or FP precision failed us. Chop at
+ // .5.
+ T = .5;
+ }
+ return T;
}
- if (!(T > 0 && T < 1)) { // Use "!(positive_logic)" so T=NaN will take this branch.
- // Either the curve is a flat line with no rotation or FP precision failed us. Chop at .5.
- T = .5;
- }
-
- return T;
}
static void flatten_double_cubic_extrema(SkScalar coords[14]) {
@@ -1499,6 +1512,43 @@
return 1 << pow2;
}
+float SkConic::findMidTangent() const {
+ // Tangents point in the direction of increasing T, so tan0 and -tan1 both point toward the
+ // midtangent. The bisector of tan0 and -tan1 is orthogonal to the midtangent:
+ //
+ // bisector dot midtangent = 0
+ //
+ SkVector tan0 = fPts[1] - fPts[0];
+ SkVector tan1 = fPts[2] - fPts[1];
+ SkVector bisector = SkFindBisector(tan0, -tan1);
+
+ // Start by finding the tangent function's power basis coefficients. These define a tangent
+ // direction (scaled by some uniform value) as:
+ // |T^2|
+ // Tangent_Direction(T) = dx,dy = |A B C| * |T |
+ // |. . .| |1 |
+ //
+ // The derivative of a conic has a cumbersome order-4 denominator. However, this isn't necessary
+ // if we are only interested in a vector in the same *direction* as a given tangent line. Since
+ // the denominator scales dx and dy uniformly, we can throw it out completely after evaluating
+ // the derivative with the standard quotient rule. This leaves us with a simpler quadratic
+ // function that we use to find a tangent.
+ SkVector A = (fPts[2] - fPts[0]) * (fW - 1);
+ SkVector B = (fPts[2] - fPts[0]) - (fPts[1] - fPts[0]) * (fW*2);
+ SkVector C = (fPts[1] - fPts[0]) * fW;
+
+ // Now solve for "bisector dot midtangent = 0":
+ //
+ // |T^2|
+ // bisector * |A B C| * |T | = 0
+ // |. . .| |1 |
+ //
+ float a = bisector.dot(A);
+ float b = bisector.dot(B);
+ float c = bisector.dot(C);
+ return solve_quadratic_equation_for_midtangent(a, b, c);
+}
+
bool SkConic::findXExtrema(SkScalar* t) const {
return conic_find_extrema(&fPts[0].fX, fW, t);
}
diff --git a/src/core/SkGeometry.h b/src/core/SkGeometry.h
index 4c4e72d..85efc7b 100644
--- a/src/core/SkGeometry.h
+++ b/src/core/SkGeometry.h
@@ -352,6 +352,7 @@
*/
int SK_SPI SK_WARN_UNUSED_RESULT chopIntoQuadsPOW2(SkPoint pts[], int pow2) const;
+ float findMidTangent() const;
bool findXExtrema(SkScalar* t) const;
bool findYExtrema(SkScalar* t) const;
bool chopAtXExtrema(SkConic dst[2]) const;
diff --git a/src/gpu/glsl/GrGLSLVertexGeoBuilder.h b/src/gpu/glsl/GrGLSLVertexGeoBuilder.h
index 882f22a..913e120 100644
--- a/src/gpu/glsl/GrGLSLVertexGeoBuilder.h
+++ b/src/gpu/glsl/GrGLSLVertexGeoBuilder.h
@@ -22,6 +22,7 @@
void insertFunction(const char* functionDefinition) {
this->functions().append(functionDefinition);
}
+ using GrGLSLShaderBuilder::functions;
protected:
GrGLSLVertexGeoBuilder(GrGLSLProgramBuilder* program) : INHERITED(program) {}
diff --git a/src/gpu/tessellate/GrStrokeIndirectOp.cpp b/src/gpu/tessellate/GrStrokeIndirectOp.cpp
index a33c15e..b34d6df 100644
--- a/src/gpu/tessellate/GrStrokeIndirectOp.cpp
+++ b/src/gpu/tessellate/GrStrokeIndirectOp.cpp
@@ -35,8 +35,8 @@
return;
}
auto* strokeTessellateShader = arena->make<GrStrokeTessellateShader>(
- GrStrokeTessellateShader::Mode::kIndirect, fStroke, fParametricIntolerance,
- fNumRadialSegmentsPerRadian, fViewMatrix, fColor);
+ GrStrokeTessellateShader::Mode::kIndirect, fTotalConicWeightCnt, fStroke,
+ fParametricIntolerance, fNumRadialSegmentsPerRadian, fViewMatrix, fColor);
this->prePreparePrograms(context->priv().recordTimeAllocator(), strokeTessellateShader,
writeView, std::move(*clip), dstProxyView, renderPassXferBarriers,
colorLoadOp, *context->priv().caps());
@@ -472,6 +472,7 @@
}
[[fallthrough]];
case Verb::kQuad:
+ case Verb::kConic:
if (prevPts[1] != prevPts[2]) {
lastControlPoint = prevPts[1];
break;
@@ -498,11 +499,15 @@
}
++fTotalInstanceCount;
break;
+ case Verb::kConic:
+ // We use the same quadratic formula for conics, ignoring w. This is pretty
+ // close to what the actual number of subdivisions would have been.
+ [[fallthrough]];
case Verb::kQuad: {
- // Check for a cusp. A quadratic can only have a cusp if it is a degenerate flat
- // line with a 180 degree turnarund. To detect this, the beginning and ending
- // tangents must be parallel (a.cross(b) == 0) and pointing in opposite
- // directions (a.dot(b) < 0).
+ // Check for a cusp. A conic of any class can only have a cusp if it is a
+ // degenerate flat line with a 180 degree turnarund. To detect this, the
+ // beginning and ending tangents must be parallel (a.cross(b) == 0) and pointing
+ // in opposite directions (a.dot(b) < 0).
SkVector a = pts[1] - pts[0];
SkVector b = pts[2] - pts[1];
if (a.cross(b) == 0 && a.dot(b) < 0) {
@@ -583,8 +588,8 @@
return;
}
auto* strokeTessellateShader = arena->make<GrStrokeTessellateShader>(
- GrStrokeTessellateShader::Mode::kIndirect, fStroke, fParametricIntolerance,
- fNumRadialSegmentsPerRadian, fViewMatrix, fColor);
+ GrStrokeTessellateShader::Mode::kIndirect, fTotalConicWeightCnt, fStroke,
+ fParametricIntolerance, fNumRadialSegmentsPerRadian, fViewMatrix, fColor);
this->prePreparePrograms(arena, strokeTessellateShader, flushState->writeView(),
flushState->detachAppliedClip(), flushState->dstProxyView(),
flushState->renderPassBarriers(), flushState->colorLoadOp(),
@@ -710,10 +715,10 @@
scratch = scratchBuffer;
continue;
case Verb::kLine:
+ resolveLevel = (isRoundJoin) ? *nextResolveLevel++ : 0;
scratch[0] = scratch[1] = pts[0];
scratch[2] = scratch[3] = pts[1];
pts_ = scratch;
- resolveLevel = (isRoundJoin) ? *nextResolveLevel++ : 0;
break;
case Verb::kQuad:
resolveLevel = *nextResolveLevel++;
@@ -734,6 +739,27 @@
}
pts_ = scratch;
break;
+ case Verb::kConic:
+ resolveLevel = *nextResolveLevel++;
+ if (resolveLevel < 0) {
+ // The curve has a cusp. Draw two lines and a cusp instead of a conic.
+ SkASSERT(resolveLevel == -1);
+ SkPoint cusp;
+ SkConic conic(pts, iter.w());
+ float cuspT = conic.findMidTangent();
+ conic.evalAt(cuspT, &cusp);
+ resolveLevel = (isRoundJoin) ? *nextResolveLevel++ : 0;
+ numChops = 1;
+ scratch[0] = scratch[1] = pts[0];
+ scratch[2] = scratch[3] = scratch[4] = cusp;
+ scratch[5] = scratch[6] = pts[2];
+ nextInstanceLocations[fResolveLevelForCircles]++->setCircle(
+ cusp, numEdgesPerResolveLevel[fResolveLevelForCircles]);
+ } else {
+ GrPathShader::WriteConicPatch(pts, iter.w(), scratch);
+ }
+ pts_ = scratch;
+ break;
case Verb::kCubic:
resolveLevel = *nextResolveLevel++;
if (resolveLevel < 0) {
@@ -769,11 +795,11 @@
(i == 0) ? numEdges : -numEdges);
}
// Determine the last control point.
- if (pts_[2] != pts_[3]) {
+ if (pts_[2] != pts_[3] && verb != Verb::kConic) { // Conics use pts_[3] for w.
lastControlPoint = pts_[2];
- } else if (pts_[1] != pts_[3]) {
+ } else if (pts_[1] != pts_[2]) {
lastControlPoint = pts_[1];
- } else if (pts_[0] != pts_[3]) {
+ } else if (pts_[0] != pts_[1]) {
lastControlPoint = pts_[0];
} else {
// This is very unusual, but all chops became degenerate. Don't update the
@@ -786,7 +812,7 @@
pts_ += 3;
// If a non-cubic got chopped, it means it was chopped into lines and a circle.
resolveLevel = (verb == Verb::kCubic) ? *nextResolveLevel++ : 0;
- SkASSERT(verb == Verb::kQuad || verb == Verb::kCubic);
+ SkASSERT(verb == Verb::kQuad || verb == Verb::kConic || verb == Verb::kCubic);
}
}
}
diff --git a/src/gpu/tessellate/GrStrokeIterator.h b/src/gpu/tessellate/GrStrokeIterator.h
index adf647f..ef75faa 100644
--- a/src/gpu/tessellate/GrStrokeIterator.h
+++ b/src/gpu/tessellate/GrStrokeIterator.h
@@ -22,6 +22,7 @@
// while (iter.next()) { // Call next() first.
// iter.verb();
// iter.pts();
+// iter.w();
// iter.prevVerb();
// iter.prevPts();
// }
@@ -39,6 +40,7 @@
// Verbs that describe stroke geometry.
kLine = (int)SkPathVerb::kLine,
kQuad = (int)SkPathVerb::kQuad,
+ kConic = (int)SkPathVerb::kConic,
kCubic = (int)SkPathVerb::kCubic,
kCircle, // A stroke-width circle drawn as a 180-degree point stroke.
@@ -75,6 +77,7 @@
case SkPathVerb::kCubic:
if (pts[3] == pts[2]) {
[[fallthrough]]; // i.e., "if (p3 == p2 && p2 == p1 && p1 == p0)"
+ case SkPathVerb::kConic:
case SkPathVerb::kQuad:
if (pts[2] == pts[1]) {
[[fallthrough]]; // i.e., "if (p2 == p1 && p1 == p0)"
@@ -83,11 +86,12 @@
fLastDegenerateStrokePt = pts;
continue;
}}}
- this->enqueue((Verb)verb, pts);
+ this->enqueue((Verb)verb, pts, w);
if (fQueueCount == 1) {
// Defer the first verb until the end when we know what it's joined to.
fFirstVerbInContour = (Verb)verb;
fFirstPtsInContour = pts;
+ fFirstWInContour = w;
continue;
}
break;
@@ -99,15 +103,13 @@
if (pts[0] != fFirstPtsInContour[0]) {
// Draw a line back to the contour's starting point.
fClosePts = {pts[0], fFirstPtsInContour[0]};
- this->enqueue(Verb::kLine, fClosePts.data());
+ this->enqueue(Verb::kLine, fClosePts.data(), nullptr);
}
// Repeat the first verb, this time as the "current" stroke instead of the prev.
- this->enqueue(fFirstVerbInContour, fFirstPtsInContour);
- this->enqueue(Verb::kContourFinished, nullptr);
+ this->enqueue(fFirstVerbInContour, fFirstPtsInContour, fFirstWInContour);
+ this->enqueue(Verb::kContourFinished, nullptr, nullptr);
fLastDegenerateStrokePt = nullptr;
break;
- case SkPathVerb::kConic:
- SkUNREACHABLE;
}
SkASSERT(fQueueCount >= 2);
++fIter;
@@ -121,6 +123,7 @@
Verb verb() const { return this->atVerb(1); }
const SkPoint* pts() const { return this->atPts(1); }
+ float w() const { return this->atW(1); }
Verb firstVerbInContour() const { SkASSERT(fQueueCount > 0); return fFirstVerbInContour; }
const SkPoint* firstPtsInContour() const {
@@ -144,11 +147,18 @@
const SkPoint* backPts() const {
return this->atPts(fQueueCount - 1);
}
- void enqueue(Verb verb, const SkPoint* pts) {
+ float atW(int i) const {
+ SkASSERT(0 <= i && i < fQueueCount);
+ const float* w = fW[(fQueueFrontIdx + i) & (kQueueBufferCount - 1)];
+ SkASSERT(w);
+ return *w;
+ }
+ void enqueue(Verb verb, const SkPoint* pts, const float* w) {
SkASSERT(fQueueCount < kQueueBufferCount);
int i = (fQueueFrontIdx + fQueueCount) & (kQueueBufferCount - 1);
fVerbs[i] = verb;
fPts[i] = pts;
+ fW[i] = w;
++fQueueCount;
}
void popFront() {
@@ -163,30 +173,30 @@
bool finishOpenContour() {
if (fQueueCount) {
SkASSERT(this->backVerb() == Verb::kLine || this->backVerb() == Verb::kQuad ||
- this->backVerb() == Verb::kCubic);
+ this->backVerb() == Verb::kConic || this->backVerb() == Verb::kCubic);
switch (fCapType) {
case SkPaint::kButt_Cap:
// There are no caps, but inject a "move" so the first stroke doesn't get joined
// with the end of the contour when it's processed.
- this->enqueue(Verb::kMoveWithinContour, fFirstPtsInContour);
+ this->enqueue(Verb::kMoveWithinContour, fFirstPtsInContour, fFirstWInContour);
break;
case SkPaint::kRound_Cap: {
// The "kCircle" verb serves as our barrier to prevent the first stroke from
// getting joined with the end of the contour. We just need to make sure that
// the first point of the contour goes last.
int backIdx = SkPathPriv::PtsInIter((unsigned)this->backVerb()) - 1;
- this->enqueue(Verb::kCircle, this->backPts() + backIdx);
- this->enqueue(Verb::kCircle, fFirstPtsInContour);
+ this->enqueue(Verb::kCircle, this->backPts() + backIdx, nullptr);
+ this->enqueue(Verb::kCircle, fFirstPtsInContour, fFirstWInContour);
break;
}
case SkPaint::kSquare_Cap:
this->fillSquareCapPoints(); // Fills in fEndingCapPts and fBeginningCapPts.
// Append the ending cap onto the current contour.
- this->enqueue(Verb::kLine, fEndingCapPts.data());
+ this->enqueue(Verb::kLine, fEndingCapPts.data(), nullptr);
// Move to the beginning cap and append it right before (and joined to) the
// first stroke (that we will add below).
- this->enqueue(Verb::kMoveWithinContour, fBeginningCapPts.data());
- this->enqueue(Verb::kLine, fBeginningCapPts.data());
+ this->enqueue(Verb::kMoveWithinContour, fBeginningCapPts.data(), nullptr);
+ this->enqueue(Verb::kLine, fBeginningCapPts.data(), nullptr);
break;
}
} else if (fLastDegenerateStrokePt) {
@@ -203,22 +213,24 @@
// generate.
return false;
case SkPaint::kRound_Cap:
- this->enqueue(Verb::kCircle, fLastDegenerateStrokePt);
+ this->enqueue(Verb::kCircle, fLastDegenerateStrokePt, nullptr);
// Setting the "first" stroke as the circle causes it to be added again below,
// this time as the "current" stroke.
fFirstVerbInContour = Verb::kCircle;
fFirstPtsInContour = fLastDegenerateStrokePt;
+ fFirstWInContour = nullptr;
break;
case SkPaint::kSquare_Cap:
fEndingCapPts = {*fLastDegenerateStrokePt - SkPoint{fStrokeRadius, 0},
*fLastDegenerateStrokePt + SkPoint{fStrokeRadius, 0}};
// Add the square first as the "prev" join.
- this->enqueue(Verb::kLine, fEndingCapPts.data());
- this->enqueue(Verb::kMoveWithinContour, fEndingCapPts.data());
+ this->enqueue(Verb::kLine, fEndingCapPts.data(), nullptr);
+ this->enqueue(Verb::kMoveWithinContour, fEndingCapPts.data(), nullptr);
// Setting the "first" stroke as the square causes it to be added again below,
// this time as the "current" stroke.
fFirstVerbInContour = Verb::kLine;
fFirstPtsInContour = fEndingCapPts.data();
+ fFirstWInContour = nullptr;
break;
}
} else {
@@ -228,8 +240,8 @@
}
// Repeat the first verb, this time as the "current" stroke instead of the prev.
- this->enqueue(fFirstVerbInContour, fFirstPtsInContour);
- this->enqueue(Verb::kContourFinished, nullptr);
+ this->enqueue(fFirstVerbInContour, fFirstPtsInContour, fFirstWInContour);
+ this->enqueue(Verb::kContourFinished, nullptr, nullptr);
fLastDegenerateStrokePt = nullptr;
return true;
}
@@ -248,6 +260,7 @@
break;
}
[[fallthrough]];
+ case Verb::kConic:
case Verb::kQuad:
lastTangent = lastPts[2] - lastPts[1];
if (!lastTangent.isZero()) {
@@ -268,7 +281,8 @@
// Find the endpoints of the cap at the beginning of the contour.
SkVector firstTangent = fFirstPtsInContour[1] - fFirstPtsInContour[0];
if (firstTangent.isZero()) {
- SkASSERT(fFirstVerbInContour == Verb::kQuad || fFirstVerbInContour == Verb::kCubic);
+ SkASSERT(fFirstVerbInContour == Verb::kQuad || fFirstVerbInContour == Verb::kConic ||
+ fFirstVerbInContour == Verb::kCubic);
firstTangent = fFirstPtsInContour[2] - fFirstPtsInContour[0];
if (firstTangent.isZero()) {
SkASSERT(fFirstVerbInContour == Verb::kCubic);
@@ -290,11 +304,13 @@
// Info for the current contour we are iterating.
Verb fFirstVerbInContour;
const SkPoint* fFirstPtsInContour;
+ const float* fFirstWInContour;
const SkPoint* fLastDegenerateStrokePt = nullptr;
// The queue is implemented as a roll-over array with a floating front index.
Verb fVerbs[kQueueBufferCount];
const SkPoint* fPts[kQueueBufferCount];
+ const float* fW[kQueueBufferCount];
int fQueueFrontIdx = 0;
int fQueueCount = 0;
diff --git a/src/gpu/tessellate/GrStrokeOp.cpp b/src/gpu/tessellate/GrStrokeOp.cpp
index 4daf21a..f5924e3 100644
--- a/src/gpu/tessellate/GrStrokeOp.cpp
+++ b/src/gpu/tessellate/GrStrokeOp.cpp
@@ -25,7 +25,8 @@
, fColor(paint.getColor4f())
, fProcessors(std::move(paint))
, fPathList(path)
- , fTotalCombinedVerbCnt(path.countVerbs()) {
+ , fTotalCombinedVerbCnt(path.countVerbs())
+ , fTotalConicWeightCnt(SkPathPriv::ConicWeightCnt(path)) {
// We don't support hairline strokes. For now, the client can transform the path into device
// space and then use a stroke width of 1.
SkASSERT(fStroke.getWidth() > 0);
@@ -74,6 +75,7 @@
fPathList.concat(std::move(op->fPathList), alloc);
fTotalCombinedVerbCnt += op->fTotalCombinedVerbCnt;
+ fTotalConicWeightCnt += op->fTotalConicWeightCnt;
return CombineResult::kMerged;
}
diff --git a/src/gpu/tessellate/GrStrokeOp.h b/src/gpu/tessellate/GrStrokeOp.h
index 7cd4c82..f9e6762 100644
--- a/src/gpu/tessellate/GrStrokeOp.h
+++ b/src/gpu/tessellate/GrStrokeOp.h
@@ -80,7 +80,8 @@
GrProcessorSet fProcessors;
GrSTArenaList<SkPath> fPathList;
- int fTotalCombinedVerbCnt;
+ int fTotalCombinedVerbCnt = 0;
+ int fTotalConicWeightCnt = 0;
const GrProgramInfo* fStencilProgram = nullptr;
const GrProgramInfo* fFillProgram = nullptr;
diff --git a/src/gpu/tessellate/GrStrokeTessellateOp.cpp b/src/gpu/tessellate/GrStrokeTessellateOp.cpp
index b1cd656..3dcb127 100644
--- a/src/gpu/tessellate/GrStrokeTessellateOp.cpp
+++ b/src/gpu/tessellate/GrStrokeTessellateOp.cpp
@@ -21,8 +21,8 @@
GrLoadOp colorLoadOp) {
SkArenaAlloc* arena = context->priv().recordTimeAllocator();
auto* strokeTessellateShader = arena->make<GrStrokeTessellateShader>(
- GrStrokeTessellateShader::Mode::kTessellation, fStroke, fParametricIntolerance,
- fNumRadialSegmentsPerRadian, fViewMatrix, fColor);
+ GrStrokeTessellateShader::Mode::kTessellation, false/*hasConics*/, fStroke,
+ fParametricIntolerance, fNumRadialSegmentsPerRadian, fViewMatrix, fColor);
this->prePreparePrograms(arena, strokeTessellateShader, writeView, std::move(*clip),
dstProxyView, renderPassXferBarriers, colorLoadOp,
*context->priv().caps());
@@ -38,8 +38,8 @@
if (!fFillProgram && !fStencilProgram) {
SkArenaAlloc* arena = flushState->allocator();
auto* strokeTessellateShader = arena->make<GrStrokeTessellateShader>(
- GrStrokeTessellateShader::Mode::kTessellation, fStroke, fParametricIntolerance,
- fNumRadialSegmentsPerRadian, fViewMatrix, fColor);
+ GrStrokeTessellateShader::Mode::kTessellation, false/*hasConics*/, fStroke,
+ fParametricIntolerance, fNumRadialSegmentsPerRadian, fViewMatrix, fColor);
this->prePreparePrograms(flushState->allocator(), strokeTessellateShader,
flushState->writeView(), flushState->detachAppliedClip(),
flushState->dstProxyView(), flushState->renderPassBarriers(),
diff --git a/src/gpu/tessellate/GrStrokeTessellateShader.cpp b/src/gpu/tessellate/GrStrokeTessellateShader.cpp
index cfab725..5c671cb 100644
--- a/src/gpu/tessellate/GrStrokeTessellateShader.cpp
+++ b/src/gpu/tessellate/GrStrokeTessellateShader.cpp
@@ -33,6 +33,8 @@
const auto& shader = args.fGP.cast<GrStrokeTessellateShader>();
auto* uniHandler = args.fUniformHandler;
+ SkASSERT(!shader.fHasConics);
+
args.fVaryingHandler->emitAttributes(shader);
// uNumSegmentsInJoin, uWangsTermPow2, uNumRadialSegmentsPerRadian, uMiterLimitInvPow2.
@@ -298,13 +300,15 @@
return atan(v.y, v.x) + bias;
})";
+static const char* kLengthPow2Fn = R"(
+float length_pow2(float2 v) {
+ return dot(v, 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 length_pow2(float2 v) {
- return dot(v, v);
-}
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;
@@ -372,6 +376,7 @@
})");
code.append(kAtan2Fn);
+ code.append(kLengthPow2Fn);
code.append(kWangsFormulaCubicFn);
code.append(kMiterExtentFn);
@@ -518,127 +523,172 @@
// precision than "a*(1 - t) + b*t" for things like chopping cubics on exact cusp points.
// We override this result anyway when t==1 so it shouldn't be a problem.
static const char* kUncheckedMixFn = R"(
-float2 unchecked_mix(float2 a, float2 b, float t) {
- return fma(b - a, float2(t), a);
+float unchecked_mix(float a, float b, float T) {
+ return fma(b - a, T, a);
+}
+float2 unchecked_mix(float2 a, float2 b, float T) {
+ return fma(b - a, float2(T), a);
})";
// Computes the location and tangent direction of the stroke edge with the integral id
// "combinedEdgeID", where combinedEdgeID is the sorted-order index of parametric and radial edges.
-static const char* kEvalStrokeEdgeFn = R"(
-void eval_stroke_edge(in float4x2 P, 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 cubic's power basis coefficients. These define a tangent direction
- // (scaled by uniform 1/3) as:
- // |T^2|
- // Tangent_Direction(T) = dx,dy = |A 2B C| * |T |
- // |. . .| |1 |
- float2 C = P[1] - P[0];
- float2 D = P[2] - P[1];
- float2 E = P[3] - P[0];
- float2 B = D - C;
- float2 A = fma(float2(-3), D, E);
+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) {
+ // Start by finding the tangent function's power basis coefficients. These define a tangent
+ // direction (scaled by some uniform value) as:
+ // |T^2|
+ // Tangent_Direction(T) = dx,dy = |A 2B C| * |T |
+ // |. . .| |1 |
+ float2 A, B, C = P[1] - P[0];
+ float2 D = P[3] - P[0];)");
+ if (hasConics) {
+ code->append(R"(
+ if (w >= 0) {
+ // P0..P2 represent a conic and P3==P2. The derivative of a conic has a cumbersome
+ // order-4 denominator. However, this isn't necessary if we are only interested in a
+ // vector in the same *direction* as a given tangent line. Since the denominator scales
+ // dx and dy uniformly, we can throw it out completely after evaluating the derivative
+ // with the standard quotient rule. This leaves us with a simpler quadratic function
+ // that we use to find a tangent.
+ C *= w;
+ B = .5*D - C;
+ A = (w - 1) * D;
+ P[1] *= w;
+ } else {)");
+ } else {
+ code->append(R"(
+ {)");
+ }
+ code->append(R"(
+ float2 E = P[2] - P[1];
+ B = E - C;
+ A = fma(float2(-3), E, D);
+ }
- // Now find the coefficients that give a tangent direction from a parametric edge ID:
- //
- // |parametricEdgeID^2|
- // Tangent_Direction(parametricEdgeID) = dx,dy = |A B_ C_| * |parametricEdgeID |
- // |. . .| |1 |
- //
- float2 B_ = B * (numParametricSegments * 2);
- float2 C_ = C * (numParametricSegments * numParametricSegments);
+ // Now find the coefficients that give a tangent direction from a parametric edge ID:
+ //
+ // |parametricEdgeID^2|
+ // Tangent_Direction(parametricEdgeID) = dx,dy = |A B_ C_| * |parametricEdgeID |
+ // |. . .| |1 |
+ //
+ float2 B_ = B * (numParametricSegments * 2);
+ float2 C_ = C * (numParametricSegments * numParametricSegments);
- // Run a binary search to determine the highest parametric edge that is located on or before the
- // combinedEdgeID. A combined ID is determined by the sum of complete parametric and radial
- // segments behind it. i.e., find the highest parametric edge where:
- //
- // parametricEdgeID + floor(numRadialSegmentsAtParametricT) <= combinedEdgeID
- //
- float lastParametricEdgeID = 0;
- float maxParametricEdgeID = min(numParametricSegments - 1, combinedEdgeID);
- float2 tan0norm = normalize(tan0);
- float negAbsRadsPerSegment = -abs(radsPerSegment);
- float maxRotation0 = (1 + combinedEdgeID) * abs(radsPerSegment);
- for (int exp = MAX_PARAMETRIC_SEGMENTS_LOG2 - 1; exp >= 0; --exp) {
- // Test the parametric edge at lastParametricEdgeID + 2^exp.
- float testParametricID = lastParametricEdgeID + (1 << exp);
- if (testParametricID <= maxParametricEdgeID) {
- float2 testTan = fma(float2(testParametricID), A, B_);
- testTan = fma(float2(testParametricID), testTan, C_);
- float cosRotation = dot(normalize(testTan), tan0norm);
- float maxRotation = fma(testParametricID, negAbsRadsPerSegment, maxRotation0);
- maxRotation = min(maxRotation, PI);
- // Is rotation <= maxRotation? (i.e., is the number of complete radial segments
- // behind testT, + testParametricID <= combinedEdgeID?)
- if (cosRotation >= cos(maxRotation)) {
- // testParametricID is on or before the combinedEdgeID. Keep it!
- lastParametricEdgeID = testParametricID;
+ // Run a binary search to determine the highest parametric edge that is located on or before
+ // the combinedEdgeID. A combined ID is determined by the sum of complete parametric and
+ // radial segments behind it. i.e., find the highest parametric edge where:
+ //
+ // parametricEdgeID + floor(numRadialSegmentsAtParametricT) <= combinedEdgeID
+ //
+ float lastParametricEdgeID = 0;
+ float maxParametricEdgeID = min(numParametricSegments - 1, combinedEdgeID);
+ float2 tan0norm = normalize(tan0);
+ float negAbsRadsPerSegment = -abs(radsPerSegment);
+ float maxRotation0 = (1 + combinedEdgeID) * abs(radsPerSegment);
+ for (int exp = MAX_PARAMETRIC_SEGMENTS_LOG2 - 1; exp >= 0; --exp) {
+ // Test the parametric edge at lastParametricEdgeID + 2^exp.
+ float testParametricID = lastParametricEdgeID + (1 << exp);
+ if (testParametricID <= maxParametricEdgeID) {
+ float2 testTan = fma(float2(testParametricID), A, B_);
+ testTan = fma(float2(testParametricID), testTan, C_);
+ float cosRotation = dot(normalize(testTan), tan0norm);
+ float maxRotation = fma(testParametricID, negAbsRadsPerSegment, maxRotation0);
+ maxRotation = min(maxRotation, PI);
+ // Is rotation <= maxRotation? (i.e., is the number of complete radial segments
+ // behind testT, + testParametricID <= combinedEdgeID?)
+ if (cosRotation >= cos(maxRotation)) {
+ // testParametricID is on or before the combinedEdgeID. Keep it!
+ lastParametricEdgeID = testParametricID;
+ }
}
}
+
+ // Find the T value of the parametric edge at lastParametricEdgeID.
+ float parametricT = lastParametricEdgeID / numParametricSegments;
+
+ // Now that we've identified the highest parametric edge on or before the combinedEdgeID,
+ // the highest radial edge is easy:
+ float lastRadialEdgeID = combinedEdgeID - lastParametricEdgeID;
+
+ // Find the tangent vector on the edge at lastRadialEdgeID.
+ float radialAngle = fma(lastRadialEdgeID, radsPerSegment, angle0);
+ tangent = float2(cos(radialAngle), sin(radialAngle));
+ float2 norm = float2(-tangent.y, tangent.x);
+
+ // Find the T value where the cubic's tangent is orthogonal to norm. This is a quadratic:
+ //
+ // dot(norm, Tangent_Direction(T)) == 0
+ //
+ // |T^2|
+ // norm * |A 2B C| * |T | == 0
+ // |. . .| |1 |
+ //
+ float3 coeffs = norm * float3x2(A,B,C);
+ float a=coeffs.x, b_over_2=coeffs.y, c=coeffs.z;
+ float discr_over_4 = max(b_over_2*b_over_2 - a*c, 0);
+ float q = sqrt(discr_over_4);
+ if (b_over_2 > 0) {
+ q = -q;
+ }
+ q -= b_over_2;
+
+ // Roots are q/a and c/q. Since each curve section does not inflect or rotate more than 180
+ // degrees, there can only be one tangent orthogonal to "norm" inside 0..1. Pick the root
+ // nearest .5.
+ float _5qa = -.5*q*a;
+ float2 root = (abs(fma(q,q,_5qa)) < abs(fma(a,c,_5qa))) ? float2(q,a) : float2(c,q);
+ float radialT = (root.t != 0) ? root.s / root.t : 0;
+ radialT = clamp(radialT, 0, 1);
+
+ if (lastRadialEdgeID == 0) {
+ // The root finder above can become unstable when lastRadialEdgeID == 0 (e.g., if there
+ // are roots at exatly 0 and 1 both). radialT should always == 0 in this case.
+ radialT = 0;
+ }
+
+ // Now that we've identified the T values of the last parametric and radial edges, our final
+ // T value for combinedEdgeID is whichever is larger.
+ float T = max(parametricT, radialT);
+
+ // Evaluate the cubic at T. Use De Casteljau's for its accuracy and stability.
+ float2 ab = unchecked_mix(P[0], P[1], T);
+ float2 bc = unchecked_mix(P[1], P[2], T);
+ float2 cd = unchecked_mix(P[2], P[3], T);
+ float2 abc = unchecked_mix(ab, bc, T);
+ float2 bcd = unchecked_mix(bc, cd, T);
+ float2 abcd = unchecked_mix(abc, bcd, T);)");
+
+ if (hasConics) {
+ code->append(R"(
+ // Evaluate the conic weights at T.
+ float u = unchecked_mix(1, w, T);
+ float v = unchecked_mix(w, 1, T);
+ float uv = unchecked_mix(u, v, T);)");
}
- // Find the T value of the parametric edge at lastParametricEdgeID.
- float parametricT = lastParametricEdgeID / numParametricSegments;
+ code->appendf(R"(
+ position =%s abcd;)", (hasConics) ? " (w >= 0) ? abc/uv :" : "");
- // Now that we've identified the highest parametric edge on or before the combinedEdgeID, the
- // highest radial edge is easy:
- float lastRadialEdgeID = combinedEdgeID - lastParametricEdgeID;
-
- // Find the tangent vector on the edge at lastRadialEdgeID.
- float radialAngle = fma(lastRadialEdgeID, radsPerSegment, angle0);
- tangent = float2(cos(radialAngle), sin(radialAngle));
- float2 norm = float2(-tangent.y, tangent.x);
-
- // Find the T value where the cubic's tangent is orthogonal to norm. This is a quadratic:
- //
- // dot(norm, Tangent_Direction(T)) == 0
- //
- // |T^2|
- // norm * |A 2B C| * |T | == 0
- // |. . .| |1 |
- //
- float3 coeffs = norm * float3x2(A,B,C);
- float a=coeffs.x, b_over_2=coeffs.y, c=coeffs.z;
- float discr_over_4 = max(b_over_2*b_over_2 - a*c, 0);
- float q = sqrt(discr_over_4);
- if (b_over_2 > 0) {
- q = -q;
- }
- q -= b_over_2;
-
- // Roots are q/a and c/q. Since each curve section does not inflect or rotate more than 180
- // degrees, there can only be one tangent orthogonal to "norm" inside 0..1. Pick the root
- // nearest .5.
- float _5qa = -.5*q*a;
- float2 root = (abs(fma(q,q,_5qa)) < abs(fma(a,c,_5qa))) ? float2(q,a) : float2(c,q);
- float radialT = (root.t != 0) ? root.s / root.t : 0;
- radialT = clamp(radialT, 0, 1);
-
- if (lastRadialEdgeID == 0) {
- // The root finder above can become unstable when lastRadialEdgeID == 0 (e.g., if there
- // are roots at exatly 0 and 1 both). radialT should always == 0 in this case.
- radialT = 0;
- }
-
- // Now that we've identified the T values of the last parametric and radial edges, our final
- // T value for combinedEdgeID is whichever is larger.
- float T = max(parametricT, radialT);
-
- // Evaluate the cubic at T. Use De Casteljau's for its accuracy and stability.
- float2 ab = unchecked_mix(P[0], P[1], T);
- float2 bc = unchecked_mix(P[1], P[2], T);
- float2 cd = unchecked_mix(P[2], P[3], T);
- float2 abc = unchecked_mix(ab, bc, T);
- float2 bcd = unchecked_mix(bc, cd, T);
- position = unchecked_mix(abc, bcd, T);
-
- // If we went with T=parametricT, then update the tangent. Otherwise leave it at the radial
- // tangent found previously. (In the event that parametricT == radialT, we keep the radial
- // tangent.)
- if (T != radialT) {
- tangent = bcd - abc;
- }
-})";
+ code->appendf(R"(
+ // If we went with T=parametricT, then update the tangent. Otherwise leave it at the radial
+ // tangent found previously. (In the event that parametricT == radialT, we keep the radial
+ // tangent.)
+ if (T != radialT) {)");
+ code->appendf(R"(
+ tangent =%s bcd - abc;)", (hasConics) ? " (w >= 0) ? bc*u - ab*v :" : "");
+ code->appendf(R"(
+ }
+ })");
+}
SkString GrStrokeTessellateShader::getTessEvaluationShaderGLSL(
const GrGLSLPrimitiveProcessor* glslPrimProc, const char* versionAndExtensionDecls,
@@ -685,7 +735,7 @@
uniform vec4 sk_RTAdjust;)");
code.append(kUncheckedMixFn);
- code.append(kEvalStrokeEdgeFn);
+ append_eval_stroke_edge_fn(&code, false/*hasConics*/);
code.append(R"(
void main() {
@@ -783,13 +833,17 @@
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(kWangsFormulaCubicFn);
+ args.fVertBuilder->insertFunction(kLengthPow2Fn);
args.fVertBuilder->insertFunction(kMiterExtentFn);
args.fVertBuilder->insertFunction(kUncheckedMixFn);
- args.fVertBuilder->insertFunction(kEvalStrokeEdgeFn);
+ append_eval_stroke_edge_fn(&args.fVertBuilder->functions(), shader.fHasConics);
args.fVertBuilder->insertFunction(R"(
float cosine_between_vectors(float2 a, float2 b) {
float ab_cosTheta = dot(a,b);
@@ -801,20 +855,37 @@
const char* tessArgsName;
fTessControlArgsUniform = args.fUniformHandler->addUniform(
nullptr, kVertex_GrShaderFlag, kFloat4_GrSLType, "tessControlArgs", &tessArgsName);
- args.fVertBuilder->codeAppendf("float uWangsTermPow2 = %s.x;\n", tessArgsName);
+ args.fVertBuilder->codeAppendf("float uParametricIntolerance = %s.x;\n", tessArgsName);
args.fVertBuilder->codeAppendf("float uNumRadialSegmentsPerRadian = %s.y;\n", tessArgsName);
args.fVertBuilder->codeAppendf("float uMiterLimitInvPow2 = %s.z;\n", tessArgsName);
args.fVertBuilder->codeAppendf("float uStrokeRadius = %s.w;\n", tessArgsName);
// Tessellation code.
args.fVertBuilder->codeAppend(R"(
- float4x2 P = float4x2(pts01, pts23);
+ float4x2 P = float4x2(pts01, pts23);)");
+ 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.
+ })");
+ }
+ args.fVertBuilder->codeAppend(R"(
float2 lastControlPoint = args.xy;
float numTotalEdges = abs(args.z);
- // Find how many parametric segments this stroke requires.
- float numParametricSegments = min(wangs_formula_cubic(P, uWangsTermPow2),
- 1 << MAX_PARAMETRIC_SEGMENTS_LOG2);
+ // 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, 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;
@@ -929,11 +1000,18 @@
})");
}
- args.fVertBuilder->codeAppend(R"(
+ args.fVertBuilder->codeAppendf(R"(
float2 tangent, localCoord;
- eval_stroke_edge(P, numParametricSegments, combinedEdgeID, tan0, radsPerSegment, angle0,
- tangent, localCoord);
+ eval_stroke_edge(P,)");
+ if (shader.fHasConics) {
+ args.fVertBuilder->codeAppend(R"(
+ w,)");
+ }
+ args.fVertBuilder->codeAppend(R"(
+ numParametricSegments, combinedEdgeID, tan0, radsPerSegment, angle0,
+ tangent, localCoord);)");
+ args.fVertBuilder->codeAppend(R"(
if (combinedEdgeID == 0) {
// Edges at the beginning of their section use P[0] and tan0. This ensures crack-free
// seaming between instances.
@@ -983,7 +1061,7 @@
// Set up the tessellation control uniforms.
float miterLimit = shader.fStroke.getMiter();
pdman.set4f(fTessControlArgsUniform,
- GrWangsFormula::length_term_pow2<3>(shader.fParametricIntolerance), // uWangsTermPow2
+ shader.fParametricIntolerance, // uParametricIntolerance
shader.fNumRadialSegmentsPerRadian, // uNumRadialSegmentsPerRadian
1 / (miterLimit * miterLimit), // uMiterLimitInvPow2.
shader.fStroke.getWidth() * .5); // uStrokeRadius.
@@ -1012,6 +1090,7 @@
SkASSERT(fStroke.getJoin() >> 2 == 0);
key = (key << 2) | fStroke.getJoin();
}
+ key = (key << 1) | (uint32_t)fHasConics;
key = (key << 1) | (uint32_t)fMode; // Must be last.
b->add32(key);
}
diff --git a/src/gpu/tessellate/GrStrokeTessellateShader.h b/src/gpu/tessellate/GrStrokeTessellateShader.h
index fca1ee8..de84d23 100644
--- a/src/gpu/tessellate/GrStrokeTessellateShader.h
+++ b/src/gpu/tessellate/GrStrokeTessellateShader.h
@@ -107,14 +107,15 @@
// smoothness.
//
// 'viewMatrix' is applied to the geometry post tessellation. It cannot have perspective.
- GrStrokeTessellateShader(Mode mode, const SkStrokeRec& stroke, float parametricIntolerance,
- float numRadialSegmentsPerRadian, const SkMatrix& viewMatrix,
- SkPMColor4f color)
+ GrStrokeTessellateShader(Mode mode, bool hasConics, const SkStrokeRec& stroke,
+ float parametricIntolerance, float numRadialSegmentsPerRadian,
+ const SkMatrix& viewMatrix, SkPMColor4f color)
: GrPathShader(kTessellate_GrStrokeTessellateShader_ClassID, viewMatrix,
(mode == Mode::kTessellation) ?
GrPrimitiveType::kPatches : GrPrimitiveType::kTriangleStrip,
(mode == Mode::kTessellation) ? 1 : 0)
, fMode(mode)
+ , fHasConics(hasConics)
, fStroke(stroke)
, fParametricIntolerance(parametricIntolerance)
, fNumRadialSegmentsPerRadian(numRadialSegmentsPerRadian)
@@ -154,6 +155,7 @@
const GrShaderCaps&) const override;
const Mode fMode;
+ const bool fHasConics;
const SkStrokeRec fStroke;
const float fParametricIntolerance;
const float fNumRadialSegmentsPerRadian;
diff --git a/src/gpu/tessellate/GrTessellationPathRenderer.cpp b/src/gpu/tessellate/GrTessellationPathRenderer.cpp
index d1f9dc7..4ee7e93 100644
--- a/src/gpu/tessellate/GrTessellationPathRenderer.cpp
+++ b/src/gpu/tessellate/GrTessellationPathRenderer.cpp
@@ -143,8 +143,7 @@
if (!shape.style().isSimpleFill()) {
// These are only temporary restrictions while we bootstrap tessellated stroking. Every one
// of them will eventually go away.
- if (shape.style().strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style ||
- SkPathPriv::ConicWeightCnt(path)) {
+ if (shape.style().strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style) {
return CanDrawPath::kNo;
}
if (shape.style().isSimpleHairline()) {
@@ -167,7 +166,8 @@
// seem to be better off using indirect draws. Our back door for HW tessellation shaders isn't
// currently capable of passing varyings to the fragment shader either, so if the paint uses
// varyings we need to use indirect draws.
- if (shaderCaps.tessellationSupport() && path.countVerbs() > 50 && !paint.usesVaryingCoords()) {
+ if (shaderCaps.tessellationSupport() && path.countVerbs() > 50 && !paint.usesVaryingCoords() &&
+ !SkPathPriv::ConicWeightCnt(path)) {
return GrOp::Make<GrStrokeTessellateOp>(context, aaType, viewMatrix, stroke, path,
std::move(paint));
} else {