blob: 354124eb6bb0746a0955e42d4ae46e71edfb6f80 [file] [log] [blame]
/*
* 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/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 shader determines how many segments are required to render each individual curve smoothly,
// and emits empty triangles at any vertices whose sk_VertexIDs are higher than necessary. It is the
// caller's responsibility to draw enough vertices per instance for the most complex curve in the
// batch to render smoothly (i.e., NumTrianglesAtResolveLevel() * 3).
class MiddleOutShader : public GrPathTessellationShader {
public:
MiddleOutShader(const SkMatrix& viewMatrix, const SkPMColor4f& color, PatchType patchType)
: GrPathTessellationShader(kTessellate_MiddleOutShader_ClassID,
GrPrimitiveType::kTriangles, 0, viewMatrix, color)
, fPatchType(patchType) {
fAttribs.emplace_back("inputPoints_0_1", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
fAttribs.emplace_back("inputPoints_2_3", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
if (fPatchType == PatchType::kWedges) {
fAttribs.emplace_back("fanPoint", kFloat2_GrVertexAttribType, kFloat2_GrSLType);
}
this->setInstanceAttributes(fAttribs.data(), fAttribs.count());
SkASSERT(fAttribs.count() <= kMaxAttribCount);
}
private:
const char* name() const final { return "tessellate_MiddleOutShader"; }
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final {
b->add32((uint32_t)fPatchType);
}
GrGLSLGeometryProcessor* createGLSLInstance(const GrShaderCaps&) const final;
const PatchType fPatchType;
constexpr static int kMaxAttribCount = 3;
SkSTArray<kMaxAttribCount, Attribute> fAttribs;
};
GrGLSLGeometryProcessor* MiddleOutShader::createGLSLInstance(const GrShaderCaps&) const {
class Impl : public GrPathTessellationShader::Impl {
void emitVertexCode(const GrPathTessellationShader& shader, GrGLSLVertexBuilder* v,
GrGPArgs* gpArgs) override {
v->defineConstant("PRECISION", GrTessellationPathRenderer::kLinearizationPrecision);
v->insertFunction(GrWangsFormula::as_sksl().c_str());
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 middleOutVertexID, float maxResolveLevel) {
int totalTriangleIdx = middleOutVertexID/3 + 1;
int resolveLevel = findMSB(totalTriangleIdx) + 1;
if (resolveLevel > int(maxResolveLevel)) {
// This vertex is at a higher resolve level than we need. Emit a degenerate
// triangle at T=1.
return 1;
}
int firstTriangleAtDepth = (1 << (resolveLevel - 1));
int triangleIdxWithinDepth = totalTriangleIdx - firstTriangleAtDepth;
int vertexIdxWithinDepth = triangleIdxWithinDepth * 2 + middleOutVertexID % 3;
return ldexp(float(vertexIdxWithinDepth), -resolveLevel);
})");
} 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(int middleOutVertexID, float maxResolveLevel) {
float totalTriangleIdx = float(middleOutVertexID/3) + 1;
float resolveLevel = floor(log2(totalTriangleIdx)) + 1;
if (resolveLevel > maxResolveLevel) {
// This vertex is at a higher resolve level than we need. Emit a degenerate
// triangle at T=1.
return 1;
}
float firstTriangleAtDepth = exp2(resolveLevel - 1);
float triangleIdxWithinDepth = totalTriangleIdx - firstTriangleAtDepth;
float vertexIdxWithinDepth =
triangleIdxWithinDepth * 2 + float(middleOutVertexID % 3);
return vertexIdxWithinDepth * exp2(-resolveLevel);
})");
}
v->codeAppend(R"(
int middleOutVertexID = sk_VertexID;
float2 localcoord;)");
if (shader.cast<MiddleOutShader>().fPatchType == PatchType::kWedges) {
v->codeAppend(R"(
// The first triangle is the fan triangle.
middleOutVertexID -= 3;
if (middleOutVertexID < 0) {
float2 endpoint = isinf(inputPoints_2_3.w) ? inputPoints_2_3.xy /*conic*/
: inputPoints_2_3.zw /*cubic*/;
localcoord = (sk_VertexID < 1) ? inputPoints_0_1.xy
: (sk_VertexID == 1) ? endpoint
: fanPoint;
} else)"); // Fall through to next if ().
}
v->codeAppend(R"(
if (isinf(inputPoints_2_3.z)) {
// A conic with w=Inf is an exact triangle.
localcoord = (middleOutVertexID < 1) ? inputPoints_0_1.xy
: (middleOutVertexID == 1) ? inputPoints_0_1.zw
: inputPoints_2_3.xy;
} else {
float w = -1; // w < 0 tells us to treat the instance as an integral cubic.
float4x2 P = float4x2(inputPoints_0_1, inputPoints_2_3);
float maxResolveLevel;
if (isinf(P[3].y)) {
// The patch is a conic.
w = P[3].x;
maxResolveLevel =
wangs_formula_conic_log2(PRECISION, AFFINE_MATRIX * float3x2(P), w);
P[3] = P[2]; // Duplicate the endpoint.
P[1] *= w; // Unproject p1.
} else {
// The patch is an integral cubic.
maxResolveLevel = wangs_formula_cubic_log2(PRECISION, P, AFFINE_MATRIX);
}
float T = find_middle_out_T(middleOutVertexID, maxResolveLevel);
if (0 < T && T < 1) {
// Evaluate at T. Use De Casteljau's for its accuracy and stability.
float2 ab = mix(P[0], P[1], T);
float2 bc = mix(P[1], P[2], T);
float2 cd = mix(P[2], P[3], T);
float2 abc = mix(ab, bc, T);
float2 bcd = mix(bc, cd, T);
float2 abcd = mix(abc, bcd, T);
// Evaluate the conic weight at T.
float u = mix(1.0, w, T);
float v = w + 1 - u; // == mix(w, 1, T)
float uv = mix(u, v, T);
localcoord = (w < 0) ? /*cubic*/ abcd : /*conic*/ abc/uv;
} else {
localcoord = (T == 0) ? P[0].xy : P[3].xy;
}
}
float2 vertexpos = AFFINE_MATRIX * localcoord + TRANSLATE;)");
gpArgs->fLocalCoordVar.set(kFloat2_GrSLType, "localcoord");
gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertexpos");
}
};
return new Impl;
}
} // namespace
GrPathTessellationShader* GrPathTessellationShader::MakeMiddleOutFixedCountShader(
SkArenaAlloc* arena, const SkMatrix& viewMatrix, const SkPMColor4f& color,
PatchType patchType) {
return arena->make<MiddleOutShader>(viewMatrix, color, patchType);
}