Use VectorXform to determine parametric segments for stroke curves

Change-Id: I088119d01d31d58b154e49479aa071ac68938cb4
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/502059
Reviewed-by: Jim Van Verth <jvanverth@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
This commit is contained in:
Michael Ludwig 2022-02-13 14:21:09 -05:00 committed by SkCQ
parent 1729710b51
commit ee45d92013
7 changed files with 81 additions and 93 deletions

View File

@ -28,28 +28,26 @@ namespace {
class InstanceWriter { class InstanceWriter {
using VectorXform = wangs_formula::VectorXform; using VectorXform = wangs_formula::VectorXform;
public: public:
InstanceWriter(PatchWriter& patchWriter, float matrixMaxScale) InstanceWriter(PatchWriter& patchWriter, const SkMatrix& shaderMatrix)
: fPatchWriter(patchWriter) : fPatchWriter(patchWriter)
, fParametricPrecision(StrokeTolerances::CalcParametricPrecision(matrixMaxScale)) { , fShaderXform(shaderMatrix) {
SkASSERT(fPatchWriter.attribs() & PatchAttribs::kJoinControlPoint); SkASSERT(fPatchWriter.attribs() & PatchAttribs::kJoinControlPoint);
} }
float parametricPrecision() const { return fParametricPrecision; }
SK_ALWAYS_INLINE void lineTo(SkPoint start, SkPoint end) { SK_ALWAYS_INLINE void lineTo(SkPoint start, SkPoint end) {
fPatchWriter.writeLine(start, end); fPatchWriter.writeLine(start, end);
} }
SK_ALWAYS_INLINE void quadraticTo(const SkPoint p[3]) { SK_ALWAYS_INLINE void quadraticTo(const SkPoint p[3]) {
fPatchWriter.writeQuadratic(p, VectorXform(), fParametricPrecision); fPatchWriter.writeQuadratic(p, fShaderXform);
} }
SK_ALWAYS_INLINE void conicTo(const SkPoint p[3], float w) { SK_ALWAYS_INLINE void conicTo(const SkPoint p[3], float w) {
fPatchWriter.writeConic(p, w, VectorXform(), fParametricPrecision); fPatchWriter.writeConic(p, w, fShaderXform);
} }
SK_ALWAYS_INLINE void cubicConvex180To(const SkPoint p[4]) { SK_ALWAYS_INLINE void cubicConvex180To(const SkPoint p[4]) {
fPatchWriter.writeCubic(p, VectorXform(), fParametricPrecision); fPatchWriter.writeCubic(p, fShaderXform);
} }
// Called when we encounter the verb "kMoveWithinContour". Moves invalidate the previous control // Called when we encounter the verb "kMoveWithinContour". Moves invalidate the previous control
@ -70,7 +68,7 @@ public:
private: private:
PatchWriter& fPatchWriter; PatchWriter& fPatchWriter;
const float fParametricPrecision; wangs_formula::VectorXform fShaderXform;
}; };
// Returns the worst-case number of edges we will need in order to draw a join of the given type. // Returns the worst-case number of edges we will need in order to draw a join of the given type.
@ -103,7 +101,8 @@ int StrokeFixedCountTessellator::writePatches(PatchWriter& patchWriter,
int maxEdgesInJoin = 0; int maxEdgesInJoin = 0;
float maxRadialSegmentsPerRadian = 0; float maxRadialSegmentsPerRadian = 0;
InstanceWriter instanceWriter(patchWriter, matrixMinMaxScales[1]); float matrixMaxScale = matrixMinMaxScales[1];
InstanceWriter instanceWriter(patchWriter, shaderMatrix);
if (!(fAttribs & PatchAttribs::kStrokeParams)) { if (!(fAttribs & PatchAttribs::kStrokeParams)) {
// Strokes are static. Calculate tolerances once. // Strokes are static. Calculate tolerances once.
@ -111,14 +110,14 @@ int StrokeFixedCountTessellator::writePatches(PatchWriter& patchWriter,
float localStrokeWidth = StrokeTolerances::GetLocalStrokeWidth(matrixMinMaxScales.data(), float localStrokeWidth = StrokeTolerances::GetLocalStrokeWidth(matrixMinMaxScales.data(),
stroke.getWidth()); stroke.getWidth());
float numRadialSegmentsPerRadian = StrokeTolerances::CalcNumRadialSegmentsPerRadian( float numRadialSegmentsPerRadian = StrokeTolerances::CalcNumRadialSegmentsPerRadian(
instanceWriter.parametricPrecision(), localStrokeWidth); matrixMaxScale, localStrokeWidth);
maxEdgesInJoin = worst_case_edges_in_join(stroke.getJoin(), numRadialSegmentsPerRadian); maxEdgesInJoin = worst_case_edges_in_join(stroke.getJoin(), numRadialSegmentsPerRadian);
maxRadialSegmentsPerRadian = numRadialSegmentsPerRadian; maxRadialSegmentsPerRadian = numRadialSegmentsPerRadian;
} }
// Fast SIMD queue that buffers up values for "numRadialSegmentsPerRadian". Only used when we // Fast SIMD queue that buffers up values for "numRadialSegmentsPerRadian". Only used when we
// have dynamic stroke. // have dynamic stroke.
StrokeToleranceBuffer toleranceBuffer(instanceWriter.parametricPrecision()); StrokeToleranceBuffer toleranceBuffer(matrixMaxScale);
for (PathStrokeList* pathStroke = pathStrokeList; pathStroke; pathStroke = pathStroke->fNext) { for (PathStrokeList* pathStroke = pathStrokeList; pathStroke; pathStroke = pathStroke->fNext) {
const SkStrokeRec& stroke = pathStroke->fStroke; const SkStrokeRec& stroke = pathStroke->fStroke;

View File

@ -69,7 +69,7 @@ public:
// Subtract 2 because the tessellation shader chops every cubic at two locations, and // Subtract 2 because the tessellation shader chops every cubic at two locations, and
// each chop has the potential to introduce an extra segment. // each chop has the potential to introduce an extra segment.
, fMaxTessellationSegments(std::max(maxTessellationSegments - 2, 1)) , fMaxTessellationSegments(std::max(maxTessellationSegments - 2, 1))
, fParametricPrecision(StrokeTolerances::CalcParametricPrecision(matrixMaxScale)) { , fParametricPrecision(matrixMaxScale * kTessellationPrecision) {
SkASSERT(fPatchWriter.attribs() & PatchAttribs::kJoinControlPoint); SkASSERT(fPatchWriter.attribs() & PatchAttribs::kJoinControlPoint);
SkASSERT(!fPatchWriter.hasJoinControlPoint()); SkASSERT(!fPatchWriter.hasJoinControlPoint());
} }
@ -672,7 +672,8 @@ int StrokeHardwareTessellator::writePatches(PatchWriter& patchWriter,
PathStrokeList* pathStrokeList) { PathStrokeList* pathStrokeList) {
using JoinType = HwPatchWriter::JoinType; using JoinType = HwPatchWriter::JoinType;
HwPatchWriter hwPatchWriter(patchWriter, fMaxTessellationSegments, matrixMinMaxScales[1]); float matrixMaxScale = matrixMinMaxScales[1];
HwPatchWriter hwPatchWriter(patchWriter, fMaxTessellationSegments, matrixMaxScale);
if (!(fAttribs & PatchAttribs::kStrokeParams)) { if (!(fAttribs & PatchAttribs::kStrokeParams)) {
// Strokes are static. Calculate tolerances once. // Strokes are static. Calculate tolerances once.
@ -680,13 +681,13 @@ int StrokeHardwareTessellator::writePatches(PatchWriter& patchWriter,
float localStrokeWidth = StrokeTolerances::GetLocalStrokeWidth(matrixMinMaxScales.data(), float localStrokeWidth = StrokeTolerances::GetLocalStrokeWidth(matrixMinMaxScales.data(),
stroke.getWidth()); stroke.getWidth());
float numRadialSegmentsPerRadian = StrokeTolerances::CalcNumRadialSegmentsPerRadian( float numRadialSegmentsPerRadian = StrokeTolerances::CalcNumRadialSegmentsPerRadian(
hwPatchWriter.parametricPrecision(), localStrokeWidth); matrixMaxScale, localStrokeWidth);
hwPatchWriter.updateTolerances(numRadialSegmentsPerRadian, stroke.getJoin()); hwPatchWriter.updateTolerances(numRadialSegmentsPerRadian, stroke.getJoin());
} }
// Fast SIMD queue that buffers up values for "numRadialSegmentsPerRadian". Only used when we // Fast SIMD queue that buffers up values for "numRadialSegmentsPerRadian". Only used when we
// have dynamic strokes. // have dynamic strokes.
StrokeToleranceBuffer toleranceBuffer(hwPatchWriter.parametricPrecision()); StrokeToleranceBuffer toleranceBuffer(matrixMaxScale);
for (PathStrokeList* pathStroke = pathStrokeList; pathStroke; pathStroke = pathStroke->fNext) { for (PathStrokeList* pathStroke = pathStrokeList; pathStroke; pathStroke = pathStroke->fNext) {
const SkStrokeRec& stroke = pathStroke->fStroke; const SkStrokeRec& stroke = pathStroke->fStroke;

View File

@ -82,26 +82,21 @@ protected:
// These tolerances decide the number of parametric and radial segments the tessellator will // These tolerances decide the number of parametric and radial segments the tessellator will
// linearize strokes into. These decisions are made in (pre-viewMatrix) local path space. // linearize strokes into. These decisions are made in (pre-viewMatrix) local path space.
struct StrokeTolerances { class StrokeTolerances {
// Decides the number of parametric segments the tessellator adds for each curve. (Uniform StrokeTolerances() = delete;
// steps in parametric space.) The tessellator will add enough parametric segments so that, public:
// once transformed into device space, they never deviate by more than
// 1/kTessellationPrecision pixels from the true curve.
constexpr static float CalcParametricPrecision(float matrixMaxScale) {
return matrixMaxScale * kTessellationPrecision;
}
// Decides the number of radial segments the tessellator adds for each curve. (Uniform steps // Decides the number of radial segments the tessellator adds for each curve. (Uniform steps
// in tangent angle.) The tessellator will add this number of radial segments for each // in tangent angle.) The tessellator will add this number of radial segments for each
// radian of rotation in local path space. // radian of rotation in local path space.
static float CalcNumRadialSegmentsPerRadian(float parametricPrecision, static float CalcNumRadialSegmentsPerRadian(float matrixMaxScale, float strokeWidth) {
float strokeWidth) { float cosTheta = 1.f - (1.f / kTessellationPrecision) / (matrixMaxScale * strokeWidth);
return .5f / acosf(std::max(1 - 2 / (parametricPrecision * strokeWidth), -1.f)); return .5f / acosf(std::max(cosTheta, -1.f));
} }
template<int N> static vec<N> ApproxNumRadialSegmentsPerRadian(float parametricPrecision, template<int N>
vec<N> strokeWidths) { static vec<N> ApproxNumRadialSegmentsPerRadian(float matrixMaxScale, vec<N> strokeWidths) {
vec<N> cosTheta = skvx::max(1 - 2 / (parametricPrecision * strokeWidths), -1); vec<N> cosTheta = 1.f - (1.f / kTessellationPrecision) / (matrixMaxScale * strokeWidths);
// Subtract SKVX_APPROX_ACOS_MAX_ERROR so we never account for too few segments. // Subtract SKVX_APPROX_ACOS_MAX_ERROR so we never account for too few segments.
return .5f / (skvx::approx_acos(cosTheta) - SKVX_APPROX_ACOS_MAX_ERROR); return .5f / (approx_acos(max(cosTheta, -1.f)) - SKVX_APPROX_ACOS_MAX_ERROR);
} }
// Returns the equivalent stroke width in (pre-viewMatrix) local path space that the // Returns the equivalent stroke width in (pre-viewMatrix) local path space that the
// tessellator will use when rendering this stroke. This only differs from the actual stroke // tessellator will use when rendering this stroke. This only differs from the actual stroke
@ -130,18 +125,6 @@ struct StrokeTolerances {
} }
return localStrokeWidth; return localStrokeWidth;
} }
static StrokeTolerances Make(const float matrixMinMaxScales[2], float strokeWidth) {
return MakeNonHairline(matrixMinMaxScales[1],
GetLocalStrokeWidth(matrixMinMaxScales, strokeWidth));
}
static StrokeTolerances MakeNonHairline(float matrixMaxScale, float strokeWidth) {
SkASSERT(strokeWidth > 0);
float parametricPrecision = CalcParametricPrecision(matrixMaxScale);
return {parametricPrecision,
CalcNumRadialSegmentsPerRadian(parametricPrecision, strokeWidth)};
}
float fParametricPrecision;
float fNumRadialSegmentsPerRadian;
}; };
// Calculates and buffers up future values for "numRadialSegmentsPerRadian" using SIMD. // Calculates and buffers up future values for "numRadialSegmentsPerRadian" using SIMD.
@ -149,7 +132,7 @@ class alignas(sizeof(float) * 4) StrokeToleranceBuffer {
public: public:
using PathStrokeList = StrokeTessellator::PathStrokeList; using PathStrokeList = StrokeTessellator::PathStrokeList;
StrokeToleranceBuffer(float parametricPrecision) : fParametricPrecision(parametricPrecision) {} StrokeToleranceBuffer(float matrixMaxScale) : fMatrixMaxScale(matrixMaxScale) {}
float fetchRadialSegmentsPerRadian(PathStrokeList* head) { float fetchRadialSegmentsPerRadian(PathStrokeList* head) {
// StrokeTessellateOp::onCombineIfPossible does not allow hairlines to become dynamic. If // StrokeTessellateOp::onCombineIfPossible does not allow hairlines to become dynamic. If
@ -163,7 +146,7 @@ public:
do { do {
fStrokeWidths[i++] = peekAhead->fStroke.getWidth(); fStrokeWidths[i++] = peekAhead->fStroke.getWidth();
} while ((peekAhead = peekAhead->fNext) && i < 4); } while ((peekAhead = peekAhead->fNext) && i < 4);
auto tol = StrokeTolerances::ApproxNumRadialSegmentsPerRadian(fParametricPrecision, auto tol = StrokeTolerances::ApproxNumRadialSegmentsPerRadian(fMatrixMaxScale,
fStrokeWidths); fStrokeWidths);
tol.store(fNumRadialSegmentsPerRadian); tol.store(fNumRadialSegmentsPerRadian);
fBufferIdx = 0; fBufferIdx = 0;
@ -176,7 +159,7 @@ public:
private: private:
float4 fStrokeWidths{}; // Must be first for alignment purposes. float4 fStrokeWidths{}; // Must be first for alignment purposes.
float fNumRadialSegmentsPerRadian[4]; float fNumRadialSegmentsPerRadian[4];
const float fParametricPrecision; const float fMatrixMaxScale;
int fBufferIdx = 4; // Initialize the buffer as "empty"; int fBufferIdx = 4; // Initialize the buffer as "empty";
}; };

View File

@ -123,10 +123,10 @@ float miter_extent(float cosTheta, float miterLimit) {
})"; })";
// Returns the number of radial segments required for each radian of rotation, in order for the // Returns the number of radial segments required for each radian of rotation, in order for the
// curve to appear "smooth" as defined by the parametricPrecision. // curve to appear "smooth" as defined by the max scale.
const char* GrStrokeTessellationShader::Impl::kNumRadialSegmentsPerRadianFn = R"( const char* GrStrokeTessellationShader::Impl::kNumRadialSegmentsPerRadianFn = R"(
float num_radial_segments_per_radian(float parametricPrecision, float strokeRadius) { float num_radial_segments_per_radian(float maxScale, float strokeRadius) {
return .5 / acos(max(1.0 - 1.0/(parametricPrecision * strokeRadius), -1.0)); return .5 / acos(max(1.0 - (1.0 / PRECISION) / (maxScale * strokeRadius), -1.0));
})"; })";
// Unlike mix(), this does not return b when t==1. But it otherwise seems to get better // Unlike mix(), this does not return b when t==1. But it otherwise seems to get better
@ -359,28 +359,24 @@ void GrStrokeTessellationShader::Impl::setData(const GrGLSLProgramDataManager& p
const auto& shader = geomProc.cast<GrStrokeTessellationShader>(); const auto& shader = geomProc.cast<GrStrokeTessellationShader>();
const auto& stroke = shader.stroke(); const auto& stroke = shader.stroke();
const float maxScale = shader.viewMatrix().getMaxScale();
if (!shader.hasDynamicStroke()) { if (!shader.hasDynamicStroke()) {
// Set up the tessellation control uniforms. // Set up the tessellation control uniforms. In the hairline case we transform prior to
skgpu::StrokeTolerances tolerances; // tessellation, so it uses an identity viewMatrix and a strokeWidth of 1.
if (!stroke.isHairlineStyle()) { const float effectiveMaxScale = stroke.isHairlineStyle() ? 1.f : maxScale;
tolerances = skgpu::StrokeTolerances::MakeNonHairline(shader.viewMatrix().getMaxScale(), const float effectiveStrokeWidth = stroke.isHairlineStyle() ? 1.f : stroke.getWidth();
stroke.getWidth()); float numRadialSegmentsPerRadian =
} else { skgpu::StrokeTolerances::CalcNumRadialSegmentsPerRadian(effectiveMaxScale,
// In the hairline case we transform prior to tessellation. Set up tolerances for an effectiveStrokeWidth);
// identity viewMatrix and a strokeWidth of 1.
tolerances = skgpu::StrokeTolerances::MakeNonHairline(1, 1);
}
float strokeRadius = (stroke.isHairlineStyle()) ? .5f : stroke.getWidth() * .5;
pdman.set4f(fTessControlArgsUniform, pdman.set4f(fTessControlArgsUniform,
tolerances.fParametricPrecision, // PARAMETRIC_PRECISION maxScale, // MAX_SCALE
tolerances.fNumRadialSegmentsPerRadian, // NUM_RADIAL_SEGMENTS_PER_RADIAN numRadialSegmentsPerRadian, // NUM_RADIAL_SEGMENTS_PER_RADIAN
skgpu::GetJoinType(stroke), // JOIN_TYPE skgpu::GetJoinType(stroke), // JOIN_TYPE
strokeRadius); // STROKE_RADIUS 0.5f * effectiveStrokeWidth); // STROKE_RADIUS
} else { } else {
SkASSERT(!stroke.isHairlineStyle()); SkASSERT(!stroke.isHairlineStyle());
float maxScale = shader.viewMatrix().getMaxScale(); pdman.set1f(fTessControlArgsUniform, maxScale);
pdman.set1f(fTessControlArgsUniform,
skgpu::StrokeTolerances::CalcParametricPrecision(maxScale));
} }
if (shader.mode() == GrStrokeTessellationShader::Mode::kFixedCount) { if (shader.mode() == GrStrokeTessellationShader::Mode::kFixedCount) {

View File

@ -100,10 +100,10 @@ protected:
// miter limit and need to revert to a bevel join. // miter limit and need to revert to a bevel join.
static const char* kMiterExtentFn; static const char* kMiterExtentFn;
// float num_radial_segments_per_radian(float parametricPrecision, float strokeRadius) { ... // float num_radial_segments_per_radian(float maxScale, float strokeRadius) { ...
// //
// Returns the number of radial segments required for each radian of rotation, in order for the // Returns the number of radial segments required for each radian of rotation, in order for the
// curve to appear "smooth" as defined by the parametricPrecision. // curve to appear "smooth" as defined by the max scale factor.
static const char* kNumRadialSegmentsPerRadianFn; static const char* kNumRadialSegmentsPerRadianFn;
// float<N> unchecked_mix(float<N> a, float<N> b, float<N> T) { ... // float<N> unchecked_mix(float<N> a, float<N> b, float<N> T) { ...

View File

@ -30,6 +30,7 @@ void GrStrokeTessellationShader::HardwareImpl::onEmitCode(EmitArgs& args, GrGPAr
using TypeModifier = GrShaderVar::TypeModifier; using TypeModifier = GrShaderVar::TypeModifier;
v->defineConstantf("float", "kParametricEpsilon", "1.0 / (%i * 128)", v->defineConstantf("float", "kParametricEpsilon", "1.0 / (%i * 128)",
args.fShaderCaps->maxTessellationSegments()); // 1/128 of a segment. args.fShaderCaps->maxTessellationSegments()); // 1/128 of a segment.
v->defineConstant("PRECISION", skgpu::kTessellationPrecision);
// [numSegmentsInJoin, innerJoinRadiusMultiplier, prevJoinTangent.xy] // [numSegmentsInJoin, innerJoinRadiusMultiplier, prevJoinTangent.xy]
v->declareGlobal(GrShaderVar("vsJoinArgs0", SkSLType::kFloat4, TypeModifier::Out)); v->declareGlobal(GrShaderVar("vsJoinArgs0", SkSLType::kFloat4, TypeModifier::Out));
@ -61,7 +62,7 @@ void GrStrokeTessellationShader::HardwareImpl::onEmitCode(EmitArgs& args, GrGPAr
} }
if (!shader.hasDynamicStroke()) { if (!shader.hasDynamicStroke()) {
// [PARAMETRIC_PRECISION, NUM_RADIAL_SEGMENTS_PER_RADIAN, JOIN_TYPE, STROKE_RADIUS] // [MAX_SCALE, NUM_RADIAL_SEGMENTS_PER_RADIAN, JOIN_TYPE, STROKE_RADIUS]
const char* tessArgsName; const char* tessArgsName;
fTessControlArgsUniform = uniHandler->addUniform(nullptr, fTessControlArgsUniform = uniHandler->addUniform(nullptr,
kVertex_GrShaderFlag | kVertex_GrShaderFlag |
@ -73,17 +74,17 @@ void GrStrokeTessellationShader::HardwareImpl::onEmitCode(EmitArgs& args, GrGPAr
float NUM_RADIAL_SEGMENTS_PER_RADIAN = %s.y; float NUM_RADIAL_SEGMENTS_PER_RADIAN = %s.y;
float JOIN_TYPE = %s.z;)", tessArgsName, tessArgsName); float JOIN_TYPE = %s.z;)", tessArgsName, tessArgsName);
} else { } else {
const char* parametricPrecisionName; const char* maxScaleName;
fTessControlArgsUniform = uniHandler->addUniform(nullptr, fTessControlArgsUniform = uniHandler->addUniform(nullptr,
kVertex_GrShaderFlag | kVertex_GrShaderFlag |
kTessControl_GrShaderFlag | kTessControl_GrShaderFlag |
kTessEvaluation_GrShaderFlag, kTessEvaluation_GrShaderFlag,
SkSLType::kFloat, "parametricPrecision", SkSLType::kFloat, "maxScale",
&parametricPrecisionName); &maxScaleName);
v->codeAppendf(R"( v->codeAppendf(R"(
float STROKE_RADIUS = dynamicStrokeAttr.x; float STROKE_RADIUS = dynamicStrokeAttr.x;
float NUM_RADIAL_SEGMENTS_PER_RADIAN = num_radial_segments_per_radian(%s,STROKE_RADIUS); float NUM_RADIAL_SEGMENTS_PER_RADIAN = num_radial_segments_per_radian(%s,STROKE_RADIUS);
float JOIN_TYPE = dynamicStrokeAttr.y;)", parametricPrecisionName); float JOIN_TYPE = dynamicStrokeAttr.y;)", maxScaleName);
} }
fTranslateUniform = uniHandler->addUniform(nullptr, kTessEvaluation_GrShaderFlag, fTranslateUniform = uniHandler->addUniform(nullptr, kTessEvaluation_GrShaderFlag,
@ -340,11 +341,11 @@ SkString GrStrokeTessellationShader::HardwareImpl::getTessControlShaderGLSL(
const char* tessArgsName = uniformHandler.getUniformCStr(fTessControlArgsUniform); const char* tessArgsName = uniformHandler.getUniformCStr(fTessControlArgsUniform);
if (!shader.hasDynamicStroke()) { if (!shader.hasDynamicStroke()) {
code.appendf("uniform vec4 %s;\n", tessArgsName); code.appendf("uniform vec4 %s;\n", tessArgsName);
code.appendf("#define PARAMETRIC_PRECISION %s.x\n", tessArgsName); code.appendf("#define MAX_SCALE %s.x\n", tessArgsName);
code.appendf("#define NUM_RADIAL_SEGMENTS_PER_RADIAN %s.y\n", tessArgsName); code.appendf("#define NUM_RADIAL_SEGMENTS_PER_RADIAN %s.y\n", tessArgsName);
} else { } else {
code.appendf("uniform float %s;\n", tessArgsName); code.appendf("uniform float %s;\n", tessArgsName);
code.appendf("#define PARAMETRIC_PRECISION %s\n", tessArgsName); code.appendf("#define MAX_SCALE %s\n", tessArgsName);
code.appendf("#define NUM_RADIAL_SEGMENTS_PER_RADIAN vsStrokeArgs[0].x\n"); code.appendf("#define NUM_RADIAL_SEGMENTS_PER_RADIAN vsStrokeArgs[0].x\n");
} }
@ -429,10 +430,10 @@ SkString GrStrokeTessellationShader::HardwareImpl::getTessControlShaderGLSL(
float w = isinf(P[3].y) ? P[3].x : -1.0; // w<0 means the curve is an integral cubic. float w = isinf(P[3].y) ? P[3].x : -1.0; // w<0 means the curve is an integral cubic.
float numParametricSegments; float numParametricSegments;
if (w < 0.0) { if (w < 0.0) {
numParametricSegments = wangs_formula_cubic(PARAMETRIC_PRECISION, P[0], P[1], P[2], numParametricSegments = wangs_formula_cubic(MAX_SCALE * PRECISION, P[0], P[1], P[2],
P[3], mat2(1)); P[3], mat2(1));
} else { } else {
numParametricSegments = wangs_formula_conic(PARAMETRIC_PRECISION, P[0], P[1], P[2], w); numParametricSegments = wangs_formula_conic(MAX_SCALE * PRECISION, P[0], P[1], P[2], w);
} }
if (P[0] == P[1] && P[2] == P[3]) { if (P[0] == P[1] && P[2] == P[3]) {
// This is how the patch builder articulates lineTos but Wang's formula returns // This is how the patch builder articulates lineTos but Wang's formula returns

View File

@ -20,6 +20,7 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA
args.fVaryingHandler->emitAttributes(shader); args.fVaryingHandler->emitAttributes(shader);
args.fVertBuilder->defineConstant("float", "PI", "3.141592653589793238"); args.fVertBuilder->defineConstant("float", "PI", "3.141592653589793238");
args.fVertBuilder->defineConstant("PRECISION", skgpu::kTessellationPrecision);
// Helper functions. // Helper functions.
if (shader.hasDynamicStroke()) { if (shader.hasDynamicStroke()) {
@ -32,27 +33,27 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA
// Tessellation control uniforms and/or dynamic attributes. // Tessellation control uniforms and/or dynamic attributes.
if (!shader.hasDynamicStroke()) { if (!shader.hasDynamicStroke()) {
// [PARAMETRIC_PRECISION, NUM_RADIAL_SEGMENTS_PER_RADIAN, JOIN_TYPE, STROKE_RADIUS] // [MAX_SCALE, NUM_RADIAL_SEGMENTS_PER_RADIAN, JOIN_TYPE, STROKE_RADIUS]
const char* tessArgsName; const char* tessArgsName;
fTessControlArgsUniform = args.fUniformHandler->addUniform( fTessControlArgsUniform = args.fUniformHandler->addUniform(
nullptr, kVertex_GrShaderFlag, SkSLType::kFloat4, "tessControlArgs", nullptr, kVertex_GrShaderFlag, SkSLType::kFloat4, "tessControlArgs",
&tessArgsName); &tessArgsName);
args.fVertBuilder->codeAppendf(R"( args.fVertBuilder->codeAppendf(R"(
float PARAMETRIC_PRECISION = %s.x; float MAX_SCALE = %s.x;
float NUM_RADIAL_SEGMENTS_PER_RADIAN = %s.y; float NUM_RADIAL_SEGMENTS_PER_RADIAN = %s.y;
float JOIN_TYPE = %s.z; float JOIN_TYPE = %s.z;
float STROKE_RADIUS = %s.w;)", tessArgsName, tessArgsName, tessArgsName, tessArgsName); float STROKE_RADIUS = %s.w;)", tessArgsName, tessArgsName, tessArgsName, tessArgsName);
} else { } else {
const char* parametricPrecisionName; const char* maxScaleName;
fTessControlArgsUniform = args.fUniformHandler->addUniform( fTessControlArgsUniform = args.fUniformHandler->addUniform(
nullptr, kVertex_GrShaderFlag, SkSLType::kFloat, "parametricPrecision", nullptr, kVertex_GrShaderFlag, SkSLType::kFloat, "maxScale",
&parametricPrecisionName); &maxScaleName);
args.fVertBuilder->codeAppendf(R"( args.fVertBuilder->codeAppendf(R"(
float PARAMETRIC_PRECISION = %s; float MAX_SCALE = %s;
float STROKE_RADIUS = dynamicStrokeAttr.x; float STROKE_RADIUS = dynamicStrokeAttr.x;
float NUM_RADIAL_SEGMENTS_PER_RADIAN = num_radial_segments_per_radian( float NUM_RADIAL_SEGMENTS_PER_RADIAN = num_radial_segments_per_radian(
PARAMETRIC_PRECISION, STROKE_RADIUS); MAX_SCALE, STROKE_RADIUS);
float JOIN_TYPE = dynamicStrokeAttr.y;)", parametricPrecisionName); float JOIN_TYPE = dynamicStrokeAttr.y;)", maxScaleName);
} }
if (shader.hasDynamicColor()) { if (shader.hasDynamicColor()) {
@ -104,6 +105,22 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA
w = p3.x; w = p3.x;
p3 = p2; // Setting p3 equal to p2 works for the remaining rotational logic. p3 = p2; // Setting p3 equal to p2 works for the remaining rotational logic.
})"); })");
// Emit code to call Wang's formula to determine parametric segments. We do this before
// transform points for hairlines so that it is consistent with how the CPU tested the control
// points for chopping.
args.fVertBuilder->codeAppend(R"(
// Find how many parametric segments this stroke requires.
float numParametricSegments;
if (w < 0) {
numParametricSegments = wangs_formula_cubic(PRECISION, p0, p1, p2, p3, AFFINE_MATRIX);
} else {
numParametricSegments = wangs_formula_conic(PRECISION,
AFFINE_MATRIX * p0,
AFFINE_MATRIX * p1,
AFFINE_MATRIX * p2, w);
})");
if (shader.stroke().isHairlineStyle()) { if (shader.stroke().isHairlineStyle()) {
// Hairline case. Transform the points before tessellation. We can still hold off on the // 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. // translate until the end; we just need to perform the scale and skew right now.
@ -116,15 +133,6 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA
} }
args.fVertBuilder->codeAppend(R"( args.fVertBuilder->codeAppend(R"(
// Find how many parametric segments this stroke requires.
float numParametricSegments;
if (w < 0) {
numParametricSegments = wangs_formula_cubic(PARAMETRIC_PRECISION, p0, p1, p2, p3,
float2x2(1));
} else {
numParametricSegments = wangs_formula_conic(PARAMETRIC_PRECISION, p0, p1, p2, w);
}
// Find the starting and ending tangents. // Find the starting and ending tangents.
float2 tan0 = ((p0 == p1) ? (p1 == p2) ? p3 : p2 : p1) - p0; float2 tan0 = ((p0 == p1) ? (p1 == p2) ? p3 : p2 : p1) - p0;
float2 tan1 = p3 - ((p3 == p2) ? (p2 == p1) ? p0 : p1 : p2); float2 tan1 = p3 - ((p3 == p2) ? (p2 == p1) ? p0 : p1 : p2);