Restructure path tessellation shaders
Merges the GrStrokePathShader and GrFillPathShader classes together.
Creates a new base class called GrPathTessellationShader. Now they all
have a uniform color and can all be draw to stencil and color both.
This is necessary cleanup in general, but will also allow us to create
a convex tessellation op that bypasses the stencil buffer.
Bug: skia:10419
Change-Id: Ifc492c94d3de044a36bd9ea95b1d5aa22e007905
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/413696
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp b/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp
index 5099f48..663fab2 100644
--- a/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp
+++ b/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp
@@ -11,13 +11,102 @@
#include "src/gpu/GrInnerFanTriangulator.h"
#include "src/gpu/GrOpFlushState.h"
#include "src/gpu/GrRecordingContextPriv.h"
-#include "src/gpu/tessellate/GrFillPathShader.h"
+#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
+#include "src/gpu/tessellate/GrPathTessellationShader.h"
#include "src/gpu/tessellate/GrPathTessellator.h"
-#include "src/gpu/tessellate/GrStencilPathShader.h"
#include "src/gpu/tessellate/GrTessellationPathRenderer.h"
using OpFlags = GrTessellationPathRenderer::OpFlags;
+namespace {
+
+// Fills an array of convex hulls surrounding 4-point cubic or conic instances. This shader is used
+// for the "fill" pass after the curves have been fully stencilled.
+class HullShader : public GrPathTessellationShader {
+public:
+ HullShader(const SkMatrix& viewMatrix, SkPMColor4f color)
+ : GrPathTessellationShader(kTessellate_HullShader_ClassID,
+ GrPrimitiveType::kTriangleStrip, 0, viewMatrix, color) {
+ constexpr static Attribute kPtsAttribs[] = {
+ {"input_points_0_1", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
+ {"input_points_2_3", kFloat4_GrVertexAttribType, kFloat4_GrSLType}};
+ this->setInstanceAttributes(kPtsAttribs, SK_ARRAY_COUNT(kPtsAttribs));
+ }
+
+private:
+ const char* name() const final { return "HullShader"; }
+ void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const final {}
+ GrGLSLGeometryProcessor* createGLSLInstance(const GrShaderCaps&) const final;
+};
+
+GrGLSLGeometryProcessor* HullShader::createGLSLInstance(const GrShaderCaps&) const {
+ class Impl : public GrPathTessellationShader::Impl {
+ void emitVertexCode(GrGLSLVertexBuilder* v, GrGPArgs* gpArgs) override {
+ v->codeAppend(R"(
+ float4x2 P = float4x2(input_points_0_1, input_points_2_3);
+ if (isinf(P[3].y)) { // Is the curve a conic?
+ float w = P[3].x;
+ if (isinf(w)) {
+ // A conic with w=Inf is an exact triangle.
+ P = float4x2(P[0], P[1], P[2], P[2]);
+ } else {
+ // Convert the points to a trapeziodal hull that circumcscribes the conic.
+ float2 p1w = P[1] * w;
+ float T = .51; // Bias outward a bit to ensure we cover the outermost samples.
+ float2 c1 = mix(P[0], p1w, T);
+ float2 c2 = mix(P[2], p1w, T);
+ float iw = 1 / mix(1, w, T);
+ P = float4x2(P[0], c1 * iw, c2 * iw, P[2]);
+ }
+ }
+
+ // Translate the points to v0..3 where v0=0.
+ float2 v1 = P[1] - P[0], v2 = P[2] - P[0], v3 = P[3] - P[0];
+
+ // Reorder the points so v2 bisects v1 and v3.
+ if (sign(determinant(float2x2(v2,v1))) == sign(determinant(float2x2(v2,v3)))) {
+ float2 tmp = P[2];
+ if (sign(determinant(float2x2(v1,v2))) != sign(determinant(float2x2(v1,v3)))) {
+ P[2] = P[1]; // swap(P2, P1)
+ P[1] = tmp;
+ } else {
+ P[2] = P[3]; // swap(P2, P3)
+ P[3] = tmp;
+ }
+ }
+
+ // sk_VertexID comes in fan order. Convert to strip order.
+ int vertexidx = sk_VertexID;
+ vertexidx ^= vertexidx >> 1;
+
+ // Find the "turn direction" of each corner and net turn direction.
+ float vertexdir = 0;
+ float netdir = 0;
+ for (int i = 0; i < 4; ++i) {
+ float2 prev = P[i] - P[(i + 3) & 3], next = P[(i + 1) & 3] - P[i];
+ float dir = sign(determinant(float2x2(prev, next)));
+ if (i == vertexidx) {
+ vertexdir = dir;
+ }
+ netdir += dir;
+ }
+
+ // Remove the non-convex vertex, if any.
+ if (vertexdir != sign(netdir)) {
+ vertexidx = (vertexidx + 1) & 3;
+ }
+
+ float2 localcoord = P[vertexidx];
+ float2 vertexpos = AFFINE_MATRIX * localcoord + TRANSLATE;)");
+ gpArgs->fLocalCoordVar.set(kFloat2_GrSLType, "localcoord");
+ gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertexpos");
+ }
+ };
+ return new Impl;
+}
+
+} // namespace
+
void GrPathInnerTriangulateOp::visitProxies(const VisitProxyFunc& fn) const {
if (fPipelineForFills) {
fPipelineForFills->visitProxies(fn);
@@ -41,22 +130,23 @@
clampType, &fColor);
}
-void GrPathInnerTriangulateOp::pushFanStencilProgram(const GrPathShader::ProgramArgs& args,
+void GrPathInnerTriangulateOp::pushFanStencilProgram(const GrTessellationShader::ProgramArgs& args,
const GrPipeline* pipelineForStencils,
const GrUserStencilSettings* stencil) {
SkASSERT(pipelineForStencils);
- fFanPrograms.push_back(GrStencilPathShader::MakeStencilProgram<GrStencilTriangleShader>(
- args, fViewMatrix, pipelineForStencils, stencil));
-}
+ auto shader = args.fArena->make<GrTriangleShader>(fViewMatrix, SK_PMColor4fTRANSPARENT);
+ fFanPrograms.push_back(GrTessellationShader::MakeProgram(args, shader, pipelineForStencils,
+ stencil)); }
-void GrPathInnerTriangulateOp::pushFanFillProgram(const GrPathShader::ProgramArgs& args,
+void GrPathInnerTriangulateOp::pushFanFillProgram(const GrTessellationShader::ProgramArgs& args,
const GrUserStencilSettings* stencil) {
SkASSERT(fPipelineForFills);
- auto* shader = args.fArena->make<GrFillTriangleShader>(fViewMatrix, fColor);
- fFanPrograms.push_back(GrPathShader::MakeProgram(args, shader, fPipelineForFills, stencil));
+ auto* shader = args.fArena->make<GrTriangleShader>(fViewMatrix, fColor);
+ fFanPrograms.push_back(GrTessellationShader::MakeProgram(args, shader, fPipelineForFills,
+ stencil));
}
-void GrPathInnerTriangulateOp::prePreparePrograms(const GrPathShader::ProgramArgs& args,
+void GrPathInnerTriangulateOp::prePreparePrograms(const GrTessellationShader::ProgramArgs& args,
GrAppliedClip&& appliedClip) {
SkASSERT(!fFanTriangulator);
SkASSERT(!fFanPolys);
@@ -82,15 +172,15 @@
// Create a pipeline for stencil passes if needed.
const GrPipeline* pipelineForStencils = nullptr;
if (forceRedbookStencilPass || !isLinear) { // Curves always get stencilled.
- pipelineForStencils = GrStencilPathShader::MakeStencilPassPipeline(args, fAAType, fOpFlags,
- appliedClip.hardClip());
+ pipelineForStencils = GrPathTessellationShader::MakeStencilOnlyPipeline(
+ args, fAAType, fOpFlags, appliedClip.hardClip());
}
// Create a pipeline for fill passes if needed.
if (doFill) {
- fPipelineForFills = GrFillPathShader::MakeFillPassPipeline(args, fAAType,
- std::move(appliedClip),
- std::move(fProcessors));
+ fPipelineForFills = GrTessellationShader::MakePipeline(args, fAAType,
+ std::move(appliedClip),
+ std::move(fProcessors));
}
// Pass 1: Tessellate the outer curves into the stencil buffer.
@@ -99,11 +189,14 @@
// and the middle-out topology used by indirect draws is easier on the rasterizer than what
// we can do with hw tessellation. So far we haven't found any platforms where trying to use
// hw tessellation here is worth it.
- fTessellator = GrPathTessellator::Make(args.fArena, fViewMatrix, fPath,
+ fTessellator = GrPathTessellator::Make(args.fArena, fPath, fViewMatrix,
+ SK_PMColor4fTRANSPARENT,
GrPathTessellator::DrawInnerFan::kNo, *args.fCaps);
- fStencilCurvesProgram = GrPathShader::MakeProgram(
- args, fTessellator->shader(), pipelineForStencils,
- GrStencilPathShader::StencilPassSettings(fPath.getFillType()));
+ const GrUserStencilSettings* stencilPathSettings =
+ GrPathTessellationShader::StencilPathSettings(fPath.getFillType());
+ fStencilCurvesProgram = GrTessellationShader::MakeProgram(args, fTessellator->shader(),
+ pipelineForStencils,
+ stencilPathSettings);
}
// Pass 2: Fill the path's inner fan with a stencil test against the curves.
@@ -111,11 +204,12 @@
if (forceRedbookStencilPass) {
// Use a standard Redbook "stencil then fill" algorithm instead of bypassing the stencil
// buffer to fill the fan directly.
- this->pushFanStencilProgram(
- args, pipelineForStencils,
- GrStencilPathShader::StencilPassSettings(fPath.getFillType()));
+ const GrUserStencilSettings* stencilPathSettings =
+ GrPathTessellationShader::StencilPathSettings(fPath.getFillType());
+ this->pushFanStencilProgram(args, pipelineForStencils, stencilPathSettings);
if (doFill) {
- this->pushFanFillProgram(args, GrFillPathShader::TestAndResetStencilSettings());
+ this->pushFanFillProgram(args,
+ GrPathTessellationShader::TestAndResetStencilSettings());
}
} else if (isLinear) {
// There are no outer curves! Ignore stencil and fill the path directly.
@@ -204,10 +298,10 @@
// by curves. We issue a final cover pass over the curves by drawing their convex hulls.
// This will fill in any remaining samples and reset the stencil values back to zero.
SkASSERT(fTessellator);
- auto* hullShader = args.fArena->make<GrFillCubicHullShader>(fViewMatrix, fColor);
- fFillHullsProgram = GrPathShader::MakeProgram(
+ auto* hullShader = args.fArena->make<HullShader>(fViewMatrix, fColor);
+ fFillHullsProgram = GrTessellationShader::MakeProgram(
args, hullShader, fPipelineForFills,
- GrFillPathShader::TestAndResetStencilSettings());
+ GrPathTessellationShader::TestAndResetStencilSettings());
}
}