Hide path tessellation shaders in private files
Replaces the public class definitions with factory methods. Separates the class definitions into their own separate files. Bug: skia:10419 Change-Id: I574d920d5a3d0dc98fa5eb231c9b510e04aebf78 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/413796 Commit-Queue: Chris Dalton <csmartdalton@google.com> Reviewed-by: Robert Phillips <robertphillips@google.com>
This commit is contained in:
parent
e79a6da3b5
commit
b63711a6c4
@ -477,6 +477,8 @@ skia_gpu_sources = [
|
||||
# tessellate/shaders
|
||||
"$_src/gpu/tessellate/shaders/GrPathTessellationShader.cpp",
|
||||
"$_src/gpu/tessellate/shaders/GrPathTessellationShader.h",
|
||||
"$_src/gpu/tessellate/shaders/GrPathTessellationShader_Hardware.cpp",
|
||||
"$_src/gpu/tessellate/shaders/GrPathTessellationShader_MiddleOut.cpp",
|
||||
"$_src/gpu/tessellate/shaders/GrStrokeTessellationShader.cpp",
|
||||
"$_src/gpu/tessellate/shaders/GrStrokeTessellationShader.h",
|
||||
"$_src/gpu/tessellate/shaders/GrStrokeTessellationShader_HardwareImpl.cpp",
|
||||
|
@ -129,12 +129,12 @@ public:
|
||||
kFwidthSquircleTestProcessor_ClassID,
|
||||
kSwizzleFragmentProcessor_ClassID,
|
||||
kTessellate_BoundingBoxShader_ClassID,
|
||||
kTessellate_GrCurveMiddleOutShader_ClassID,
|
||||
kTessellate_GrCurveTessellateShader_ClassID,
|
||||
kTessellate_GrStrokeTessellationShader_ClassID,
|
||||
kTessellate_GrTriangleShader_ClassID,
|
||||
kTessellate_GrWedgeTessellateShader_ClassID,
|
||||
kTessellate_HardwareCurveShader_ClassID,
|
||||
kTessellate_HardwareWedgeShader_ClassID,
|
||||
kTessellate_HullShader_ClassID,
|
||||
kTessellate_MiddleOutShader_ClassID,
|
||||
kTessellate_SimpleTriangleShader_ClassID,
|
||||
kTessellationTestTriShader_ClassID,
|
||||
kTessellationTestRectShader_ClassID,
|
||||
kTestFP_ClassID,
|
||||
|
@ -34,7 +34,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
const char* name() const final { return "HullShader"; }
|
||||
const char* name() const final { return "tessellate_HullShader"; }
|
||||
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const final {}
|
||||
GrGLSLGeometryProcessor* createGLSLInstance(const GrShaderCaps&) const final;
|
||||
};
|
||||
@ -134,14 +134,16 @@ void GrPathInnerTriangulateOp::pushFanStencilProgram(const GrTessellationShader:
|
||||
const GrPipeline* pipelineForStencils,
|
||||
const GrUserStencilSettings* stencil) {
|
||||
SkASSERT(pipelineForStencils);
|
||||
auto shader = args.fArena->make<GrTriangleShader>(fViewMatrix, SK_PMColor4fTRANSPARENT);
|
||||
auto shader = GrPathTessellationShader::MakeSimpleTriangleShader(args.fArena, fViewMatrix,
|
||||
SK_PMColor4fTRANSPARENT);
|
||||
fFanPrograms.push_back(GrTessellationShader::MakeProgram(args, shader, pipelineForStencils,
|
||||
stencil)); }
|
||||
|
||||
void GrPathInnerTriangulateOp::pushFanFillProgram(const GrTessellationShader::ProgramArgs& args,
|
||||
const GrUserStencilSettings* stencil) {
|
||||
SkASSERT(fPipelineForFills);
|
||||
auto* shader = args.fArena->make<GrTriangleShader>(fViewMatrix, fColor);
|
||||
auto shader = GrPathTessellationShader::MakeSimpleTriangleShader(args.fArena, fViewMatrix,
|
||||
fColor);
|
||||
fFanPrograms.push_back(GrTessellationShader::MakeProgram(args, shader, fPipelineForFills,
|
||||
stencil));
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
const char* name() const final { return "BoundingBoxShader"; }
|
||||
const char* name() const final { return "tessellate_BoundingBoxShader"; }
|
||||
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const final {}
|
||||
GrGLSLGeometryProcessor* createGLSLInstance(const GrShaderCaps&) const final;
|
||||
};
|
||||
@ -110,7 +110,8 @@ void GrPathStencilFillOp::prePreparePrograms(const GrTessellationShader::Program
|
||||
// Large complex paths do better with a dedicated triangle shader for the inner fan.
|
||||
// This takes less PCI bus bandwidth (6 floats per triangle instead of 8) and allows us
|
||||
// to make sure it has an efficient middle-out topology.
|
||||
auto shader = args.fArena->make<GrTriangleShader>(fViewMatrix, SK_PMColor4fTRANSPARENT);
|
||||
auto shader = GrPathTessellationShader::MakeSimpleTriangleShader(
|
||||
args.fArena, fViewMatrix, SK_PMColor4fTRANSPARENT);
|
||||
fStencilFanProgram = GrTessellationShader::MakeProgram(args, shader, stencilPipeline,
|
||||
stencilPathSettings);
|
||||
drawFanWithTessellator = GrPathTessellator::DrawInnerFan::kNo;
|
||||
|
@ -45,7 +45,7 @@ GrPathTessellator* GrPathIndirectTessellator::Make(SkArenaAlloc* arena, const Sk
|
||||
const SkMatrix& viewMatrix,
|
||||
const SkPMColor4f& color,
|
||||
DrawInnerFan drawInnerFan) {
|
||||
auto shader = arena->make<GrCurveMiddleOutShader>(viewMatrix, color);
|
||||
auto shader = GrPathTessellationShader::MakeMiddleOutInstancedShader(arena, viewMatrix, color);
|
||||
return arena->make<GrPathIndirectTessellator>(shader, path, drawInnerFan);
|
||||
}
|
||||
|
||||
@ -119,6 +119,17 @@ static int write_breadcrumb_triangles(
|
||||
return numWritten;
|
||||
}
|
||||
|
||||
// How many vertices do we need to draw in order to triangulate a curve with 2^resolveLevel line
|
||||
// segments?
|
||||
constexpr static int num_vertices_at_resolve_level(int resolveLevel) {
|
||||
// resolveLevel=0 -> 0 line segments -> 0 triangles -> 0 vertices
|
||||
// resolveLevel=1 -> 2 line segments -> 1 triangle -> 3 vertices
|
||||
// resolveLevel=2 -> 4 line segments -> 3 triangles -> 9 vertices
|
||||
// resolveLevel=3 -> 8 line segments -> 7 triangles -> 21 vertices
|
||||
// ...
|
||||
return ((1 << resolveLevel) - 1) * 3;
|
||||
}
|
||||
|
||||
void GrPathIndirectTessellator::prepare(GrMeshDrawOp::Target* target, const SkRect& /*cullBounds*/,
|
||||
const SkPath& path,
|
||||
const BreadcrumbTriangleList* breadcrumbTriangleList) {
|
||||
@ -186,9 +197,11 @@ void GrPathIndirectTessellator::prepare(GrMeshDrawOp::Target* target, const SkRe
|
||||
}
|
||||
instanceLocations[resolveLevel] = instanceWriter.makeOffset(0);
|
||||
SkASSERT(fIndirectDrawCount < indirectLockCnt);
|
||||
GrCurveMiddleOutShader::WriteDrawIndirectCmd(&indirectWriter, resolveLevel,
|
||||
instanceCountAtCurrLevel + numExtraInstances,
|
||||
currentBaseInstance);
|
||||
// The vertex shader determines the T value at which to draw each vertex. Since the
|
||||
// triangles are arranged in "middle-out" order, we can conveniently control the
|
||||
// resolveLevel by changing only the vertexCount.
|
||||
indirectWriter.write(instanceCountAtCurrLevel + numExtraInstances, currentBaseInstance,
|
||||
num_vertices_at_resolve_level(resolveLevel), 0);
|
||||
++fIndirectDrawCount;
|
||||
currentBaseInstance += instanceCountAtCurrLevel + numExtraInstances;
|
||||
instanceWriter = instanceWriter.makeOffset(instanceCountAtCurrLevel * 4 * sizeof(SkPoint));
|
||||
@ -283,7 +296,7 @@ GrPathTessellator* GrPathOuterCurveTessellator::Make(SkArenaAlloc* arena,
|
||||
const SkMatrix& viewMatrix,
|
||||
const SkPMColor4f& color,
|
||||
DrawInnerFan drawInnerFan) {
|
||||
auto shader = arena->make<GrCurveTessellateShader>(viewMatrix, color);
|
||||
auto shader = GrPathTessellationShader::MakeHardwareCurveShader(arena, viewMatrix, color);
|
||||
return arena->make<GrPathOuterCurveTessellator>(shader, drawInnerFan);
|
||||
}
|
||||
|
||||
@ -451,7 +464,7 @@ void GrPathOuterCurveTessellator::prepare(GrMeshDrawOp::Target* target, const Sk
|
||||
|
||||
GrPathTessellator* GrPathWedgeTessellator::Make(SkArenaAlloc* arena, const SkMatrix& viewMatrix,
|
||||
const SkPMColor4f& color) {
|
||||
auto shader = arena->make<GrWedgeTessellateShader>(viewMatrix, color);
|
||||
auto shader = GrPathTessellationShader::MakeHardwareWedgeShader(arena, viewMatrix, color);
|
||||
return arena->make<GrPathWedgeTessellator>(shader);
|
||||
}
|
||||
|
||||
|
@ -7,12 +7,79 @@
|
||||
|
||||
#include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
|
||||
|
||||
#include "src/gpu/geometry/GrWangsFormula.h"
|
||||
#include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
|
||||
#include "src/gpu/glsl/GrGLSLProgramBuilder.h"
|
||||
#include "src/gpu/glsl/GrGLSLVarying.h"
|
||||
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Draws a simple array of triangles.
|
||||
class SimpleTriangleShader : public GrPathTessellationShader {
|
||||
public:
|
||||
SimpleTriangleShader(const SkMatrix& viewMatrix, SkPMColor4f color)
|
||||
: GrPathTessellationShader(kTessellate_SimpleTriangleShader_ClassID,
|
||||
GrPrimitiveType::kTriangles, 0, viewMatrix, color) {
|
||||
constexpr static Attribute kInputPointAttrib{"inputPoint", kFloat2_GrVertexAttribType,
|
||||
kFloat2_GrSLType};
|
||||
this->setVertexAttributes(&kInputPointAttrib, 1);
|
||||
}
|
||||
|
||||
private:
|
||||
const char* name() const final { return "tessellate_SimpleTriangleShader"; }
|
||||
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const final {}
|
||||
GrGLSLGeometryProcessor* createGLSLInstance(const GrShaderCaps&) const final;
|
||||
};
|
||||
|
||||
GrGLSLGeometryProcessor* SimpleTriangleShader::createGLSLInstance(const GrShaderCaps&) const {
|
||||
class Impl : public GrPathTessellationShader::Impl {
|
||||
void emitVertexCode(GrGLSLVertexBuilder* v, GrGPArgs* gpArgs) override {
|
||||
v->codeAppend(R"(
|
||||
float2 localcoord = inputPoint;
|
||||
float2 vertexpos = AFFINE_MATRIX * localcoord + TRANSLATE;)");
|
||||
gpArgs->fLocalCoordVar.set(kFloat2_GrSLType, "localcoord");
|
||||
gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertexpos");
|
||||
}
|
||||
};
|
||||
return new Impl;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GrPathTessellationShader* GrPathTessellationShader::MakeSimpleTriangleShader(
|
||||
SkArenaAlloc* arena, const SkMatrix& viewMatrix, const SkPMColor4f& color) {
|
||||
return arena->make<SimpleTriangleShader>(viewMatrix, color);
|
||||
}
|
||||
|
||||
// Converts a 4-point input patch into the rational cubic it intended to represent.
|
||||
const char* GrPathTessellationShader::Impl::kUnpackRationalCubicFn = R"(
|
||||
float4x3 unpack_rational_cubic(float2 p0, float2 p1, float2 p2, float2 p3) {
|
||||
float4x3 P = float4x3(p0,1, p1,1, p2,1, p3,1);
|
||||
if (isinf(P[3].y)) {
|
||||
// This patch is actually a conic. Convert to a rational cubic.
|
||||
float w = P[3].x;
|
||||
float3 c = P[1] * ((2.0/3.0) * w);
|
||||
P = float4x3(P[0], fma(P[0], float3(1.0/3.0), c), fma(P[2], float3(1.0/3.0), c), P[2]);
|
||||
}
|
||||
return P;
|
||||
})";
|
||||
|
||||
// Evaluate our point of interest using numerically stable linear interpolations. We add our own
|
||||
// "safe_mix" method to guarantee we get exactly "b" when T=1. The builtin mix() function seems
|
||||
// spec'd to behave this way, but empirical results results have shown it does not always.
|
||||
const char* GrPathTessellationShader::Impl::kEvalRationalCubicFn = R"(
|
||||
float3 safe_mix(float3 a, float3 b, float T, float one_minus_T) {
|
||||
return a*one_minus_T + b*T;
|
||||
}
|
||||
float2 eval_rational_cubic(float4x3 P, float T) {
|
||||
float one_minus_T = 1.0 - T;
|
||||
float3 ab = safe_mix(P[0], P[1], T, one_minus_T);
|
||||
float3 bc = safe_mix(P[1], P[2], T, one_minus_T);
|
||||
float3 cd = safe_mix(P[2], P[3], T, one_minus_T);
|
||||
float3 abc = safe_mix(ab, bc, T, one_minus_T);
|
||||
float3 bcd = safe_mix(bc, cd, T, one_minus_T);
|
||||
float3 abcd = safe_mix(abc, bcd, T, one_minus_T);
|
||||
return abcd.xy / abcd.z;
|
||||
})";
|
||||
|
||||
void GrPathTessellationShader::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
|
||||
args.fVaryingHandler->emitAttributes(args.fGeomProc);
|
||||
|
||||
@ -45,347 +112,3 @@ void GrPathTessellationShader::Impl::setData(const GrGLSLProgramDataManager& pdm
|
||||
const SkPMColor4f& color = shader.color();
|
||||
pdman.set4f(fColorUniform, color.fR, color.fG, color.fB, color.fA);
|
||||
}
|
||||
|
||||
constexpr static char kSkSLTypeDefs[] = R"(
|
||||
#define float4x3 mat4x3
|
||||
#define float2 vec2
|
||||
#define float3 vec3
|
||||
#define float4 vec4
|
||||
)";
|
||||
|
||||
// Converts a 4-point input patch into the rational cubic it intended to represent.
|
||||
constexpr static char kUnpackRationalCubicFn[] = R"(
|
||||
float4x3 unpack_rational_cubic(float2 p0, float2 p1, float2 p2, float2 p3) {
|
||||
float4x3 P = float4x3(p0,1, p1,1, p2,1, p3,1);
|
||||
if (isinf(P[3].y)) {
|
||||
// This patch is actually a conic. Convert to a rational cubic.
|
||||
float w = P[3].x;
|
||||
float3 c = P[1] * ((2.0/3.0) * w);
|
||||
P = float4x3(P[0], fma(P[0], float3(1.0/3.0), c), fma(P[2], float3(1.0/3.0), c), P[2]);
|
||||
}
|
||||
return P;
|
||||
})";
|
||||
|
||||
// Evaluate our point of interest using numerically stable linear interpolations. We add our own
|
||||
// "safe_mix" method to guarantee we get exactly "b" when T=1. The builtin mix() function seems
|
||||
// spec'd to behave this way, but empirical results results have shown it does not always.
|
||||
constexpr static char kEvalRationalCubicFn[] = R"(
|
||||
float3 safe_mix(float3 a, float3 b, float T, float one_minus_T) {
|
||||
return a*one_minus_T + b*T;
|
||||
}
|
||||
float2 eval_rational_cubic(float4x3 P, float T) {
|
||||
float one_minus_T = 1.0 - T;
|
||||
float3 ab = safe_mix(P[0], P[1], T, one_minus_T);
|
||||
float3 bc = safe_mix(P[1], P[2], T, one_minus_T);
|
||||
float3 cd = safe_mix(P[2], P[3], T, one_minus_T);
|
||||
float3 abc = safe_mix(ab, bc, T, one_minus_T);
|
||||
float3 bcd = safe_mix(bc, cd, T, one_minus_T);
|
||||
float3 abcd = safe_mix(abc, bcd, T, one_minus_T);
|
||||
return abcd.xy / abcd.z;
|
||||
})";
|
||||
|
||||
GrGLSLGeometryProcessor* GrTriangleShader::createGLSLInstance(const GrShaderCaps&) const {
|
||||
class Impl : public GrPathTessellationShader::Impl {
|
||||
void emitVertexCode(GrGLSLVertexBuilder* v, GrGPArgs* gpArgs) override {
|
||||
v->codeAppend(R"(
|
||||
float2 localcoord = inputPoint;
|
||||
float2 vertexpos = AFFINE_MATRIX * localcoord + TRANSLATE;)");
|
||||
gpArgs->fLocalCoordVar.set(kFloat2_GrSLType, "localcoord");
|
||||
gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertexpos");
|
||||
}
|
||||
};
|
||||
return new Impl;
|
||||
}
|
||||
|
||||
GrGLSLGeometryProcessor* GrCurveTessellateShader::createGLSLInstance(const GrShaderCaps&) const {
|
||||
class Impl : public GrPathTessellationShader::Impl {
|
||||
void emitVertexCode(GrGLSLVertexBuilder* v, GrGPArgs*) override {
|
||||
v->declareGlobal(GrShaderVar("P", kFloat2_GrSLType, GrShaderVar::TypeModifier::Out));
|
||||
v->codeAppend(R"(
|
||||
// If y is infinity then x is a conic weight. Don't transform.
|
||||
P = (isinf(inputPoint.y)) ? inputPoint : AFFINE_MATRIX * inputPoint + TRANSLATE;)");
|
||||
}
|
||||
SkString getTessControlShaderGLSL(const GrGeometryProcessor&,
|
||||
const char* versionAndExtensionDecls,
|
||||
const GrGLSLUniformHandler&,
|
||||
const GrShaderCaps& shaderCaps) const override {
|
||||
SkString code(versionAndExtensionDecls);
|
||||
code.appendf(R"(
|
||||
#define MAX_TESSELLATION_SEGMENTS %i)", shaderCaps.maxTessellationSegments());
|
||||
code.appendf(R"(
|
||||
#define PRECISION %f)", GrTessellationPathRenderer::kLinearizationPrecision);
|
||||
code.append(kSkSLTypeDefs);
|
||||
code.append(GrWangsFormula::as_sksl());
|
||||
code.append(kUnpackRationalCubicFn);
|
||||
code.append(R"(
|
||||
layout(vertices = 1) out;
|
||||
|
||||
in vec2 P[];
|
||||
patch out mat4x2 rationalCubicXY;
|
||||
patch out float rationalCubicW;
|
||||
|
||||
void main() {
|
||||
float w = -1; // w<0 means a cubic.
|
||||
vec2 p1w = P[1];
|
||||
if (isinf(P[3].y)) {
|
||||
// This patch is actually a conic. Project to homogeneous space.
|
||||
w = P[3].x;
|
||||
p1w *= w;
|
||||
}
|
||||
|
||||
// Chop the curve at T=1/2.
|
||||
vec2 ab = (P[0] + p1w) * .5;
|
||||
vec2 bc = (p1w + P[2]) * .5;
|
||||
vec2 cd = (P[2] + P[3]) * .5;
|
||||
vec2 abc = (ab + bc) * .5;
|
||||
vec2 bcd = (bc + cd) * .5;
|
||||
vec2 abcd = (abc + bcd) * .5;
|
||||
|
||||
float n0, n1;
|
||||
if (w < 0 || isinf(w)) {
|
||||
if (w < 0) {
|
||||
// The patch is a cubic. Calculate how many segments are required to
|
||||
// linearize each half of the curve.
|
||||
n0 = wangs_formula(PRECISION, P[0], ab, abc, abcd, -1); // w<0 means cubic.
|
||||
n1 = wangs_formula(PRECISION, abcd, bcd, cd, P[3], -1);
|
||||
rationalCubicW = 1;
|
||||
} else {
|
||||
// The patch is a triangle (a conic with infinite weight).
|
||||
n0 = n1 = 1;
|
||||
rationalCubicW = -1; // In the next stage, rationalCubicW<0 means triangle.
|
||||
}
|
||||
rationalCubicXY = mat4x2(P[0], P[1], P[2], P[3]);
|
||||
} else {
|
||||
// The patch is a conic. Unproject p0..5. w1 == w2 == w3 when chopping at .5.
|
||||
// (See SkConic::chopAt().)
|
||||
float r = 2.0 / (1.0 + w);
|
||||
ab *= r, bc *= r, abc *= r;
|
||||
// Put in "standard form" where w0 == w2 == w4 == 1.
|
||||
float w_ = inversesqrt(r); // Both halves have the same w' when chopping at .5.
|
||||
// Calculate how many segments are needed to linearize each half of the curve.
|
||||
n0 = wangs_formula(PRECISION, P[0], ab, abc, float2(0), w_);
|
||||
n1 = wangs_formula(PRECISION, abc, bc, P[2], float2(0), w_);
|
||||
// Covert the conic to a rational cubic in projected form.
|
||||
rationalCubicXY = mat4x2(P[0],
|
||||
mix(float4(P[0],P[2]), p1w.xyxy, 2.0/3.0),
|
||||
P[2]);
|
||||
rationalCubicW = fma(w, 2.0/3.0, 1.0/3.0);
|
||||
}
|
||||
|
||||
gl_TessLevelOuter[0] = min(n1, MAX_TESSELLATION_SEGMENTS);
|
||||
gl_TessLevelOuter[1] = 1.0;
|
||||
gl_TessLevelOuter[2] = min(n0, MAX_TESSELLATION_SEGMENTS);
|
||||
|
||||
// Changing the inner level to 1 when n0 == n1 == 1 collapses the entire patch to a
|
||||
// single triangle. Otherwise, we need an inner level of 2 so our curve triangles
|
||||
// have an interior point to originate from.
|
||||
gl_TessLevelInner[0] = min(max(n0, n1), 2.0);
|
||||
})");
|
||||
|
||||
return code;
|
||||
}
|
||||
SkString getTessEvaluationShaderGLSL(const GrGeometryProcessor&,
|
||||
const char* versionAndExtensionDecls,
|
||||
const GrGLSLUniformHandler&,
|
||||
const GrShaderCaps&) const override {
|
||||
SkString code(versionAndExtensionDecls);
|
||||
code.append(kSkSLTypeDefs);
|
||||
code.append(kEvalRationalCubicFn);
|
||||
code.append(R"(
|
||||
layout(triangles, equal_spacing, ccw) in;
|
||||
|
||||
uniform vec4 sk_RTAdjust;
|
||||
|
||||
patch in mat4x2 rationalCubicXY;
|
||||
patch in float rationalCubicW;
|
||||
|
||||
void main() {
|
||||
vec2 vertexpos;
|
||||
if (rationalCubicW < 0) { // rationalCubicW < 0 means a triangle now.
|
||||
vertexpos = (gl_TessCoord.x != 0) ? rationalCubicXY[0]
|
||||
: (gl_TessCoord.y != 0) ? rationalCubicXY[1]
|
||||
: rationalCubicXY[2];
|
||||
} else {
|
||||
// Locate our parametric point of interest. T ramps from [0..1/2] on the left
|
||||
// edge of the triangle, and [1/2..1] on the right. If we are the patch's
|
||||
// interior vertex, then we want T=1/2. Since the barycentric coords are
|
||||
// (1/3, 1/3, 1/3) at the interior vertex, the below fma() works in all 3
|
||||
// scenarios.
|
||||
float T = fma(.5, gl_TessCoord.y, gl_TessCoord.z);
|
||||
|
||||
mat4x3 P = mat4x3(rationalCubicXY[0], 1,
|
||||
rationalCubicXY[1], rationalCubicW,
|
||||
rationalCubicXY[2], rationalCubicW,
|
||||
rationalCubicXY[3], 1);
|
||||
vertexpos = eval_rational_cubic(P, T);
|
||||
if (all(notEqual(gl_TessCoord.xz, vec2(0)))) {
|
||||
// We are the interior point of the patch; center it inside
|
||||
// [C(0), C(.5), C(1)].
|
||||
vertexpos = (P[0].xy + vertexpos + P[3].xy) / 3.0;
|
||||
}
|
||||
}
|
||||
|
||||
gl_Position = vec4(vertexpos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0);
|
||||
})");
|
||||
|
||||
return code;
|
||||
}
|
||||
};
|
||||
return new Impl;
|
||||
}
|
||||
|
||||
GrGLSLGeometryProcessor* GrWedgeTessellateShader::createGLSLInstance(const GrShaderCaps&) const {
|
||||
class Impl : public GrPathTessellationShader::Impl {
|
||||
void emitVertexCode(GrGLSLVertexBuilder* v, GrGPArgs*) override {
|
||||
v->declareGlobal(GrShaderVar("P", kFloat2_GrSLType, GrShaderVar::TypeModifier::Out));
|
||||
v->codeAppend(R"(
|
||||
// If y is infinity then x is a conic weight. Don't transform.
|
||||
P = (isinf(inputPoint.y)) ? inputPoint : AFFINE_MATRIX * inputPoint + TRANSLATE;)");
|
||||
}
|
||||
SkString getTessControlShaderGLSL(const GrGeometryProcessor&,
|
||||
const char* versionAndExtensionDecls,
|
||||
const GrGLSLUniformHandler&,
|
||||
const GrShaderCaps& shaderCaps) const override {
|
||||
SkString code(versionAndExtensionDecls);
|
||||
code.appendf(R"(
|
||||
#define MAX_TESSELLATION_SEGMENTS %i)", shaderCaps.maxTessellationSegments());
|
||||
code.appendf(R"(
|
||||
#define PRECISION %f)", GrTessellationPathRenderer::kLinearizationPrecision);
|
||||
code.append(kSkSLTypeDefs);
|
||||
code.append(GrWangsFormula::as_sksl());
|
||||
code.append(kUnpackRationalCubicFn);
|
||||
code.append(R"(
|
||||
layout(vertices = 1) out;
|
||||
|
||||
in vec2 P[];
|
||||
patch out mat4x2 rationalCubicXY;
|
||||
patch out float rationalCubicW;
|
||||
patch out vec2 fanpoint;
|
||||
|
||||
void main() {
|
||||
// Figure out how many segments to divide the curve into.
|
||||
float w = isinf(P[3].y) ? P[3].x : -1; // w<0 means cubic.
|
||||
float n = wangs_formula(PRECISION, P[0], P[1], P[2], P[3], w);
|
||||
|
||||
// Tessellate the first side of the patch into n triangles.
|
||||
gl_TessLevelOuter[0] = min(n, MAX_TESSELLATION_SEGMENTS);
|
||||
|
||||
// Leave the other two sides of the patch as single segments.
|
||||
gl_TessLevelOuter[1] = 1.0;
|
||||
gl_TessLevelOuter[2] = 1.0;
|
||||
|
||||
// Changing the inner level to 1 when n == 1 collapses the entire
|
||||
// patch to a single triangle. Otherwise, we need an inner level of 2 so our curve
|
||||
// triangles have an interior point to originate from.
|
||||
gl_TessLevelInner[0] = min(n, 2.0);
|
||||
|
||||
if (w < 0) {
|
||||
rationalCubicXY = mat4x2(P[0], P[1], P[2], P[3]);
|
||||
rationalCubicW = 1;
|
||||
} else {
|
||||
// Convert the conic to a rational cubic in projected form.
|
||||
rationalCubicXY = mat4x2(P[0],
|
||||
mix(vec4(P[0], P[2]), (P[1] * w).xyxy, 2.0/3.0),
|
||||
P[2]);
|
||||
rationalCubicW = fma(w, 2.0/3.0, 1.0/3.0);
|
||||
}
|
||||
fanpoint = P[4];
|
||||
})");
|
||||
|
||||
return code;
|
||||
}
|
||||
SkString getTessEvaluationShaderGLSL(const GrGeometryProcessor&,
|
||||
const char* versionAndExtensionDecls,
|
||||
const GrGLSLUniformHandler&,
|
||||
const GrShaderCaps&) const override {
|
||||
SkString code(versionAndExtensionDecls);
|
||||
code.append(kSkSLTypeDefs);
|
||||
code.append(kEvalRationalCubicFn);
|
||||
code.append(R"(
|
||||
layout(triangles, equal_spacing, ccw) in;
|
||||
|
||||
uniform vec4 sk_RTAdjust;
|
||||
|
||||
patch in mat4x2 rationalCubicXY;
|
||||
patch in float rationalCubicW;
|
||||
patch in vec2 fanpoint[];
|
||||
|
||||
void main() {
|
||||
// Locate our parametric point of interest. It is equal to the barycentric
|
||||
// y-coordinate if we are a vertex on the tessellated edge of the triangle patch,
|
||||
// 0.5 if we are the patch's interior vertex, or N/A if we are the fan point.
|
||||
// NOTE: We are on the tessellated edge when the barycentric x-coordinate == 0.
|
||||
float T = (gl_TessCoord.x == 0.0) ? gl_TessCoord.y : 0.5;
|
||||
|
||||
mat4x3 P = mat4x3(rationalCubicXY[0], 1,
|
||||
rationalCubicXY[1], rationalCubicW,
|
||||
rationalCubicXY[2], rationalCubicW,
|
||||
rationalCubicXY[3], 1);
|
||||
vec2 vertexpos = eval_rational_cubic(P, T);
|
||||
|
||||
if (gl_TessCoord.x == 1.0) {
|
||||
// We are the anchor point that fans from the center of the curve's contour.
|
||||
vertexpos = fanpoint[0];
|
||||
} else if (gl_TessCoord.x != 0.0) {
|
||||
// We are the interior point of the patch; center it inside [C(0), C(.5), C(1)].
|
||||
vertexpos = (P[0].xy + vertexpos + P[3].xy) / 3.0;
|
||||
}
|
||||
|
||||
gl_Position = vec4(vertexpos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0);
|
||||
})");
|
||||
|
||||
return code;
|
||||
}
|
||||
};
|
||||
return new Impl;
|
||||
}
|
||||
|
||||
class GrCurveMiddleOutShader::Impl : public GrPathTessellationShader::Impl {
|
||||
void emitVertexCode(GrGLSLVertexBuilder* v, GrGPArgs* gpArgs) override {
|
||||
v->insertFunction(kUnpackRationalCubicFn);
|
||||
v->insertFunction(kEvalRationalCubicFn);
|
||||
if (v->getProgramBuilder()->shaderCaps()->bitManipulationSupport()) {
|
||||
// Determines the T value at which to place the given vertex in a "middle-out" topology.
|
||||
v->insertFunction(R"(
|
||||
float find_middle_out_T() {
|
||||
int totalTriangleIdx = sk_VertexID/3 + 1;
|
||||
int depth = findMSB(totalTriangleIdx);
|
||||
int firstTriangleAtDepth = (1 << depth);
|
||||
int triangleIdxWithinDepth = totalTriangleIdx - firstTriangleAtDepth;
|
||||
int vertexIdxWithinDepth = triangleIdxWithinDepth * 2 + sk_VertexID % 3;
|
||||
return ldexp(float(vertexIdxWithinDepth), -1 - depth);
|
||||
})");
|
||||
} else {
|
||||
// Determines the T value at which to place the given vertex in a "middle-out" topology.
|
||||
v->insertFunction(R"(
|
||||
float find_middle_out_T() {
|
||||
float totalTriangleIdx = float(sk_VertexID/3) + 1;
|
||||
float depth = floor(log2(totalTriangleIdx));
|
||||
float firstTriangleAtDepth = exp2(depth);
|
||||
float triangleIdxWithinDepth = totalTriangleIdx - firstTriangleAtDepth;
|
||||
float vertexIdxWithinDepth = triangleIdxWithinDepth * 2 + float(sk_VertexID % 3);
|
||||
return vertexIdxWithinDepth * exp2(-1 - depth);
|
||||
})");
|
||||
}
|
||||
v->codeAppend(R"(
|
||||
float2 localcoord;
|
||||
if (isinf(inputPoints_2_3.z)) {
|
||||
// A conic with w=Inf is an exact triangle.
|
||||
localcoord = (sk_VertexID < 1) ? inputPoints_0_1.xy
|
||||
: (sk_VertexID == 1) ? inputPoints_0_1.zw
|
||||
: inputPoints_2_3.xy;
|
||||
} else {
|
||||
float4x3 P = unpack_rational_cubic(inputPoints_0_1.xy, inputPoints_0_1.zw,
|
||||
inputPoints_2_3.xy, inputPoints_2_3.zw);
|
||||
float T = find_middle_out_T();
|
||||
localcoord = eval_rational_cubic(P, T);
|
||||
}
|
||||
float2 vertexpos = AFFINE_MATRIX * localcoord + TRANSLATE;)");
|
||||
gpArgs->fLocalCoordVar.set(kFloat2_GrSLType, "localcoord");
|
||||
gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertexpos");
|
||||
}
|
||||
};
|
||||
|
||||
GrGLSLGeometryProcessor* GrCurveMiddleOutShader::createGLSLInstance(const GrShaderCaps&) const {
|
||||
return new Impl;
|
||||
}
|
||||
|
@ -15,12 +15,37 @@
|
||||
// This is the base class for shaders in the GPU tessellator that fill paths.
|
||||
class GrPathTessellationShader : public GrTessellationShader {
|
||||
public:
|
||||
GrPathTessellationShader(ClassID classID, GrPrimitiveType primitiveType,
|
||||
int tessellationPatchVertexCount, const SkMatrix& viewMatrix,
|
||||
const SkPMColor4f& color)
|
||||
: GrTessellationShader(classID, primitiveType, tessellationPatchVertexCount, viewMatrix,
|
||||
color) {
|
||||
}
|
||||
// Draws a simple array of triangles.
|
||||
static GrPathTessellationShader* MakeSimpleTriangleShader(SkArenaAlloc*,
|
||||
const SkMatrix& viewMatrix,
|
||||
const SkPMColor4f&);
|
||||
|
||||
// Uses instanced draws to triangulate standalone closed curves with a "middle-out" topology.
|
||||
// Middle-out draws a triangle with vertices at T=[0, 1/2, 1] and then recurses breadth first:
|
||||
//
|
||||
// depth=0: T=[0, 1/2, 1]
|
||||
// depth=1: T=[0, 1/4, 2/4], T=[2/4, 3/4, 1]
|
||||
// depth=2: T=[0, 1/8, 2/8], T=[2/8, 3/8, 4/8], T=[4/8, 5/8, 6/8], T=[6/8, 7/8, 1]
|
||||
// ...
|
||||
//
|
||||
// The caller may compute each cubic's resolveLevel on the CPU (i.e., the log2 number of line
|
||||
// segments it will be divided into; see GrWangsFormula::cubic_log2/quadratic_log2/conic_log2),
|
||||
// and then sort the instance buffer by resolveLevel for efficient batching of indirect draws.
|
||||
static GrPathTessellationShader* MakeMiddleOutInstancedShader(SkArenaAlloc*,
|
||||
const SkMatrix& viewMatrix,
|
||||
const SkPMColor4f&);
|
||||
|
||||
// Uses GPU tessellation shaders to linearize, triangulate, and render cubic "wedge" patches. A
|
||||
// wedge is a 5-point patch consisting of 4 cubic control points, plus an anchor point fanning
|
||||
// from the center of the curve's resident contour.
|
||||
static GrPathTessellationShader* MakeHardwareWedgeShader(SkArenaAlloc*,
|
||||
const SkMatrix& viewMatrix,
|
||||
const SkPMColor4f&);
|
||||
|
||||
// Uses GPU tessellation shaders to linearize, triangulate, and render standalone closed cubics.
|
||||
static GrPathTessellationShader* MakeHardwareCurveShader(SkArenaAlloc*,
|
||||
const SkMatrix& viewMatrix,
|
||||
const SkPMColor4f&);
|
||||
|
||||
// Returns the stencil settings to use for a standard Redbook "stencil" pass.
|
||||
static const GrUserStencilSettings* StencilPathSettings(SkPathFillType fillType) {
|
||||
@ -84,6 +109,13 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
GrPathTessellationShader(ClassID classID, GrPrimitiveType primitiveType,
|
||||
int tessellationPatchVertexCount, const SkMatrix& viewMatrix,
|
||||
const SkPMColor4f& color)
|
||||
: GrTessellationShader(classID, primitiveType, tessellationPatchVertexCount, viewMatrix,
|
||||
color) {
|
||||
}
|
||||
|
||||
// Default path tessellation shader implementation that manages a uniform matrix and color.
|
||||
class Impl : public GrGLSLGeometryProcessor {
|
||||
public:
|
||||
@ -92,6 +124,19 @@ protected:
|
||||
const GrGeometryProcessor&) override;
|
||||
|
||||
protected:
|
||||
// float2 eval_rational_cubic(float4x3 P, float T) { ...
|
||||
//
|
||||
// Converts a 4-point input patch into the rational cubic it intended to represent.
|
||||
static const char* kUnpackRationalCubicFn;
|
||||
|
||||
// float4x3 unpack_rational_cubic(float2 p0, float2 p1, float2 p2, float2 p3) { ...
|
||||
//
|
||||
// Evaluate our point of interest using numerically stable linear interpolations. We add our
|
||||
// own "safe_mix" method to guarantee we get exactly "b" when T=1. The builtin mix()
|
||||
// function seems spec'd to behave this way, but empirical results results have shown it
|
||||
// does not always.
|
||||
static const char* kEvalRationalCubicFn;
|
||||
|
||||
virtual void emitVertexCode(GrGLSLVertexBuilder*, GrGPArgs*) = 0;
|
||||
|
||||
GrGLSLUniformHandler::UniformHandle fAffineMatrixUniform;
|
||||
@ -100,112 +145,4 @@ protected:
|
||||
};
|
||||
};
|
||||
|
||||
// Draws a simple array of triangles.
|
||||
class GrTriangleShader : public GrPathTessellationShader {
|
||||
public:
|
||||
GrTriangleShader(const SkMatrix& viewMatrix, SkPMColor4f color)
|
||||
: GrPathTessellationShader(kTessellate_GrTriangleShader_ClassID,
|
||||
GrPrimitiveType::kTriangles, 0, viewMatrix, color) {
|
||||
constexpr static Attribute kInputPointAttrib{"inputPoint", kFloat2_GrVertexAttribType,
|
||||
kFloat2_GrSLType};
|
||||
this->setVertexAttributes(&kInputPointAttrib, 1);
|
||||
}
|
||||
|
||||
private:
|
||||
const char* name() const final { return "tessellate_GrTriangleShader"; }
|
||||
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const final {}
|
||||
GrGLSLGeometryProcessor* createGLSLInstance(const GrShaderCaps&) const final;
|
||||
};
|
||||
|
||||
// Uses GPU tessellation shaders to linearize, triangulate, and render standalone closed cubics.
|
||||
// TODO: Eventually we want to use rational cubic wedges in order to support perspective and conics.
|
||||
class GrCurveTessellateShader : public GrPathTessellationShader {
|
||||
public:
|
||||
GrCurveTessellateShader(const SkMatrix& viewMatrix, const SkPMColor4f& color)
|
||||
: GrPathTessellationShader(kTessellate_GrCurveTessellateShader_ClassID,
|
||||
GrPrimitiveType::kPatches, 4, viewMatrix, color) {
|
||||
constexpr static Attribute kInputPointAttrib{"inputPoint", kFloat2_GrVertexAttribType,
|
||||
kFloat2_GrSLType};
|
||||
this->setVertexAttributes(&kInputPointAttrib, 1);
|
||||
}
|
||||
|
||||
private:
|
||||
const char* name() const final { return "tessellate_GrCurveTessellateShader"; }
|
||||
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final {}
|
||||
GrGLSLGeometryProcessor* createGLSLInstance(const GrShaderCaps&) const final;
|
||||
};
|
||||
|
||||
// Uses GPU tessellation shaders to linearize, triangulate, and render cubic "wedge" patches. A
|
||||
// wedge is a 5-point patch consisting of 4 cubic control points, plus an anchor point fanning from
|
||||
// the center of the curve's resident contour.
|
||||
// TODO: Eventually we want to use rational cubic wedges in order to support perspective and conics.
|
||||
class GrWedgeTessellateShader : public GrPathTessellationShader {
|
||||
public:
|
||||
GrWedgeTessellateShader(const SkMatrix& viewMatrix, const SkPMColor4f& color)
|
||||
: GrPathTessellationShader(kTessellate_GrWedgeTessellateShader_ClassID,
|
||||
GrPrimitiveType::kPatches, 5, viewMatrix, color) {
|
||||
constexpr static Attribute kInputPointAttrib{"inputPoint", kFloat2_GrVertexAttribType,
|
||||
kFloat2_GrSLType};
|
||||
this->setVertexAttributes(&kInputPointAttrib, 1);
|
||||
}
|
||||
|
||||
private:
|
||||
const char* name() const final { return "tessellate_GrWedgeTessellateShader"; }
|
||||
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final {}
|
||||
GrGLSLGeometryProcessor* createGLSLInstance(const GrShaderCaps&) const final;
|
||||
};
|
||||
|
||||
// Uses instanced draws to triangulate standalone closed curves with a "middle-out" topology.
|
||||
// Middle-out draws a triangle with vertices at T=[0, 1/2, 1] and then recurses breadth first:
|
||||
//
|
||||
// depth=0: T=[0, 1/2, 1]
|
||||
// depth=1: T=[0, 1/4, 2/4], T=[2/4, 3/4, 1]
|
||||
// depth=2: T=[0, 1/8, 2/8], T=[2/8, 3/8, 4/8], T=[4/8, 5/8, 6/8], T=[6/8, 7/8, 1]
|
||||
// ...
|
||||
//
|
||||
// The caller may compute each cubic's resolveLevel on the CPU (i.e., the log2 number of line
|
||||
// segments it will be divided into; see GrWangsFormula::cubic_log2/quadratic_log2/conic_log2), and
|
||||
// then sort the instance buffer by resolveLevel for efficient batching of indirect draws.
|
||||
class GrCurveMiddleOutShader : public GrPathTessellationShader {
|
||||
public:
|
||||
// How many vertices do we need to draw in order to triangulate a cubic with 2^resolveLevel
|
||||
// line segments?
|
||||
constexpr static int NumVerticesAtResolveLevel(int resolveLevel) {
|
||||
// resolveLevel=0 -> 0 line segments -> 0 triangles -> 0 vertices
|
||||
// resolveLevel=1 -> 2 line segments -> 1 triangle -> 3 vertices
|
||||
// resolveLevel=2 -> 4 line segments -> 3 triangles -> 9 vertices
|
||||
// resolveLevel=3 -> 8 line segments -> 7 triangles -> 21 vertices
|
||||
// ...
|
||||
return ((1 << resolveLevel) - 1) * 3;
|
||||
}
|
||||
|
||||
// Configures an indirect draw to render cubic instances with 2^resolveLevel evenly-spaced (in
|
||||
// the parametric sense) line segments.
|
||||
static void WriteDrawIndirectCmd(GrDrawIndirectWriter* indirectWriter, int resolveLevel,
|
||||
uint32_t instanceCount, uint32_t baseInstance) {
|
||||
SkASSERT(resolveLevel > 0 && resolveLevel <= GrTessellationPathRenderer::kMaxResolveLevel);
|
||||
// The vertex shader determines the T value at which to draw each vertex. Since the
|
||||
// triangles are arranged in "middle-out" order, we can conveniently control the
|
||||
// resolveLevel by changing only the vertexCount.
|
||||
uint32_t vertexCount = NumVerticesAtResolveLevel(resolveLevel);
|
||||
indirectWriter->write(instanceCount, baseInstance, vertexCount, 0);
|
||||
}
|
||||
|
||||
GrCurveMiddleOutShader(const SkMatrix& viewMatrix, const SkPMColor4f& color)
|
||||
: GrPathTessellationShader(kTessellate_GrCurveMiddleOutShader_ClassID,
|
||||
GrPrimitiveType::kTriangles, 0, viewMatrix, color) {
|
||||
constexpr static Attribute kInputPtsAttribs[] = {
|
||||
{"inputPoints_0_1", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
|
||||
{"inputPoints_2_3", kFloat4_GrVertexAttribType, kFloat4_GrSLType}};
|
||||
this->setInstanceAttributes(kInputPtsAttribs, 2);
|
||||
}
|
||||
|
||||
private:
|
||||
const char* name() const final { return "tessellate_GrCurveMiddleOutShader"; }
|
||||
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final {}
|
||||
GrGLSLGeometryProcessor* createGLSLInstance(const GrShaderCaps&) const final;
|
||||
|
||||
class Impl;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
319
src/gpu/tessellate/shaders/GrPathTessellationShader_Hardware.cpp
Normal file
319
src/gpu/tessellate/shaders/GrPathTessellationShader_Hardware.cpp
Normal file
@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Copyright 2019 Google LLC.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
|
||||
|
||||
#include "src/gpu/geometry/GrWangsFormula.h"
|
||||
#include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
|
||||
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
|
||||
|
||||
// Converts keywords from shared SkSL strings to native GLSL keywords.
|
||||
constexpr static char kSkSLTypeDefs[] = R"(
|
||||
#define float4x3 mat4x3
|
||||
#define float2 vec2
|
||||
#define float3 vec3
|
||||
#define float4 vec4
|
||||
)";
|
||||
|
||||
namespace {
|
||||
|
||||
// Uses GPU tessellation shaders to linearize, triangulate, and render cubic "wedge" patches. A
|
||||
// wedge is a 5-point patch consisting of 4 cubic control points, plus an anchor point fanning from
|
||||
// the center of the curve's resident contour.
|
||||
// TODO: Eventually we want to use rational cubic wedges in order to support perspective and conics.
|
||||
class HardwareWedgeShader : public GrPathTessellationShader {
|
||||
public:
|
||||
HardwareWedgeShader(const SkMatrix& viewMatrix, const SkPMColor4f& color)
|
||||
: GrPathTessellationShader(kTessellate_HardwareWedgeShader_ClassID,
|
||||
GrPrimitiveType::kPatches, 5, viewMatrix, color) {
|
||||
constexpr static Attribute kInputPointAttrib{"inputPoint", kFloat2_GrVertexAttribType,
|
||||
kFloat2_GrSLType};
|
||||
this->setVertexAttributes(&kInputPointAttrib, 1);
|
||||
}
|
||||
|
||||
private:
|
||||
const char* name() const final { return "tessellate_HardwareWedgeShader"; }
|
||||
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final {}
|
||||
GrGLSLGeometryProcessor* createGLSLInstance(const GrShaderCaps&) const final;
|
||||
};
|
||||
|
||||
GrGLSLGeometryProcessor* HardwareWedgeShader::createGLSLInstance(const GrShaderCaps&) const {
|
||||
class Impl : public GrPathTessellationShader::Impl {
|
||||
void emitVertexCode(GrGLSLVertexBuilder* v, GrGPArgs*) override {
|
||||
v->declareGlobal(GrShaderVar("P", kFloat2_GrSLType, GrShaderVar::TypeModifier::Out));
|
||||
v->codeAppend(R"(
|
||||
// If y is infinity then x is a conic weight. Don't transform.
|
||||
P = (isinf(inputPoint.y)) ? inputPoint : AFFINE_MATRIX * inputPoint + TRANSLATE;)");
|
||||
}
|
||||
SkString getTessControlShaderGLSL(const GrGeometryProcessor&,
|
||||
const char* versionAndExtensionDecls,
|
||||
const GrGLSLUniformHandler&,
|
||||
const GrShaderCaps& shaderCaps) const override {
|
||||
SkString code(versionAndExtensionDecls);
|
||||
code.appendf(R"(
|
||||
#define MAX_TESSELLATION_SEGMENTS %i)", shaderCaps.maxTessellationSegments());
|
||||
code.appendf(R"(
|
||||
#define PRECISION %f)", GrTessellationPathRenderer::kLinearizationPrecision);
|
||||
code.append(kSkSLTypeDefs);
|
||||
code.append(GrWangsFormula::as_sksl());
|
||||
code.append(kUnpackRationalCubicFn);
|
||||
code.append(R"(
|
||||
layout(vertices = 1) out;
|
||||
|
||||
in vec2 P[];
|
||||
patch out mat4x2 rationalCubicXY;
|
||||
patch out float rationalCubicW;
|
||||
patch out vec2 fanpoint;
|
||||
|
||||
void main() {
|
||||
// Figure out how many segments to divide the curve into.
|
||||
float w = isinf(P[3].y) ? P[3].x : -1; // w<0 means cubic.
|
||||
float n = wangs_formula(PRECISION, P[0], P[1], P[2], P[3], w);
|
||||
|
||||
// Tessellate the first side of the patch into n triangles.
|
||||
gl_TessLevelOuter[0] = min(n, MAX_TESSELLATION_SEGMENTS);
|
||||
|
||||
// Leave the other two sides of the patch as single segments.
|
||||
gl_TessLevelOuter[1] = 1.0;
|
||||
gl_TessLevelOuter[2] = 1.0;
|
||||
|
||||
// Changing the inner level to 1 when n == 1 collapses the entire
|
||||
// patch to a single triangle. Otherwise, we need an inner level of 2 so our curve
|
||||
// triangles have an interior point to originate from.
|
||||
gl_TessLevelInner[0] = min(n, 2.0);
|
||||
|
||||
if (w < 0) {
|
||||
rationalCubicXY = mat4x2(P[0], P[1], P[2], P[3]);
|
||||
rationalCubicW = 1;
|
||||
} else {
|
||||
// Convert the conic to a rational cubic in projected form.
|
||||
rationalCubicXY = mat4x2(P[0],
|
||||
mix(vec4(P[0], P[2]), (P[1] * w).xyxy, 2.0/3.0),
|
||||
P[2]);
|
||||
rationalCubicW = fma(w, 2.0/3.0, 1.0/3.0);
|
||||
}
|
||||
fanpoint = P[4];
|
||||
})");
|
||||
|
||||
return code;
|
||||
}
|
||||
SkString getTessEvaluationShaderGLSL(const GrGeometryProcessor&,
|
||||
const char* versionAndExtensionDecls,
|
||||
const GrGLSLUniformHandler&,
|
||||
const GrShaderCaps&) const override {
|
||||
SkString code(versionAndExtensionDecls);
|
||||
code.append(kSkSLTypeDefs);
|
||||
code.append(kEvalRationalCubicFn);
|
||||
code.append(R"(
|
||||
layout(triangles, equal_spacing, ccw) in;
|
||||
|
||||
uniform vec4 sk_RTAdjust;
|
||||
|
||||
patch in mat4x2 rationalCubicXY;
|
||||
patch in float rationalCubicW;
|
||||
patch in vec2 fanpoint[];
|
||||
|
||||
void main() {
|
||||
// Locate our parametric point of interest. It is equal to the barycentric
|
||||
// y-coordinate if we are a vertex on the tessellated edge of the triangle patch,
|
||||
// 0.5 if we are the patch's interior vertex, or N/A if we are the fan point.
|
||||
// NOTE: We are on the tessellated edge when the barycentric x-coordinate == 0.
|
||||
float T = (gl_TessCoord.x == 0.0) ? gl_TessCoord.y : 0.5;
|
||||
|
||||
mat4x3 P = mat4x3(rationalCubicXY[0], 1,
|
||||
rationalCubicXY[1], rationalCubicW,
|
||||
rationalCubicXY[2], rationalCubicW,
|
||||
rationalCubicXY[3], 1);
|
||||
vec2 vertexpos = eval_rational_cubic(P, T);
|
||||
|
||||
if (gl_TessCoord.x == 1.0) {
|
||||
// We are the anchor point that fans from the center of the curve's contour.
|
||||
vertexpos = fanpoint[0];
|
||||
} else if (gl_TessCoord.x != 0.0) {
|
||||
// We are the interior point of the patch; center it inside [C(0), C(.5), C(1)].
|
||||
vertexpos = (P[0].xy + vertexpos + P[3].xy) / 3.0;
|
||||
}
|
||||
|
||||
gl_Position = vec4(vertexpos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0);
|
||||
})");
|
||||
|
||||
return code;
|
||||
}
|
||||
};
|
||||
return new Impl;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GrPathTessellationShader* GrPathTessellationShader::MakeHardwareWedgeShader(
|
||||
SkArenaAlloc* arena, const SkMatrix& viewMatrix, const SkPMColor4f& color) {
|
||||
return arena->make<HardwareWedgeShader>(viewMatrix, color);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Uses GPU tessellation shaders to linearize, triangulate, and render standalone closed cubics.
|
||||
// TODO: Eventually we want to use rational cubic wedges in order to support perspective and conics.
|
||||
class HardwareCurveShader : public GrPathTessellationShader {
|
||||
public:
|
||||
HardwareCurveShader(const SkMatrix& viewMatrix, const SkPMColor4f& color)
|
||||
: GrPathTessellationShader(kTessellate_HardwareCurveShader_ClassID,
|
||||
GrPrimitiveType::kPatches, 4, viewMatrix, color) {
|
||||
constexpr static Attribute kInputPointAttrib{"inputPoint", kFloat2_GrVertexAttribType,
|
||||
kFloat2_GrSLType};
|
||||
this->setVertexAttributes(&kInputPointAttrib, 1);
|
||||
}
|
||||
|
||||
private:
|
||||
const char* name() const final { return "tessellate_HardwareCurveShader"; }
|
||||
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final {}
|
||||
GrGLSLGeometryProcessor* createGLSLInstance(const GrShaderCaps&) const final;
|
||||
};
|
||||
|
||||
GrGLSLGeometryProcessor* HardwareCurveShader::createGLSLInstance(const GrShaderCaps&) const {
|
||||
class Impl : public GrPathTessellationShader::Impl {
|
||||
void emitVertexCode(GrGLSLVertexBuilder* v, GrGPArgs*) override {
|
||||
v->declareGlobal(GrShaderVar("P", kFloat2_GrSLType, GrShaderVar::TypeModifier::Out));
|
||||
v->codeAppend(R"(
|
||||
// If y is infinity then x is a conic weight. Don't transform.
|
||||
P = (isinf(inputPoint.y)) ? inputPoint : AFFINE_MATRIX * inputPoint + TRANSLATE;)");
|
||||
}
|
||||
SkString getTessControlShaderGLSL(const GrGeometryProcessor&,
|
||||
const char* versionAndExtensionDecls,
|
||||
const GrGLSLUniformHandler&,
|
||||
const GrShaderCaps& shaderCaps) const override {
|
||||
SkString code(versionAndExtensionDecls);
|
||||
code.appendf(R"(
|
||||
#define MAX_TESSELLATION_SEGMENTS %i)", shaderCaps.maxTessellationSegments());
|
||||
code.appendf(R"(
|
||||
#define PRECISION %f)", GrTessellationPathRenderer::kLinearizationPrecision);
|
||||
code.append(kSkSLTypeDefs);
|
||||
code.append(GrWangsFormula::as_sksl());
|
||||
code.append(kUnpackRationalCubicFn);
|
||||
code.append(R"(
|
||||
layout(vertices = 1) out;
|
||||
|
||||
in vec2 P[];
|
||||
patch out mat4x2 rationalCubicXY;
|
||||
patch out float rationalCubicW;
|
||||
|
||||
void main() {
|
||||
float w = -1; // w<0 means a cubic.
|
||||
vec2 p1w = P[1];
|
||||
if (isinf(P[3].y)) {
|
||||
// This patch is actually a conic. Project to homogeneous space.
|
||||
w = P[3].x;
|
||||
p1w *= w;
|
||||
}
|
||||
|
||||
// Chop the curve at T=1/2.
|
||||
vec2 ab = (P[0] + p1w) * .5;
|
||||
vec2 bc = (p1w + P[2]) * .5;
|
||||
vec2 cd = (P[2] + P[3]) * .5;
|
||||
vec2 abc = (ab + bc) * .5;
|
||||
vec2 bcd = (bc + cd) * .5;
|
||||
vec2 abcd = (abc + bcd) * .5;
|
||||
|
||||
float n0, n1;
|
||||
if (w < 0 || isinf(w)) {
|
||||
if (w < 0) {
|
||||
// The patch is a cubic. Calculate how many segments are required to
|
||||
// linearize each half of the curve.
|
||||
n0 = wangs_formula(PRECISION, P[0], ab, abc, abcd, -1); // w<0 means cubic.
|
||||
n1 = wangs_formula(PRECISION, abcd, bcd, cd, P[3], -1);
|
||||
rationalCubicW = 1;
|
||||
} else {
|
||||
// The patch is a triangle (a conic with infinite weight).
|
||||
n0 = n1 = 1;
|
||||
rationalCubicW = -1; // In the next stage, rationalCubicW<0 means triangle.
|
||||
}
|
||||
rationalCubicXY = mat4x2(P[0], P[1], P[2], P[3]);
|
||||
} else {
|
||||
// The patch is a conic. Unproject p0..5. w1 == w2 == w3 when chopping at .5.
|
||||
// (See SkConic::chopAt().)
|
||||
float r = 2.0 / (1.0 + w);
|
||||
ab *= r, bc *= r, abc *= r;
|
||||
// Put in "standard form" where w0 == w2 == w4 == 1.
|
||||
float w_ = inversesqrt(r); // Both halves have the same w' when chopping at .5.
|
||||
// Calculate how many segments are needed to linearize each half of the curve.
|
||||
n0 = wangs_formula(PRECISION, P[0], ab, abc, float2(0), w_);
|
||||
n1 = wangs_formula(PRECISION, abc, bc, P[2], float2(0), w_);
|
||||
// Covert the conic to a rational cubic in projected form.
|
||||
rationalCubicXY = mat4x2(P[0],
|
||||
mix(float4(P[0],P[2]), p1w.xyxy, 2.0/3.0),
|
||||
P[2]);
|
||||
rationalCubicW = fma(w, 2.0/3.0, 1.0/3.0);
|
||||
}
|
||||
|
||||
gl_TessLevelOuter[0] = min(n1, MAX_TESSELLATION_SEGMENTS);
|
||||
gl_TessLevelOuter[1] = 1.0;
|
||||
gl_TessLevelOuter[2] = min(n0, MAX_TESSELLATION_SEGMENTS);
|
||||
|
||||
// Changing the inner level to 1 when n0 == n1 == 1 collapses the entire patch to a
|
||||
// single triangle. Otherwise, we need an inner level of 2 so our curve triangles
|
||||
// have an interior point to originate from.
|
||||
gl_TessLevelInner[0] = min(max(n0, n1), 2.0);
|
||||
})");
|
||||
|
||||
return code;
|
||||
}
|
||||
SkString getTessEvaluationShaderGLSL(const GrGeometryProcessor&,
|
||||
const char* versionAndExtensionDecls,
|
||||
const GrGLSLUniformHandler&,
|
||||
const GrShaderCaps&) const override {
|
||||
SkString code(versionAndExtensionDecls);
|
||||
code.append(kSkSLTypeDefs);
|
||||
code.append(kEvalRationalCubicFn);
|
||||
code.append(R"(
|
||||
layout(triangles, equal_spacing, ccw) in;
|
||||
|
||||
uniform vec4 sk_RTAdjust;
|
||||
|
||||
patch in mat4x2 rationalCubicXY;
|
||||
patch in float rationalCubicW;
|
||||
|
||||
void main() {
|
||||
vec2 vertexpos;
|
||||
if (rationalCubicW < 0) { // rationalCubicW < 0 means a triangle now.
|
||||
vertexpos = (gl_TessCoord.x != 0) ? rationalCubicXY[0]
|
||||
: (gl_TessCoord.y != 0) ? rationalCubicXY[1]
|
||||
: rationalCubicXY[2];
|
||||
} else {
|
||||
// Locate our parametric point of interest. T ramps from [0..1/2] on the left
|
||||
// edge of the triangle, and [1/2..1] on the right. If we are the patch's
|
||||
// interior vertex, then we want T=1/2. Since the barycentric coords are
|
||||
// (1/3, 1/3, 1/3) at the interior vertex, the below fma() works in all 3
|
||||
// scenarios.
|
||||
float T = fma(.5, gl_TessCoord.y, gl_TessCoord.z);
|
||||
|
||||
mat4x3 P = mat4x3(rationalCubicXY[0], 1,
|
||||
rationalCubicXY[1], rationalCubicW,
|
||||
rationalCubicXY[2], rationalCubicW,
|
||||
rationalCubicXY[3], 1);
|
||||
vertexpos = eval_rational_cubic(P, T);
|
||||
if (all(notEqual(gl_TessCoord.xz, vec2(0)))) {
|
||||
// We are the interior point of the patch; center it inside
|
||||
// [C(0), C(.5), C(1)].
|
||||
vertexpos = (P[0].xy + vertexpos + P[3].xy) / 3.0;
|
||||
}
|
||||
}
|
||||
|
||||
gl_Position = vec4(vertexpos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0);
|
||||
})");
|
||||
|
||||
return code;
|
||||
}
|
||||
};
|
||||
return new Impl;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GrPathTessellationShader* GrPathTessellationShader::MakeHardwareCurveShader(
|
||||
SkArenaAlloc* arena, const SkMatrix& viewMatrix, const SkPMColor4f& color) {
|
||||
return arena->make<HardwareCurveShader>(viewMatrix, color);
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2019 Google LLC.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
|
||||
|
||||
#include "src/gpu/glsl/GrGLSLProgramBuilder.h"
|
||||
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Uses instanced draws to triangulate standalone closed curves with a "middle-out" topology.
|
||||
// Middle-out draws a triangle with vertices at T=[0, 1/2, 1] and then recurses breadth first:
|
||||
//
|
||||
// depth=0: T=[0, 1/2, 1]
|
||||
// depth=1: T=[0, 1/4, 2/4], T=[2/4, 3/4, 1]
|
||||
// depth=2: T=[0, 1/8, 2/8], T=[2/8, 3/8, 4/8], T=[4/8, 5/8, 6/8], T=[6/8, 7/8, 1]
|
||||
// ...
|
||||
//
|
||||
// The caller may compute each cubic's resolveLevel on the CPU (i.e., the log2 number of line
|
||||
// segments it will be divided into; see GrWangsFormula::cubic_log2/quadratic_log2/conic_log2), and
|
||||
// then sort the instance buffer by resolveLevel for efficient batching of indirect draws.
|
||||
class MiddleOutShader : public GrPathTessellationShader {
|
||||
public:
|
||||
MiddleOutShader(const SkMatrix& viewMatrix, const SkPMColor4f& color)
|
||||
: GrPathTessellationShader(kTessellate_MiddleOutShader_ClassID,
|
||||
GrPrimitiveType::kTriangles, 0, viewMatrix, color) {
|
||||
constexpr static Attribute kInputPtsAttribs[] = {
|
||||
{"inputPoints_0_1", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
|
||||
{"inputPoints_2_3", kFloat4_GrVertexAttribType, kFloat4_GrSLType}};
|
||||
this->setInstanceAttributes(kInputPtsAttribs, 2);
|
||||
}
|
||||
|
||||
private:
|
||||
const char* name() const final { return "tessellate_MiddleOutShader"; }
|
||||
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final {}
|
||||
GrGLSLGeometryProcessor* createGLSLInstance(const GrShaderCaps&) const final;
|
||||
};
|
||||
|
||||
GrGLSLGeometryProcessor* MiddleOutShader::createGLSLInstance(const GrShaderCaps&) const {
|
||||
class Impl : public GrPathTessellationShader::Impl {
|
||||
void emitVertexCode(GrGLSLVertexBuilder* v, GrGPArgs* gpArgs) override {
|
||||
v->insertFunction(kUnpackRationalCubicFn);
|
||||
v->insertFunction(kEvalRationalCubicFn);
|
||||
if (v->getProgramBuilder()->shaderCaps()->bitManipulationSupport()) {
|
||||
// Determines the T value at which to place the given vertex in a "middle-out"
|
||||
// topology.
|
||||
v->insertFunction(R"(
|
||||
float find_middle_out_T() {
|
||||
int totalTriangleIdx = sk_VertexID/3 + 1;
|
||||
int depth = findMSB(totalTriangleIdx);
|
||||
int firstTriangleAtDepth = (1 << depth);
|
||||
int triangleIdxWithinDepth = totalTriangleIdx - firstTriangleAtDepth;
|
||||
int vertexIdxWithinDepth = triangleIdxWithinDepth * 2 + sk_VertexID % 3;
|
||||
return ldexp(float(vertexIdxWithinDepth), -1 - depth);
|
||||
})");
|
||||
} else {
|
||||
// Determines the T value at which to place the given vertex in a "middle-out"
|
||||
// topology.
|
||||
v->insertFunction(R"(
|
||||
float find_middle_out_T() {
|
||||
float totalTriangleIdx = float(sk_VertexID/3) + 1;
|
||||
float depth = floor(log2(totalTriangleIdx));
|
||||
float firstTriangleAtDepth = exp2(depth);
|
||||
float triangleIdxWithinDepth = totalTriangleIdx - firstTriangleAtDepth;
|
||||
float vertexIdxWithinDepth = triangleIdxWithinDepth*2 + float(sk_VertexID % 3);
|
||||
return vertexIdxWithinDepth * exp2(-1 - depth);
|
||||
})");
|
||||
}
|
||||
v->codeAppend(R"(
|
||||
float2 localcoord;
|
||||
if (isinf(inputPoints_2_3.z)) {
|
||||
// A conic with w=Inf is an exact triangle.
|
||||
localcoord = (sk_VertexID < 1) ? inputPoints_0_1.xy
|
||||
: (sk_VertexID == 1) ? inputPoints_0_1.zw
|
||||
: inputPoints_2_3.xy;
|
||||
} else {
|
||||
float4x3 P = unpack_rational_cubic(inputPoints_0_1.xy, inputPoints_0_1.zw,
|
||||
inputPoints_2_3.xy, inputPoints_2_3.zw);
|
||||
float T = find_middle_out_T();
|
||||
localcoord = eval_rational_cubic(P, T);
|
||||
}
|
||||
float2 vertexpos = AFFINE_MATRIX * localcoord + TRANSLATE;)");
|
||||
gpArgs->fLocalCoordVar.set(kFloat2_GrSLType, "localcoord");
|
||||
gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertexpos");
|
||||
}
|
||||
};
|
||||
return new Impl;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GrPathTessellationShader* GrPathTessellationShader::MakeMiddleOutInstancedShader(
|
||||
SkArenaAlloc* arena, const SkMatrix& viewMatrix, const SkPMColor4f& color) {
|
||||
return arena->make<MiddleOutShader>(viewMatrix, color);
|
||||
}
|
Loading…
Reference in New Issue
Block a user