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 {
|
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;
|
||||||
|
@ -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;
|
||||||
|
@ -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";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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) { ...
|
||||||
|
@ -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",
|
||||||
¶metricPrecisionName);
|
&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
|
||||||
|
@ -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",
|
||||||
¶metricPrecisionName);
|
&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);
|
||||||
|
Loading…
Reference in New Issue
Block a user