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:
parent
1729710b51
commit
ee45d92013
@ -28,28 +28,26 @@ namespace {
|
||||
class InstanceWriter {
|
||||
using VectorXform = wangs_formula::VectorXform;
|
||||
public:
|
||||
InstanceWriter(PatchWriter& patchWriter, float matrixMaxScale)
|
||||
InstanceWriter(PatchWriter& patchWriter, const SkMatrix& shaderMatrix)
|
||||
: fPatchWriter(patchWriter)
|
||||
, fParametricPrecision(StrokeTolerances::CalcParametricPrecision(matrixMaxScale)) {
|
||||
, fShaderXform(shaderMatrix) {
|
||||
SkASSERT(fPatchWriter.attribs() & PatchAttribs::kJoinControlPoint);
|
||||
}
|
||||
|
||||
float parametricPrecision() const { return fParametricPrecision; }
|
||||
|
||||
SK_ALWAYS_INLINE void lineTo(SkPoint start, SkPoint end) {
|
||||
fPatchWriter.writeLine(start, end);
|
||||
}
|
||||
|
||||
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) {
|
||||
fPatchWriter.writeConic(p, w, VectorXform(), fParametricPrecision);
|
||||
fPatchWriter.writeConic(p, w, fShaderXform);
|
||||
}
|
||||
|
||||
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
|
||||
@ -70,7 +68,7 @@ public:
|
||||
|
||||
private:
|
||||
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.
|
||||
@ -103,7 +101,8 @@ int StrokeFixedCountTessellator::writePatches(PatchWriter& patchWriter,
|
||||
int maxEdgesInJoin = 0;
|
||||
float maxRadialSegmentsPerRadian = 0;
|
||||
|
||||
InstanceWriter instanceWriter(patchWriter, matrixMinMaxScales[1]);
|
||||
float matrixMaxScale = matrixMinMaxScales[1];
|
||||
InstanceWriter instanceWriter(patchWriter, shaderMatrix);
|
||||
|
||||
if (!(fAttribs & PatchAttribs::kStrokeParams)) {
|
||||
// Strokes are static. Calculate tolerances once.
|
||||
@ -111,14 +110,14 @@ int StrokeFixedCountTessellator::writePatches(PatchWriter& patchWriter,
|
||||
float localStrokeWidth = StrokeTolerances::GetLocalStrokeWidth(matrixMinMaxScales.data(),
|
||||
stroke.getWidth());
|
||||
float numRadialSegmentsPerRadian = StrokeTolerances::CalcNumRadialSegmentsPerRadian(
|
||||
instanceWriter.parametricPrecision(), localStrokeWidth);
|
||||
matrixMaxScale, localStrokeWidth);
|
||||
maxEdgesInJoin = worst_case_edges_in_join(stroke.getJoin(), numRadialSegmentsPerRadian);
|
||||
maxRadialSegmentsPerRadian = numRadialSegmentsPerRadian;
|
||||
}
|
||||
|
||||
// Fast SIMD queue that buffers up values for "numRadialSegmentsPerRadian". Only used when we
|
||||
// have dynamic stroke.
|
||||
StrokeToleranceBuffer toleranceBuffer(instanceWriter.parametricPrecision());
|
||||
StrokeToleranceBuffer toleranceBuffer(matrixMaxScale);
|
||||
|
||||
for (PathStrokeList* pathStroke = pathStrokeList; pathStroke; pathStroke = pathStroke->fNext) {
|
||||
const SkStrokeRec& stroke = pathStroke->fStroke;
|
||||
|
@ -69,7 +69,7 @@ public:
|
||||
// Subtract 2 because the tessellation shader chops every cubic at two locations, and
|
||||
// each chop has the potential to introduce an extra segment.
|
||||
, fMaxTessellationSegments(std::max(maxTessellationSegments - 2, 1))
|
||||
, fParametricPrecision(StrokeTolerances::CalcParametricPrecision(matrixMaxScale)) {
|
||||
, fParametricPrecision(matrixMaxScale * kTessellationPrecision) {
|
||||
SkASSERT(fPatchWriter.attribs() & PatchAttribs::kJoinControlPoint);
|
||||
SkASSERT(!fPatchWriter.hasJoinControlPoint());
|
||||
}
|
||||
@ -672,7 +672,8 @@ int StrokeHardwareTessellator::writePatches(PatchWriter& patchWriter,
|
||||
PathStrokeList* pathStrokeList) {
|
||||
using JoinType = HwPatchWriter::JoinType;
|
||||
|
||||
HwPatchWriter hwPatchWriter(patchWriter, fMaxTessellationSegments, matrixMinMaxScales[1]);
|
||||
float matrixMaxScale = matrixMinMaxScales[1];
|
||||
HwPatchWriter hwPatchWriter(patchWriter, fMaxTessellationSegments, matrixMaxScale);
|
||||
|
||||
if (!(fAttribs & PatchAttribs::kStrokeParams)) {
|
||||
// Strokes are static. Calculate tolerances once.
|
||||
@ -680,13 +681,13 @@ int StrokeHardwareTessellator::writePatches(PatchWriter& patchWriter,
|
||||
float localStrokeWidth = StrokeTolerances::GetLocalStrokeWidth(matrixMinMaxScales.data(),
|
||||
stroke.getWidth());
|
||||
float numRadialSegmentsPerRadian = StrokeTolerances::CalcNumRadialSegmentsPerRadian(
|
||||
hwPatchWriter.parametricPrecision(), localStrokeWidth);
|
||||
matrixMaxScale, localStrokeWidth);
|
||||
hwPatchWriter.updateTolerances(numRadialSegmentsPerRadian, stroke.getJoin());
|
||||
}
|
||||
|
||||
// Fast SIMD queue that buffers up values for "numRadialSegmentsPerRadian". Only used when we
|
||||
// have dynamic strokes.
|
||||
StrokeToleranceBuffer toleranceBuffer(hwPatchWriter.parametricPrecision());
|
||||
StrokeToleranceBuffer toleranceBuffer(matrixMaxScale);
|
||||
|
||||
for (PathStrokeList* pathStroke = pathStrokeList; pathStroke; pathStroke = pathStroke->fNext) {
|
||||
const SkStrokeRec& stroke = pathStroke->fStroke;
|
||||
|
@ -82,26 +82,21 @@ protected:
|
||||
|
||||
// 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.
|
||||
struct StrokeTolerances {
|
||||
// Decides the number of parametric segments the tessellator adds for each curve. (Uniform
|
||||
// steps in parametric space.) The tessellator will add enough parametric segments so that,
|
||||
// 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;
|
||||
}
|
||||
class StrokeTolerances {
|
||||
StrokeTolerances() = delete;
|
||||
public:
|
||||
// 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
|
||||
// radian of rotation in local path space.
|
||||
static float CalcNumRadialSegmentsPerRadian(float parametricPrecision,
|
||||
float strokeWidth) {
|
||||
return .5f / acosf(std::max(1 - 2 / (parametricPrecision * strokeWidth), -1.f));
|
||||
static float CalcNumRadialSegmentsPerRadian(float matrixMaxScale, float strokeWidth) {
|
||||
float cosTheta = 1.f - (1.f / kTessellationPrecision) / (matrixMaxScale * strokeWidth);
|
||||
return .5f / acosf(std::max(cosTheta, -1.f));
|
||||
}
|
||||
template<int N> static vec<N> ApproxNumRadialSegmentsPerRadian(float parametricPrecision,
|
||||
vec<N> strokeWidths) {
|
||||
vec<N> cosTheta = skvx::max(1 - 2 / (parametricPrecision * strokeWidths), -1);
|
||||
template<int N>
|
||||
static vec<N> ApproxNumRadialSegmentsPerRadian(float matrixMaxScale, vec<N> strokeWidths) {
|
||||
vec<N> cosTheta = 1.f - (1.f / kTessellationPrecision) / (matrixMaxScale * strokeWidths);
|
||||
// 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
|
||||
// tessellator will use when rendering this stroke. This only differs from the actual stroke
|
||||
@ -130,18 +125,6 @@ struct StrokeTolerances {
|
||||
}
|
||||
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.
|
||||
@ -149,7 +132,7 @@ class alignas(sizeof(float) * 4) StrokeToleranceBuffer {
|
||||
public:
|
||||
using PathStrokeList = StrokeTessellator::PathStrokeList;
|
||||
|
||||
StrokeToleranceBuffer(float parametricPrecision) : fParametricPrecision(parametricPrecision) {}
|
||||
StrokeToleranceBuffer(float matrixMaxScale) : fMatrixMaxScale(matrixMaxScale) {}
|
||||
|
||||
float fetchRadialSegmentsPerRadian(PathStrokeList* head) {
|
||||
// StrokeTessellateOp::onCombineIfPossible does not allow hairlines to become dynamic. If
|
||||
@ -163,7 +146,7 @@ public:
|
||||
do {
|
||||
fStrokeWidths[i++] = peekAhead->fStroke.getWidth();
|
||||
} while ((peekAhead = peekAhead->fNext) && i < 4);
|
||||
auto tol = StrokeTolerances::ApproxNumRadialSegmentsPerRadian(fParametricPrecision,
|
||||
auto tol = StrokeTolerances::ApproxNumRadialSegmentsPerRadian(fMatrixMaxScale,
|
||||
fStrokeWidths);
|
||||
tol.store(fNumRadialSegmentsPerRadian);
|
||||
fBufferIdx = 0;
|
||||
@ -176,7 +159,7 @@ public:
|
||||
private:
|
||||
float4 fStrokeWidths{}; // Must be first for alignment purposes.
|
||||
float fNumRadialSegmentsPerRadian[4];
|
||||
const float fParametricPrecision;
|
||||
const float fMatrixMaxScale;
|
||||
int fBufferIdx = 4; // Initialize the buffer as "empty";
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
// 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"(
|
||||
float num_radial_segments_per_radian(float parametricPrecision, float strokeRadius) {
|
||||
return .5 / acos(max(1.0 - 1.0/(parametricPrecision * strokeRadius), -1.0));
|
||||
float num_radial_segments_per_radian(float maxScale, float strokeRadius) {
|
||||
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
|
||||
@ -359,28 +359,24 @@ void GrStrokeTessellationShader::Impl::setData(const GrGLSLProgramDataManager& p
|
||||
const auto& shader = geomProc.cast<GrStrokeTessellationShader>();
|
||||
const auto& stroke = shader.stroke();
|
||||
|
||||
const float maxScale = shader.viewMatrix().getMaxScale();
|
||||
if (!shader.hasDynamicStroke()) {
|
||||
// Set up the tessellation control uniforms.
|
||||
skgpu::StrokeTolerances tolerances;
|
||||
if (!stroke.isHairlineStyle()) {
|
||||
tolerances = skgpu::StrokeTolerances::MakeNonHairline(shader.viewMatrix().getMaxScale(),
|
||||
stroke.getWidth());
|
||||
} else {
|
||||
// In the hairline case we transform prior to tessellation. Set up tolerances for an
|
||||
// identity viewMatrix and a strokeWidth of 1.
|
||||
tolerances = skgpu::StrokeTolerances::MakeNonHairline(1, 1);
|
||||
}
|
||||
float strokeRadius = (stroke.isHairlineStyle()) ? .5f : stroke.getWidth() * .5;
|
||||
// Set up the tessellation control uniforms. In the hairline case we transform prior to
|
||||
// tessellation, so it uses an identity viewMatrix and a strokeWidth of 1.
|
||||
const float effectiveMaxScale = stroke.isHairlineStyle() ? 1.f : maxScale;
|
||||
const float effectiveStrokeWidth = stroke.isHairlineStyle() ? 1.f : stroke.getWidth();
|
||||
float numRadialSegmentsPerRadian =
|
||||
skgpu::StrokeTolerances::CalcNumRadialSegmentsPerRadian(effectiveMaxScale,
|
||||
effectiveStrokeWidth);
|
||||
|
||||
pdman.set4f(fTessControlArgsUniform,
|
||||
tolerances.fParametricPrecision, // PARAMETRIC_PRECISION
|
||||
tolerances.fNumRadialSegmentsPerRadian, // NUM_RADIAL_SEGMENTS_PER_RADIAN
|
||||
maxScale, // MAX_SCALE
|
||||
numRadialSegmentsPerRadian, // NUM_RADIAL_SEGMENTS_PER_RADIAN
|
||||
skgpu::GetJoinType(stroke), // JOIN_TYPE
|
||||
strokeRadius); // STROKE_RADIUS
|
||||
0.5f * effectiveStrokeWidth); // STROKE_RADIUS
|
||||
} else {
|
||||
SkASSERT(!stroke.isHairlineStyle());
|
||||
float maxScale = shader.viewMatrix().getMaxScale();
|
||||
pdman.set1f(fTessControlArgsUniform,
|
||||
skgpu::StrokeTolerances::CalcParametricPrecision(maxScale));
|
||||
pdman.set1f(fTessControlArgsUniform, maxScale);
|
||||
}
|
||||
|
||||
if (shader.mode() == GrStrokeTessellationShader::Mode::kFixedCount) {
|
||||
|
@ -100,10 +100,10 @@ protected:
|
||||
// miter limit and need to revert to a bevel join.
|
||||
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
|
||||
// curve to appear "smooth" as defined by the parametricPrecision.
|
||||
// curve to appear "smooth" as defined by the max scale factor.
|
||||
static const char* kNumRadialSegmentsPerRadianFn;
|
||||
|
||||
// float<N> unchecked_mix(float<N> a, float<N> b, float<N> T) { ...
|
||||
|
@ -30,6 +30,7 @@ void GrStrokeTessellationShader::HardwareImpl::onEmitCode(EmitArgs& args, GrGPAr
|
||||
using TypeModifier = GrShaderVar::TypeModifier;
|
||||
v->defineConstantf("float", "kParametricEpsilon", "1.0 / (%i * 128)",
|
||||
args.fShaderCaps->maxTessellationSegments()); // 1/128 of a segment.
|
||||
v->defineConstant("PRECISION", skgpu::kTessellationPrecision);
|
||||
|
||||
// [numSegmentsInJoin, innerJoinRadiusMultiplier, prevJoinTangent.xy]
|
||||
v->declareGlobal(GrShaderVar("vsJoinArgs0", SkSLType::kFloat4, TypeModifier::Out));
|
||||
@ -61,7 +62,7 @@ void GrStrokeTessellationShader::HardwareImpl::onEmitCode(EmitArgs& args, GrGPAr
|
||||
}
|
||||
|
||||
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;
|
||||
fTessControlArgsUniform = uniHandler->addUniform(nullptr,
|
||||
kVertex_GrShaderFlag |
|
||||
@ -73,17 +74,17 @@ void GrStrokeTessellationShader::HardwareImpl::onEmitCode(EmitArgs& args, GrGPAr
|
||||
float NUM_RADIAL_SEGMENTS_PER_RADIAN = %s.y;
|
||||
float JOIN_TYPE = %s.z;)", tessArgsName, tessArgsName);
|
||||
} else {
|
||||
const char* parametricPrecisionName;
|
||||
const char* maxScaleName;
|
||||
fTessControlArgsUniform = uniHandler->addUniform(nullptr,
|
||||
kVertex_GrShaderFlag |
|
||||
kTessControl_GrShaderFlag |
|
||||
kTessEvaluation_GrShaderFlag,
|
||||
SkSLType::kFloat, "parametricPrecision",
|
||||
¶metricPrecisionName);
|
||||
SkSLType::kFloat, "maxScale",
|
||||
&maxScaleName);
|
||||
v->codeAppendf(R"(
|
||||
float STROKE_RADIUS = dynamicStrokeAttr.x;
|
||||
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,
|
||||
@ -340,11 +341,11 @@ SkString GrStrokeTessellationShader::HardwareImpl::getTessControlShaderGLSL(
|
||||
const char* tessArgsName = uniformHandler.getUniformCStr(fTessControlArgsUniform);
|
||||
if (!shader.hasDynamicStroke()) {
|
||||
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);
|
||||
} else {
|
||||
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");
|
||||
}
|
||||
|
||||
@ -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 numParametricSegments;
|
||||
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));
|
||||
} 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]) {
|
||||
// This is how the patch builder articulates lineTos but Wang's formula returns
|
||||
|
@ -20,6 +20,7 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA
|
||||
args.fVaryingHandler->emitAttributes(shader);
|
||||
|
||||
args.fVertBuilder->defineConstant("float", "PI", "3.141592653589793238");
|
||||
args.fVertBuilder->defineConstant("PRECISION", skgpu::kTessellationPrecision);
|
||||
|
||||
// Helper functions.
|
||||
if (shader.hasDynamicStroke()) {
|
||||
@ -32,27 +33,27 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA
|
||||
|
||||
// Tessellation control uniforms and/or dynamic attributes.
|
||||
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;
|
||||
fTessControlArgsUniform = args.fUniformHandler->addUniform(
|
||||
nullptr, kVertex_GrShaderFlag, SkSLType::kFloat4, "tessControlArgs",
|
||||
&tessArgsName);
|
||||
args.fVertBuilder->codeAppendf(R"(
|
||||
float PARAMETRIC_PRECISION = %s.x;
|
||||
float MAX_SCALE = %s.x;
|
||||
float NUM_RADIAL_SEGMENTS_PER_RADIAN = %s.y;
|
||||
float JOIN_TYPE = %s.z;
|
||||
float STROKE_RADIUS = %s.w;)", tessArgsName, tessArgsName, tessArgsName, tessArgsName);
|
||||
} else {
|
||||
const char* parametricPrecisionName;
|
||||
const char* maxScaleName;
|
||||
fTessControlArgsUniform = args.fUniformHandler->addUniform(
|
||||
nullptr, kVertex_GrShaderFlag, SkSLType::kFloat, "parametricPrecision",
|
||||
¶metricPrecisionName);
|
||||
nullptr, kVertex_GrShaderFlag, SkSLType::kFloat, "maxScale",
|
||||
&maxScaleName);
|
||||
args.fVertBuilder->codeAppendf(R"(
|
||||
float PARAMETRIC_PRECISION = %s;
|
||||
float MAX_SCALE = %s;
|
||||
float STROKE_RADIUS = dynamicStrokeAttr.x;
|
||||
float NUM_RADIAL_SEGMENTS_PER_RADIAN = num_radial_segments_per_radian(
|
||||
PARAMETRIC_PRECISION, STROKE_RADIUS);
|
||||
float JOIN_TYPE = dynamicStrokeAttr.y;)", parametricPrecisionName);
|
||||
MAX_SCALE, STROKE_RADIUS);
|
||||
float JOIN_TYPE = dynamicStrokeAttr.y;)", maxScaleName);
|
||||
}
|
||||
|
||||
if (shader.hasDynamicColor()) {
|
||||
@ -104,6 +105,22 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA
|
||||
w = p3.x;
|
||||
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()) {
|
||||
// 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.
|
||||
@ -116,15 +133,6 @@ void GrStrokeTessellationShader::InstancedImpl::onEmitCode(EmitArgs& args, GrGPA
|
||||
}
|
||||
|
||||
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.
|
||||
float2 tan0 = ((p0 == p1) ? (p1 == p2) ? p3 : p2 : p1) - p0;
|
||||
float2 tan1 = p3 - ((p3 == p2) ? (p2 == p1) ? p0 : p1 : p2);
|
||||
|
Loading…
Reference in New Issue
Block a user