blob: d378d504702fe67437ff0daa0fc5dc5bc9f979e9 [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/GrVx.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
};
enum class ShaderFlags {
kNone = 0,
kHasConics = 1 << 0,
kWideColor = 1 << 1,
kDynamicStroke = 1 << 2, // Each patch or instance has its own stroke width and join type.
kDynamicColor = 1 << 3, // Each patch or instance has its own color.
};
GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(ShaderFlags);
// When using indirect draws, 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
//
constexpr static int NumExtraEdgesInIndirectJoin(SkPaint::Join joinType) {
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;
}
// We encode all of a join's information in a single float value:
//
// Negative => Round Join
// Zero => Bevel Join
// Positive => Miter join, and the value is also the miter limit
//
static float GetJoinType(const SkStrokeRec& stroke) {
switch (stroke.getJoin()) {
case SkPaint::kRound_Join: return -1;
case SkPaint::kBevel_Join: return 0;
case SkPaint::kMiter_Join: SkASSERT(stroke.getMiter() >= 0); return stroke.getMiter();
}
SkUNREACHABLE;
}
// This struct gets written out to each patch or instance if kDynamicStroke is enabled.
struct DynamicStroke {
static bool StrokesHaveEqualDynamicState(const SkStrokeRec& a, const SkStrokeRec& b) {
return a.getWidth() == b.getWidth() && a.getJoin() == b.getJoin() &&
(a.getJoin() != SkPaint::kMiter_Join || a.getMiter() == b.getMiter());
}
void set(const SkStrokeRec& stroke) {
fRadius = stroke.getWidth() * .5f;
fJoinType = GetJoinType(stroke);
}
float fRadius;
float fJoinType; // See GetJoinType().
};
// Size in bytes of a tessellation patch with the given shader flags.
static size_t PatchStride(ShaderFlags shaderFlags) {
return sizeof(SkPoint) * 5 + DynamicStateStride(shaderFlags);
}
// Size in bytes of an indirect draw instance with the given shader flags.
static size_t IndirectInstanceStride(ShaderFlags shaderFlags) {
return sizeof(float) * 11 + DynamicStateStride(shaderFlags);
}
// Combined size in bytes of the dynamic state attribs enabled in the given shader flags.
static size_t DynamicStateStride(ShaderFlags shaderFlags) {
size_t stride = 0;
if (shaderFlags & ShaderFlags::kDynamicStroke) {
stride += sizeof(DynamicStroke);
}
if (shaderFlags & ShaderFlags::kDynamicColor) {
stride += (shaderFlags & ShaderFlags::kWideColor) ? sizeof(float) * 4 : 4;
}
return stride;
}
// 'viewMatrix' is applied to the geometry post tessellation. It cannot have perspective.
GrStrokeTessellateShader(Mode mode, ShaderFlags shaderFlags, const SkMatrix& viewMatrix,
const SkStrokeRec& stroke, SkPMColor4f color)
: GrPathShader(kTessellate_GrStrokeTessellateShader_ClassID, viewMatrix,
(mode == Mode::kTessellation) ?
GrPrimitiveType::kPatches : GrPrimitiveType::kTriangleStrip,
(mode == Mode::kTessellation) ? 1 : 0)
, fMode(mode)
, fShaderFlags(shaderFlags)
, fStroke(stroke)
, fColor(color) {
if (fMode == Mode::kTessellation) {
// A join calculates its starting angle using prevCtrlPtAttr.
fAttribs.emplace_back("prevCtrlPtAttr", kFloat2_GrVertexAttribType, kFloat2_GrSLType);
// pts 0..3 define the stroke as a cubic bezier. If p3.y is infinity, then it's a conic
// with w=p3.x.
//
// If p0 == prevCtrlPtAttr, then no join is emitted.
//
// pts=[p0, p3, p3, p3] is a reserved pattern that means this patch is a join only,
// whose start and end tangents are (p0 - inputPrevCtrlPt) and (p3 - p0).
//
// pts=[p0, p0, p0, p3] is a reserved pattern that means this patch is a "bowtie", or
// double-sided round join, anchored on p0 and rotating from (p0 - prevCtrlPtAttr) to
// (p3 - p0).
fAttribs.emplace_back("pts01Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
fAttribs.emplace_back("pts23Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
} else {
// pts 0..3 define the stroke as a cubic bezier. If p3.y is infinity, then it's a conic
// with w=p3.x.
//
// An empty stroke (p0==p1==p2==p3) is a special case that denotes a circle, or
// 180-degree point stroke.
fAttribs.emplace_back("pts01Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
fAttribs.emplace_back("pts23Attr", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
// "lastControlPoint" and "numTotalEdges" are both packed into argsAttr.
//
// A join calculates its starting angle using "argsAttr.xy=lastControlPoint".
//
// "abs(argsAttr.z=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.
fAttribs.emplace_back("argsAttr", kFloat3_GrVertexAttribType, kFloat3_GrSLType);
}
if (fShaderFlags & ShaderFlags::kDynamicStroke) {
fAttribs.emplace_back("dynamicStrokeAttr", kFloat2_GrVertexAttribType,
kFloat2_GrSLType);
}
if (fShaderFlags & ShaderFlags::kDynamicColor) {
fAttribs.emplace_back("dynamicColorAttr",
(fShaderFlags & ShaderFlags::kWideColor)
? kFloat4_GrVertexAttribType
: kUByte4_norm_GrVertexAttribType,
kHalf4_GrSLType);
}
if (fMode == Mode::kTessellation) {
this->setVertexAttributes(fAttribs.data(), fAttribs.count());
SkASSERT(this->vertexStride() == PatchStride(fShaderFlags));
} else {
this->setInstanceAttributes(fAttribs.data(), fAttribs.count());
SkASSERT(this->instanceStride() == IndirectInstanceStride(fShaderFlags));
}
SkASSERT(fAttribs.count() <= kMaxAttribCount);
}
bool hasConics() const { return fShaderFlags & ShaderFlags::kHasConics; }
bool hasDynamicStroke() const { return fShaderFlags & ShaderFlags::kDynamicStroke; }
bool hasDynamicColor() const { return fShaderFlags & ShaderFlags::kDynamicColor; }
private:
const char* name() const override { return "GrStrokeTessellateShader"; }
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override;
GrGLSLGeometryProcessor* createGLSLInstance(const GrShaderCaps&) const final;
SkString getTessControlShaderGLSL(const GrGLSLGeometryProcessor*,
const char* versionAndExtensionDecls,
const GrGLSLUniformHandler&,
const GrShaderCaps&) const override;
SkString getTessEvaluationShaderGLSL(const GrGLSLGeometryProcessor*,
const char* versionAndExtensionDecls,
const GrGLSLUniformHandler&,
const GrShaderCaps&) const override;
const Mode fMode;
const ShaderFlags fShaderFlags;
const SkStrokeRec fStroke;
const SkPMColor4f fColor;
constexpr static int kMaxAttribCount = 5;
SkSTArray<kMaxAttribCount, Attribute> fAttribs;
class TessellationImpl;
class IndirectImpl;
};
GR_MAKE_BITFIELD_CLASS_OPS(GrStrokeTessellateShader::ShaderFlags);
#endif