blob: 70e65794e1ad5a7fbb1d01b5a161bb4e03d90767 [file] [log] [blame]
/*
* Copyright 2020 Google LLC.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrStrokeTessellateShader_DEFINED
#define GrStrokeTessellateShader_DEFINED
#include "src/gpu/tessellate/GrPathShader.h"
#include "include/core/SkStrokeRec.h"
#include "src/gpu/tessellate/GrTessellationPathRenderer.h"
#include <array>
class GrGLSLUniformHandler;
// Tessellates a batch of stroke patches directly to the canvas. Tessellated stroking works by
// creating stroke-width, orthogonal edges at set locations along the curve and then connecting them
// with a quad strip. These orthogonal edges come from two different sets: "parametric edges" and
// "radial edges". Parametric edges are spaced evenly in the parametric sense, and radial edges
// divide the curve's _rotation_ into even steps. The tessellation shader evaluates both sets of
// edges and sorts them into a single quad strip. With this combined set of edges we can stroke any
// curve, regardless of curvature.
class GrStrokeTessellateShader : public GrPathShader {
public:
// Are we using hardware tessellation or indirect draws?
enum class Mode : bool {
kTessellation,
kIndirect
};
// When using Mode::kTessellation, this is how patches sent to the GPU are structured.
struct Patch {
// A join calculates its starting angle using fPrevControlPoint.
SkPoint fPrevControlPoint;
// fPts define the cubic stroke as well as the ending angle of the previous join.
//
// If fPts[0] == fPrevControlPoint, then no join is emitted.
//
// fPts=[p0, p3, p3, p3] is a reserved pattern that means this patch is a join only, whose
// start and end tangents are (fPts[0] - fPrevControlPoint) and (fPts[3] - fPts[0]).
//
// fPts=[p0, p0, p0, p3] is a reserved pattern that means this patch is a cusp point
// anchored on p0 and rotating from (fPts[0] - fPrevControlPoint) to (fPts[3] - fPts[0]).
std::array<SkPoint, 4> fPts;
};
// When using Mode::kIndirect, these are the instances that get sent to the GPU.
struct IndirectInstance {
constexpr static int NumExtraEdgesInJoin(SkPaint::Join joinType) {
// We expect a fixed number of additional edges to be appended onto each instance in
// order to implement its preceding join. Specifically, each join emits:
//
// * Two colocated edges at the beginning (a double-sided edge to seam with the
// preceding stroke and a single-sided edge to seam with the join).
//
// * An extra edge in the middle for miter joins, or else a variable number for round
// joins (counted in the resolveLevel).
//
// * A single sided edge at the end of the join that is colocated with the first
// (double sided) edge of the stroke
//
switch (joinType) {
case SkPaint::kMiter_Join:
return 4;
case SkPaint::kRound_Join:
// The inner edges for round joins are counted in the stroke's resolveLevel.
[[fallthrough]];
case SkPaint::kBevel_Join:
return 3;
}
SkUNREACHABLE;
}
// Writes the given stroke into this instance. "abs(numTotalEdges)" tells the shader the
// literal number of edges in the triangle strip being rendered (i.e., it should be
// vertexCount/2). If numTotalEdges is negative and the join type is "kRound", it also
// instructs the shader to only allocate one segment the preceding round join.
void set(const SkPoint pts[4], SkPoint lastControlPoint, int numTotalEdges) {
memcpy(fPts.data(), pts, sizeof(fPts));
fLastControlPoint = lastControlPoint;
fNumTotalEdges = numTotalEdges;
}
// A "circle" is a stroke-width circle drawn as a 180-degree point stroke. They should be
// drawn at cusp points on the curve and for round caps.
void setCircle(SkPoint pt, int numEdgesForCircles) {
SkASSERT(numEdgesForCircles >= 0);
// An empty stroke is a special case that denotes a circle, or 180-degree point stroke.
fPts.fill(pt);
fLastControlPoint = pt;
// Mark fNumTotalEdges negative so the shader assigns the least possible number of edges
// to its (empty) preceding join.
fNumTotalEdges = -numEdgesForCircles;
}
std::array<SkPoint, 4> fPts;
SkPoint fLastControlPoint;
float fNumTotalEdges;
};
// These tolerances decide the number of parametric and radial segments the tessellator will
// linearize curves into. These decisions are made in (pre-viewMatrix) local path space.
struct Tolerances {
// Returns the equivalent tolerances in (pre-viewMatrix) local path space that the
// tessellator will use when rendering this stroke.
static Tolerances MakePreTransform(const SkMatrix& viewMatrix, const SkStrokeRec& stroke) {
std::array<float,2> matrixScales;
if (!viewMatrix.getMinMaxScales(matrixScales.data())) {
matrixScales.fill(1);
}
auto [matrixMinScale, matrixMaxScale] = matrixScales;
float localStrokeWidth = stroke.getWidth();
if (stroke.isHairlineStyle()) {
// If the stroke is hairline then the tessellator will operate in post-transform
// space instead. But for the sake of CPU methods that need to conservatively
// approximate the number of segments to emit, we use
// localStrokeWidth ~= 1/matrixMinScale.
float approxScale = matrixMinScale;
// If the matrix has strong skew, don't let the scale shoot off to infinity. (This
// does not affect the tessellator; only the CPU methods that approximate the number
// of segments to emit.)
approxScale = std::max(matrixMinScale, matrixMaxScale * .25f);
localStrokeWidth = 1/approxScale;
}
return GrStrokeTessellateShader::Tolerances(matrixMaxScale, localStrokeWidth);
}
Tolerances() = default;
Tolerances(float matrixMaxScale, float strokeWidth) {
this->set(matrixMaxScale, strokeWidth);
}
void set(float matrixMaxScale, float strokeWidth) {
fParametricIntolerance =
matrixMaxScale * GrTessellationPathRenderer::kLinearizationIntolerance;
fNumRadialSegmentsPerRadian =
.5f / acosf(std::max(1 - 2/(fParametricIntolerance * strokeWidth), -1.f));
}
// 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/GrTessellationPathRenderer::kLinearizationIntolerance pixels from the true curve.
float fParametricIntolerance;
// 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.
float fNumRadialSegmentsPerRadian;
};
// 'viewMatrix' is applied to the geometry post tessellation. It cannot have perspective.
GrStrokeTessellateShader(Mode mode, bool hasConics, const SkStrokeRec& stroke,
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)
, fColor(color) {
if (fMode == Mode::kTessellation) {
constexpr static Attribute kTessellationAttribs[] = {
{"inputPrevCtrlPt", kFloat2_GrVertexAttribType, kFloat2_GrSLType},
{"inputPts01", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
{"inputPts23", kFloat4_GrVertexAttribType, kFloat4_GrSLType}};
this->setVertexAttributes(kTessellationAttribs, SK_ARRAY_COUNT(kTessellationAttribs));
SkASSERT(this->vertexStride() == sizeof(Patch));
} else {
constexpr static Attribute kIndirectAttribs[] = {
{"pts01", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
{"pts23", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
// "fLastControlPoint" and "fNumTotalEdges" are both packed into these args.
// See IndirectInstance.
{"args", kFloat3_GrVertexAttribType, kFloat3_GrSLType}};
this->setInstanceAttributes(kIndirectAttribs, SK_ARRAY_COUNT(kIndirectAttribs));
SkASSERT(this->instanceStride() == sizeof(IndirectInstance));
}
}
private:
const char* name() const override { return "GrStrokeTessellateShader"; }
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override;
GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final;
SkString getTessControlShaderGLSL(const GrGLSLPrimitiveProcessor*,
const char* versionAndExtensionDecls,
const GrGLSLUniformHandler&,
const GrShaderCaps&) const override;
SkString getTessEvaluationShaderGLSL(const GrGLSLPrimitiveProcessor*,
const char* versionAndExtensionDecls,
const GrGLSLUniformHandler&,
const GrShaderCaps&) const override;
const Mode fMode;
const bool fHasConics;
const SkStrokeRec fStroke;
const SkPMColor4f fColor;
class TessellationImpl;
class IndirectImpl;
};
#endif