blob: ee1d162084511c686ee0085305986c90b4edc0fb [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/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*) 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 {
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 resolveLevel = findMSB(totalTriangleIdx) + 1;
int firstTriangleAtDepth = (1 << (resolveLevel - 1));
int triangleIdxWithinDepth = totalTriangleIdx - firstTriangleAtDepth;
int vertexIdxWithinDepth = triangleIdxWithinDepth * 2 + sk_VertexID % 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() {
float totalTriangleIdx = float(sk_VertexID/3) + 1;
float resolveLevel = floor(log2(totalTriangleIdx)) + 1;
float firstTriangleAtDepth = exp2(resolveLevel - 1);
float triangleIdxWithinDepth = totalTriangleIdx - firstTriangleAtDepth;
float vertexIdxWithinDepth = triangleIdxWithinDepth*2 + float(sk_VertexID % 3);
return vertexIdxWithinDepth * exp2(-resolveLevel);
})");
}
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 {
float w = -1; // w < 0 tells us to treat the instance as an integral cubic.
float4x2 P = float4x2(inputPoints_0_1, inputPoints_2_3);
if (isinf(P[3].y)) {
// The patch is a conic.
w = P[3].x;
P[3] = P[2]; // Duplicate the endpoint.
P[1] *= w; // Unproject p1.
}
float T = find_middle_out_T();
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::MakeMiddleOutInstancedShader(
SkArenaAlloc* arena, const SkMatrix& viewMatrix, const SkPMColor4f& color) {
return arena->make<MiddleOutShader>(viewMatrix, color);
}