diff --git a/gn/gpu.gni b/gn/gpu.gni index e34cce1b033b9..74b286c8c5f42 100644 --- a/gn/gpu.gni +++ b/gn/gpu.gni @@ -426,10 +426,10 @@ skia_gpu_sources = [ "$_src/gpu/tessellate/GrGpuTessellationPathRenderer.h", "$_src/gpu/tessellate/GrPathParser.cpp", "$_src/gpu/tessellate/GrPathParser.h", + "$_src/gpu/tessellate/GrStencilPathShader.cpp", + "$_src/gpu/tessellate/GrStencilPathShader.h", "$_src/gpu/tessellate/GrTessellatePathOp.cpp", "$_src/gpu/tessellate/GrTessellatePathOp.h", - "$_src/gpu/tessellate/GrTessellateWedgeShader.cpp", - "$_src/gpu/tessellate/GrTessellateWedgeShader.h", # text "$_src/gpu/text/GrAtlasManager.cpp", diff --git a/samplecode/SampleTessellatedWedge.cpp b/samplecode/SampleTessellatedWedge.cpp index 29f624eca181a..92fe17728c1cc 100644 --- a/samplecode/SampleTessellatedWedge.cpp +++ b/samplecode/SampleTessellatedWedge.cpp @@ -25,9 +25,20 @@ class TessellatedWedgeView : public Sample { public: TessellatedWedgeView() { +#if 0 + fPath.moveTo(1, 0); + int numSides = 32 * 3; + for (int i = 1; i < numSides; ++i) { + float theta = 2*3.1415926535897932384626433832785 * i / numSides; + fPath.lineTo(std::cos(theta), std::sin(theta)); + } + fPath.transform(SkMatrix::MakeScale(200, 200)); + fPath.transform(SkMatrix::MakeTrans(300, 300)); +#else fPath.moveTo(100, 200); fPath.cubicTo(100, 100, 400, 100, 400, 200); fPath.lineTo(250, 500); +#endif } private: @@ -87,8 +98,14 @@ void TessellatedWedgeView::onDrawContent(SkCanvas* canvas) { SkPaint pointsPaint; pointsPaint.setColor(SK_ColorBLUE); pointsPaint.setStrokeWidth(8); - canvas->drawPoints(SkCanvas::kPoints_PointMode, fPath.countPoints(), - SkPathPriv::PointData(fPath), pointsPaint); + SkPath devPath = fPath; + devPath.transform(canvas->getTotalMatrix()); + { + SkAutoCanvasRestore acr(canvas, true); + canvas->setMatrix(SkMatrix::I()); + canvas->drawPoints(SkCanvas::kPoints_PointMode, devPath.countPoints(), + SkPathPriv::PointData(devPath), pointsPaint); + } fLastViewMatrix = canvas->getTotalMatrix(); } diff --git a/src/gpu/GrProcessor.h b/src/gpu/GrProcessor.h index 8a9aeb1211f18..fe6b12b15edce 100644 --- a/src/gpu/GrProcessor.h +++ b/src/gpu/GrProcessor.h @@ -139,7 +139,6 @@ class GrProcessor { kGrSRGBEffect_ClassID, kGrSampleMaskProcessor_ClassID, kGrSweepGradientLayout_ClassID, - kGrTessellateWedgeShader_ClassID, kGrTextureEffect_ClassID, kGrTextureGradientColorizer_ClassID, kGrTiledGradientEffect_ClassID, @@ -162,6 +161,9 @@ class GrProcessor { kSwizzleFragmentProcessor_ClassID, kTessellationTestTriShader_ClassID, kTessellationTestRectShader_ClassID, + kTessellate_GrStencilCubicShader_ClassID, + kTessellate_GrStencilTriangleShader_ClassID, + kTessellate_GrStencilWedgeShader_ClassID, kTestFP_ClassID, kTestRectOp_ClassID, kFlatNormalsFP_ClassID, diff --git a/src/gpu/tessellate/GrGpuTessellationPathRenderer.cpp b/src/gpu/tessellate/GrGpuTessellationPathRenderer.cpp index 0f2fcbdd1ab30..1f70a0f8ef201 100644 --- a/src/gpu/tessellate/GrGpuTessellationPathRenderer.cpp +++ b/src/gpu/tessellate/GrGpuTessellationPathRenderer.cpp @@ -41,9 +41,9 @@ bool GrGpuTessellationPathRenderer::onDrawPath(const DrawPathArgs& args) { SkPath path; args.fShape->asPath(&path); - GrOpMemoryPool* pool = args.fContext->priv().opMemoryPool(); - args.fRenderTargetContext->addDrawOp(*args.fClip, pool->allocate( - *args.fViewMatrix, path, std::move(args.fPaint), args.fAAType)); + auto op = args.fContext->priv().opMemoryPool()->allocate( + *args.fViewMatrix, path, std::move(args.fPaint), args.fAAType); + args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op)); return true; } @@ -54,7 +54,7 @@ void GrGpuTessellationPathRenderer::onStencilPath(const StencilPathArgs& args) { GrAAType aaType = (GrAA::kYes == args.fDoStencilMSAA) ? GrAAType::kMSAA : GrAAType::kNone; - GrOpMemoryPool* pool = args.fContext->priv().opMemoryPool(); - args.fRenderTargetContext->addDrawOp(*args.fClip, pool->allocate( - *args.fViewMatrix, path, GrPaint(), aaType, GrTessellatePathOp::Flags::kStencilOnly)); + auto op = args.fContext->priv().opMemoryPool()->allocate( + *args.fViewMatrix, path, GrPaint(), aaType, GrTessellatePathOp::Flags::kStencilOnly); + args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op)); } diff --git a/src/gpu/tessellate/GrPathParser.cpp b/src/gpu/tessellate/GrPathParser.cpp index 18f72da6ee50c..c6c61acb75baf 100644 --- a/src/gpu/tessellate/GrPathParser.cpp +++ b/src/gpu/tessellate/GrPathParser.cpp @@ -7,10 +7,9 @@ #include "src/gpu/tessellate/GrPathParser.h" +#include "include/private/SkTArray.h" #include "src/core/SkPathPriv.h" -namespace GrPathParser { - static SkPoint lerp(const SkPoint& a, const SkPoint& b, float T) { SkASSERT(1 != T); // The below does not guarantee lerp(a, b, 1) === b. return (b - a) * T + a; @@ -25,7 +24,7 @@ static SkPoint write_line_as_cubic(SkPoint* data, const SkPoint& p0, const SkPoi } static SkPoint write_quadratic_as_cubic(SkPoint* data, const SkPoint& p0, const SkPoint& p1, - const SkPoint& p2) { + const SkPoint& p2) { data[0] = p0; data[1] = lerp(p0, p1, 2/3.f); data[2] = lerp(p1, p2, 1/3.f); @@ -77,7 +76,7 @@ class MidpointContourParser : public SkTPathContourParser int fMidpointWeight; }; -int EmitCenterWedges(const SkPath& path, SkPoint* patchData) { +int GrPathParser::EmitCenterWedgePatches(const SkPath& path, SkPoint* patchData) { int vertexCount = 0; MidpointContourParser parser(path); while (parser.parseNextContour()) { @@ -122,8 +121,112 @@ int EmitCenterWedges(const SkPath& path, SkPoint* patchData) { } } - SkASSERT(vertexCount <= MaxPossibleWedgeVertices(path)); + SkASSERT(vertexCount <= MaxWedgeVertices(path)); + return vertexCount; +} + +// Triangulates the polygon defined by the points in the range [first..last] inclusive. +// Called by InnerPolygonContourParser::emitInnerPolygon() (and recursively). +static int emit_subpolygon(const SkPoint* points, int first, int last, SkPoint* vertexData) { + if (last - first < 2) { + return 0; + } + + // For sub-polygons we subdivide the points in two and connect the endpoints. + int mid = (first + last) / 2; + vertexData[0] = points[first]; + vertexData[1] = points[mid]; + vertexData[2] = points[last]; + + // Emit the sub-polygon at each outer-edge of our new triangle. + int vertexCount = 3; + vertexCount += emit_subpolygon(points, first, mid, vertexData + vertexCount); + vertexCount += emit_subpolygon(points, mid, last, vertexData + vertexCount); + return vertexCount; +} + +class InnerPolygonContourParser : public SkTPathContourParser { +public: + InnerPolygonContourParser(const SkPath& path) : SkTPathContourParser(path) { + fPolyPoints.reserve(GrPathParser::MaxInnerPolygonVertices(path)); + } + + int emitInnerPolygon(SkPoint* vertexData) { + if (fPolyPoints.size() < 3) { + return 0; + } + + // For the first triangle in the polygon, subdivide our points into thirds. + int i1 = fPolyPoints.size() / 3; + int i2 = (2 * fPolyPoints.size()) / 3; + vertexData[0] = fPolyPoints[0]; + vertexData[1] = fPolyPoints[i1]; + vertexData[2] = fPolyPoints[i2]; + + // Emit the sub-polygons at all three edges of our first triangle. + int vertexCount = 3; + vertexCount += emit_subpolygon(fPolyPoints.begin(), 0, i1, vertexData + vertexCount); + vertexCount += emit_subpolygon(fPolyPoints.begin(), i1, i2, vertexData + vertexCount); + int i3 = fPolyPoints.size(); + fPolyPoints.push_back(fPolyPoints.front()); + vertexCount += emit_subpolygon(fPolyPoints.begin(), i2, i3, vertexData + vertexCount); + fPolyPoints.pop_back(); + + return vertexCount; + } + + int numCurves() const { return fNumCurves; } + +private: + friend class SkTPathContourParser; + + void resetGeometry(const SkPoint& startPoint) { + fPolyPoints.pop_back_n(fPolyPoints.count()); + fPolyPoints.push_back(startPoint); + fNumCurves = 0; + } + + void geometryTo(SkPathVerb verb, const SkPoint& endpoint) { + fPolyPoints.push_back(endpoint); + if (SkPathVerb::kLine != verb) { + ++fNumCurves; + } + } + + SkSTArray<128, SkPoint> fPolyPoints; + int fNumCurves; +}; + +int GrPathParser::EmitInnerPolygonTriangles(const SkPath& path, SkPoint* vertexData, + int* numCurves) { + *numCurves = 0; + int vertexCount = 0; + InnerPolygonContourParser parser(path); + while (parser.parseNextContour()) { + vertexCount += parser.emitInnerPolygon(vertexData + vertexCount); + *numCurves += parser.numCurves(); + } + + SkASSERT(vertexCount <= MaxInnerPolygonVertices(path)); return vertexCount; } -} // namespace +int GrPathParser::EmitCubicInstances(const SkPath& path, SkPoint* vertexData) { + int instanceCount = 0; + SkPath::Iter iter(path, false); + SkPath::Verb verb; + SkPoint pts[4]; + while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { + if (SkPath::kQuad_Verb == verb) { + write_quadratic_as_cubic(vertexData + (instanceCount * 4), pts[0], pts[1], pts[2]); + ++instanceCount; + continue; + } + if (SkPath::kCubic_Verb == verb) { + write_cubic(vertexData + (instanceCount * 4), pts[0], pts[1], pts[2], pts[3]); + ++instanceCount; + continue; + } + } + return instanceCount; +} diff --git a/src/gpu/tessellate/GrPathParser.h b/src/gpu/tessellate/GrPathParser.h index 014423f349386..76f61cfc945e1 100644 --- a/src/gpu/tessellate/GrPathParser.h +++ b/src/gpu/tessellate/GrPathParser.h @@ -12,9 +12,9 @@ namespace GrPathParser { -// Returns the maximum possible number of vertices that can be written by EmitCenterWedges() for the -// given path. -inline int MaxPossibleWedgeVertices(const SkPath& path) { +// Returns the maximum number of vertices that can be written by EmitCenterWedges() for the given +// path. +inline int MaxWedgeVertices(const SkPath& path) { // No initial moveTo, one wedge per verb, plus an implicit close at the end. // Each wedge has 5 vertices. return (path.countVerbs() + 1) * 5; @@ -29,8 +29,29 @@ inline int MaxPossibleWedgeVertices(const SkPath& path) { // // Returns the number of vertices written to the array. // -// NOTE: The incoming patchData must have allocated at least MaxWedgeVertices() vertices. -int EmitCenterWedges(const SkPath&, SkPoint* patchData); +// The incoming patchData array must have at least MaxWedgeVertices() elements. +int EmitCenterWedgePatches(const SkPath&, SkPoint* patchData); + +// Returns the maximum number of vertices required to triangulate the given path's inner polygon(s). +inline int MaxInnerPolygonVertices(const SkPath& path) { + // No initial moveTo, plus an implicit close at the end; n-2 trianles fill an n-gon. + // Each triangle has 3 vertices. + return (path.countVerbs() - 1) * 3; +} + +// Triangulates the path's inner polygon(s) and writes the result to "vertexData". The inner +// polygons connect the endpoints of each verb. (i.e., they are the path that would result from +// collapsing all curves to single lines.) +// +// This method works by recursively subdividing the path rather than emitting a linear triangle fan +// or strip. This can reduce the load on the rasterizer by a great deal on complex paths. +// +// Returns the number of vertices written to the array. +// +// The incoming vertexData array must have at least MaxInnerPolygonVertices() elements. +int EmitInnerPolygonTriangles(const SkPath&, SkPoint* vertexData, int* numCurves); + +int EmitCubicInstances(const SkPath&, SkPoint* vertexData); } // namespace diff --git a/src/gpu/tessellate/GrStencilPathShader.cpp b/src/gpu/tessellate/GrStencilPathShader.cpp new file mode 100644 index 0000000000000..cf17222245b15 --- /dev/null +++ b/src/gpu/tessellate/GrStencilPathShader.cpp @@ -0,0 +1,220 @@ +/* + * 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/GrStencilPathShader.h" + +#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" +#include "src/gpu/glsl/GrGLSLVarying.h" +#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" + +// Wang's formula for cubics (1985) gives us the number of evenly spaced (in the +// parametric sense) line segments that are guaranteed to be within a distance of +// "MAX_LINEARIZATION_ERROR" from the actual curve. +constexpr char kWangsFormulaCubicFn[] = R"( + #define MAX_LINEARIZATION_ERROR 0.25 // 1/4 pixel + float wangs_formula_cubic(vec2 p0, vec2 p1, vec2 p2, vec2 p3) { + float k = (3.0 * 2.0) / (8.0 * MAX_LINEARIZATION_ERROR); + float f = sqrt(k * length(max(abs(p2 - p1*2.0 + p0), + abs(p3 - p2*2.0 + p1)))); + return max(1.0, ceil(f)); + })"; + +// Evaluate our point of interest using numerically stable mix() operations. +constexpr char kEvalCubicFn[] = R"( + vec2 eval_cubic(mat4x2 P, float T) { + vec2 ab = mix(P[0], P[1], T); + vec2 bc = mix(P[1], P[2], T); + vec2 cd = mix(P[2], P[3], T); + vec2 abc = mix(ab, bc, T); + vec2 bcd = mix(bc, cd, T); + return mix(abc, bcd, T); + })"; + +class GrStencilPathShader::Impl : public GrGLSLGeometryProcessor { + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { + const auto& shader = args.fGP.cast(); + args.fVaryingHandler->emitAttributes(shader); + + GrShaderVar vertexPos = (*shader.vertexAttributes().begin()).asShaderVar(); + if (!shader.fViewMatrix.isIdentity()) { + const char* viewMatrix; + fViewMatrixUniform = args.fUniformHandler->addUniform( + kVertex_GrShaderFlag, kFloat3x3_GrSLType, "view_matrix", &viewMatrix); + args.fVertBuilder->codeAppendf( + "float2 vertexpos = (%s * float3(point, 1)).xy;", viewMatrix); + vertexPos.set(kFloat2_GrSLType, "vertexpos"); + } + + if (!shader.willUseTessellationShaders()) { + gpArgs->fPositionVar = vertexPos; + } else { + args.fVertBuilder->declareGlobal(GrShaderVar( + "P", kFloat2_GrSLType, GrShaderVar::kOut_TypeModifier)); + args.fVertBuilder->codeAppendf("P = %s;", vertexPos.c_str()); + } + + // No fragment shader. + } + + void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc, + const CoordTransformRange& transformRange) override { + const auto& shader = primProc.cast(); + if (!shader.fViewMatrix.isIdentity()) { + pdman.setSkMatrix(fViewMatrixUniform, shader.fViewMatrix); + } + } + + GrGLSLUniformHandler::UniformHandle fViewMatrixUniform; +}; + +GrGLSLPrimitiveProcessor* GrStencilPathShader::createGLSLInstance(const GrShaderCaps&) const { + return new Impl; +} + +SkString GrStencilCubicShader::getTessControlShaderGLSL(const char* versionAndExtensionDecls, + const GrShaderCaps&) const { + SkString code(versionAndExtensionDecls); + code.append(kWangsFormulaCubicFn); + code.append(R"( + layout(vertices = 1) out; + + in vec2 P[]; + out vec4 X[]; + out vec4 Y[]; + + void main() { + // Chop the curve at T=1/2. + vec2 ab = mix(P[0], P[1], .5); + vec2 bc = mix(P[1], P[2], .5); + vec2 cd = mix(P[2], P[3], .5); + vec2 abc = mix(ab, bc, .5); + vec2 bcd = mix(bc, cd, .5); + vec2 abcd = mix(abc, bcd, .5); + + // Calculate how many triangles we need to linearize each half of the curve. + float l0 = wangs_formula_cubic(P[0], ab, abc, abcd); + float l1 = wangs_formula_cubic(abcd, bcd, cd, P[3]); + + gl_TessLevelOuter[0] = l1; + gl_TessLevelOuter[1] = 1.0; + gl_TessLevelOuter[2] = l0; + + // Changing the inner level to 1 when l0 == l1 == 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(l0, l1), 2.0); + + X[gl_InvocationID /*== 0*/] = vec4(P[0].x, P[1].x, P[2].x, P[3].x); + Y[gl_InvocationID /*== 0*/] = vec4(P[0].y, P[1].y, P[2].y, P[3].y); + })"); + + return code; +} + +SkString GrStencilCubicShader::getTessEvaluationShaderGLSL(const char* versionAndExtensionDecls, + const GrShaderCaps&) const { + SkString code(versionAndExtensionDecls); + code.append(kEvalCubicFn); + code.append(R"( + layout(triangles, equal_spacing, ccw) in; + + uniform vec4 sk_RTAdjust; + + in vec4 X[]; + in vec4 Y[]; + + void main() { + // 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); + + mat4x2 P = transpose(mat2x4(X[0], Y[0])); + vec2 vertexpos = eval_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] + vertexpos + P[3]) / 3.0; + } + + gl_Position = vec4(vertexpos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0); + })"); + + return code; +} + +SkString GrStencilWedgeShader::getTessControlShaderGLSL(const char* versionAndExtensionDecls, + const GrShaderCaps&) const { + SkString code(versionAndExtensionDecls); + code.append(kWangsFormulaCubicFn); + code.append(R"( + layout(vertices = 1) out; + + in vec2 P[]; + out vec4 X[]; + out vec4 Y[]; + out vec2 fanpoint[]; + + void main() { + // Calculate how many triangles we need to linearize the curve. + float num_segments = wangs_formula_cubic(P[0], P[1], P[2], P[3]); + + // Tessellate the first side of the patch into num_segments triangles. + gl_TessLevelOuter[0] = num_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 num_segments == 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(num_segments, 2.0); + + X[gl_InvocationID /*== 0*/] = vec4(P[0].x, P[1].x, P[2].x, P[3].x); + Y[gl_InvocationID /*== 0*/] = vec4(P[0].y, P[1].y, P[2].y, P[3].y); + fanpoint[gl_InvocationID /*== 0*/] = P[4]; + })"); + + return code; +} + +SkString GrStencilWedgeShader::getTessEvaluationShaderGLSL(const char* versionAndExtensionDecls, + const GrShaderCaps&) const { + SkString code(versionAndExtensionDecls); + code.append(kEvalCubicFn); + code.append(R"( + layout(triangles, equal_spacing, ccw) in; + + uniform vec4 sk_RTAdjust; + + in vec4 X[]; + in vec4 Y[]; + 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; + + mat4x2 P = transpose(mat2x4(X[0], Y[0])); + vec2 vertexpos = eval_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] + vertexpos + P[3]) / 3.0; + } + + gl_Position = vec4(vertexpos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0); + })"); + + return code; +} diff --git a/src/gpu/tessellate/GrStencilPathShader.h b/src/gpu/tessellate/GrStencilPathShader.h new file mode 100644 index 0000000000000..f3ea703ef74cf --- /dev/null +++ b/src/gpu/tessellate/GrStencilPathShader.h @@ -0,0 +1,86 @@ +/* + * Copyright 2019 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrStencilPathShader_DEFINED +#define GrStencilPathShader_DEFINED + +#include "src/gpu/GrGeometryProcessor.h" + +// This is the base class for shaders that stencil path elements, namely, triangles, standalone +// cubics, and wedges. +class GrStencilPathShader : public GrGeometryProcessor { +public: + GrStencilPathShader(ClassID classID, const SkMatrix& viewMatrix, GrPrimitiveType primitiveType, + int tessellationPatchVertexCount = 0) + : GrGeometryProcessor(classID) + , fViewMatrix(viewMatrix) + , fPrimitiveType(primitiveType) + , fTessellationPatchVertexCount(tessellationPatchVertexCount) { + constexpr static Attribute kPointAttrib = { + "point", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; + this->setVertexAttributes(&kPointAttrib, 1); + if (fTessellationPatchVertexCount) { + this->setWillUseTessellationShaders(); + } + } + GrPrimitiveType primitiveType() const { return fPrimitiveType; } + int tessellationPatchVertexCount() const { return fTessellationPatchVertexCount; } + void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final { + b->add32(fViewMatrix.isIdentity()); + } + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final; + +private: + const SkMatrix fViewMatrix; + const GrPrimitiveType fPrimitiveType; + const int fTessellationPatchVertexCount; + + class Impl; +}; + +// Draws simple triangles to the stencil buffer. +class GrStencilTriangleShader : public GrStencilPathShader { +public: + GrStencilTriangleShader(const SkMatrix& viewMatrix) : GrStencilPathShader( + kTessellate_GrStencilTriangleShader_ClassID, viewMatrix, GrPrimitiveType::kTriangles) {} + const char* name() const override { return "tessellate_GrStencilTriangleShader"; } +}; + +// Uses GPU tessellation shaders to linearize, triangulate, and render standalone cubics. Here, a +// "cubic" is a standalone closed contour consisting of a single cubic bezier. +// TODO: Eventually we want to use rational cubic wedges in order to support perspective and conics. +class GrStencilCubicShader : public GrStencilPathShader { +public: + GrStencilCubicShader(const SkMatrix& viewMatrix) : GrStencilPathShader( + kTessellate_GrStencilCubicShader_ClassID, viewMatrix, GrPrimitiveType::kPatches, 4) {} + const char* name() const override { return "tessellate_GrStencilCubicShader"; } + +private: + SkString getTessControlShaderGLSL(const char* versionAndExtensionDecls, + const GrShaderCaps&) const override; + SkString getTessEvaluationShaderGLSL(const char* versionAndExtensionDecls, + const GrShaderCaps&) const override; +}; + +// 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 GrStencilWedgeShader : public GrStencilPathShader { +public: + GrStencilWedgeShader(const SkMatrix& viewMatrix) : GrStencilPathShader( + kTessellate_GrStencilWedgeShader_ClassID, viewMatrix, GrPrimitiveType::kPatches, 5) {} + const char* name() const override { return "tessellate_GrStencilWedgeShader"; } + +private: + SkString getTessControlShaderGLSL(const char* versionAndExtensionDecls, + const GrShaderCaps&) const override; + SkString getTessEvaluationShaderGLSL(const char* versionAndExtensionDecls, + const GrShaderCaps&) const override; +}; + +#endif diff --git a/src/gpu/tessellate/GrTessellatePathOp.cpp b/src/gpu/tessellate/GrTessellatePathOp.cpp index 1722f65a3d922..7b03923277755 100644 --- a/src/gpu/tessellate/GrTessellatePathOp.cpp +++ b/src/gpu/tessellate/GrTessellatePathOp.cpp @@ -13,7 +13,7 @@ #include "src/gpu/GrProgramInfo.h" #include "src/gpu/tessellate/GrCoverShader.h" #include "src/gpu/tessellate/GrPathParser.h" -#include "src/gpu/tessellate/GrTessellateWedgeShader.h" +#include "src/gpu/tessellate/GrStencilPathShader.h" GrTessellatePathOp::FixedFunctionFlags GrTessellatePathOp::fixedFunctionFlags() const { auto flags = FixedFunctionFlags::kUsesStencil; @@ -24,21 +24,51 @@ GrTessellatePathOp::FixedFunctionFlags GrTessellatePathOp::fixedFunctionFlags() } void GrTessellatePathOp::onPrepare(GrOpFlushState* state) { - int maxVertexCount = GrPathParser::MaxPossibleWedgeVertices(fPath); - if (auto* wedgeData = (SkPoint*)state->makeVertexSpace( - sizeof(SkPoint), maxVertexCount, &fWedgeBuffer, &fBaseWedgeVertex)) { - if (!(fWedgeVertexCount = GrPathParser::EmitCenterWedges(fPath, wedgeData))) { - fWedgeBuffer.reset(); + // First see if we should split up inner polygon triangles and curves, and triangulate the inner + // polygon(s) more efficiently. This causes greater CPU overhead due to the extra shaders and + // draw calls, but the better triangulation can reduce the rasterizer load by a great deal on + // complex paths. + const SkRect& bounds = fPath.getBounds(); + float scale = fViewMatrix.getMaxScale(); + // Raster-edge work is 1-dimensional, so we sum height and width rather than multiplying them. + float rasterEdgeWork = (bounds.height() + bounds.width()) * scale * fPath.countVerbs(); + if (rasterEdgeWork > 1000 * 1000) { + int numCurves = 0; + int maxVertexCount = GrPathParser::MaxInnerPolygonVertices(fPath); + if (auto* triangleData = (SkPoint*)state->makeVertexSpace( + sizeof(SkPoint), maxVertexCount, &fPathVertexBuffer, &fBasePathVertex)) { + if ((fPathVertexCount = + GrPathParser::EmitInnerPolygonTriangles(fPath, triangleData, &numCurves))) { + fPathShader = state->allocator()->make(fViewMatrix); + } else { + fPathVertexBuffer.reset(); + } + state->putBackVertices(maxVertexCount - fPathVertexCount, sizeof(SkPoint)); } - state->putBackVertices(maxVertexCount - fWedgeVertexCount, sizeof(SkPoint)); + if (numCurves) { + if (auto* cubicData = (SkPoint*)state->makeVertexSpace( + sizeof(SkPoint) * 4, numCurves, &fCubicInstanceBuffer, &fBaseCubicInstance)) { + fCubicInstanceCount = GrPathParser::EmitCubicInstances(fPath, cubicData); + SkASSERT(fCubicInstanceCount == numCurves); + } + } + return; } -} -void GrTessellatePathOp::onExecute(GrOpFlushState* state, const SkRect& chainBounds) { - if (!fWedgeBuffer) { - return; + // Fastest CPU approach: emit one cubic wedge per verb, fanning out from the center. + int maxVertexCount = GrPathParser::MaxWedgeVertices(fPath); + if (auto* patchData = (SkPoint*)state->makeVertexSpace( + sizeof(SkPoint), maxVertexCount, &fPathVertexBuffer, &fBasePathVertex)) { + if ((fPathVertexCount = GrPathParser::EmitCenterWedgePatches(fPath, patchData))) { + fPathShader = state->allocator()->make(fViewMatrix); + } else { + fPathVertexBuffer.reset(); + } + state->putBackVertices(maxVertexCount - fPathVertexCount, sizeof(SkPoint)); } +} +void GrTessellatePathOp::onExecute(GrOpFlushState* state, const SkRect& chainBounds) { GrAppliedClip clip = state->detachAppliedClip(); GrPipeline::FixedDynamicState fixedDynamicState; if (clip.scissorState().enabled()) { @@ -46,6 +76,7 @@ void GrTessellatePathOp::onExecute(GrOpFlushState* state, const SkRect& chainBou } this->drawStencilPass(state, clip.hardClip(), &fixedDynamicState); + if (!(Flags::kStencilOnly & fFlags)) { this->drawCoverPass(state, std::move(clip), &fixedDynamicState); } @@ -87,17 +118,35 @@ void GrTessellatePathOp::drawStencilPass(GrOpFlushState* state, const GrAppliedH initArgs.fCaps = &state->caps(); GrPipeline pipeline(initArgs, GrDisableColorXPFactory::MakeXferProcessor(), hardClip); - GrTessellateWedgeShader shader(fViewMatrix); - GrProgramInfo programInfo(state->proxy()->numSamples(), state->proxy()->numStencilSamples(), - state->proxy()->backendFormat(), state->view()->origin(), &pipeline, - &shader, fixedDynamicState, nullptr, 0, - GrPrimitiveType::kPatches, 5); - GrMesh mesh(GrPrimitiveType::kPatches, 5); - mesh.setNonIndexedNonInstanced(fWedgeVertexCount); - mesh.setVertexData(fWedgeBuffer, fBaseWedgeVertex); + if (fPathVertexBuffer) { + GrProgramInfo programInfo(state->proxy()->numSamples(), state->proxy()->numStencilSamples(), + state->proxy()->backendFormat(), state->view()->origin(), + &pipeline, fPathShader, fixedDynamicState, nullptr, 0, + fPathShader->primitiveType(), + fPathShader->tessellationPatchVertexCount()); - state->opsRenderPass()->draw(programInfo, &mesh, 1, this->bounds()); + GrMesh mesh(fPathShader->primitiveType(), fPathShader->tessellationPatchVertexCount()); + mesh.setNonIndexedNonInstanced(fPathVertexCount); + mesh.setVertexData(fPathVertexBuffer, fBasePathVertex); + + state->opsRenderPass()->draw(programInfo, &mesh, 1, this->bounds()); + } + + if (fCubicInstanceBuffer) { + // Here we treat the cubic instance buffer as tessellation patches. + GrStencilCubicShader shader(fViewMatrix); + GrProgramInfo programInfo(state->proxy()->numSamples(), state->proxy()->numStencilSamples(), + state->proxy()->backendFormat(), state->view()->origin(), + &pipeline, &shader, fixedDynamicState, nullptr, 0, + GrPrimitiveType::kPatches, 4); + + GrMesh mesh(GrPrimitiveType::kPatches, 4); + mesh.setNonIndexedNonInstanced(fCubicInstanceCount * 4); + mesh.setVertexData(fCubicInstanceBuffer, fBaseCubicInstance * 4); + + state->opsRenderPass()->draw(programInfo, &mesh, 1, this->bounds()); + } // http://skbug.com/9739 if (state->caps().requiresManualFBBarrierAfterTessellatedStencilDraw()) { diff --git a/src/gpu/tessellate/GrTessellatePathOp.h b/src/gpu/tessellate/GrTessellatePathOp.h index b33da8dac0047..0e17c415492d2 100644 --- a/src/gpu/tessellate/GrTessellatePathOp.h +++ b/src/gpu/tessellate/GrTessellatePathOp.h @@ -11,6 +11,7 @@ #include "src/gpu/ops/GrDrawOp.h" class GrAppliedHardClip; +class GrStencilPathShader; // Renders paths using the classic Red Book "stencil, then cover" method. Curves get linearized by // GPU tessellation shaders. This Op doesn't apply analytic AA, so it requires a render target that @@ -66,9 +67,21 @@ class GrTessellatePathOp : public GrDrawOp { SkPMColor4f fColor; GrProcessorSet fProcessors; - sk_sp fWedgeBuffer; - int fBaseWedgeVertex; - int fWedgeVertexCount; + // The "path vertex buffer" is made up of either inner polygon triangles (see + // GrPathParser::EmitInnerPolygonTriangles) or cubic wedge patches (see + // GrPathParser::EmitCenterWedgePatches). + sk_sp fPathVertexBuffer; + int fBasePathVertex; + int fPathVertexCount; + + // The "path shader" draws the above path geometry. + GrStencilPathShader* fPathShader; + + // The cubic instance buffer defines standalone cubics to tessellate into the stencil buffer, in + // addition to the above path geometry. + sk_sp fCubicInstanceBuffer; + int fBaseCubicInstance; + int fCubicInstanceCount; friend class GrOpMemoryPool; // For ctor. }; diff --git a/src/gpu/tessellate/GrTessellateWedgeShader.cpp b/src/gpu/tessellate/GrTessellateWedgeShader.cpp deleted file mode 100644 index 9fc1ad131c3b5..0000000000000 --- a/src/gpu/tessellate/GrTessellateWedgeShader.cpp +++ /dev/null @@ -1,133 +0,0 @@ -/* - * 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/GrTessellateWedgeShader.h" - -#include "src/gpu/glsl/GrGLSLGeometryProcessor.h" -#include "src/gpu/glsl/GrGLSLVarying.h" -#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" - -class GrTessellateWedgeShader::Impl : public GrGLSLGeometryProcessor { - void onEmitCode(EmitArgs&, GrGPArgs*) override; - void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc, - const CoordTransformRange& transformRange) override; - - GrGLSLUniformHandler::UniformHandle fViewMatrixUniform; -}; - -void GrTessellateWedgeShader::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) { - const char* viewMatrix; - fViewMatrixUniform = args.fUniformHandler->addUniform( - kVertex_GrShaderFlag, kFloat3x3_GrSLType, "view_matrix", &viewMatrix); - - args.fVaryingHandler->emitAttributes(args.fGP.cast()); - - args.fVertBuilder->declareGlobal(GrShaderVar( - "P_", kFloat2_GrSLType, GrShaderVar::kOut_TypeModifier)); - args.fVertBuilder->codeAppendf("P_ = (%s * float3(P, 1)).xy;", viewMatrix); - - // No fragment shader. -}; - -SkString GrTessellateWedgeShader::getTessControlShaderGLSL(const char* versionAndExtensionDecls, - const GrShaderCaps&) const { - SkString code(versionAndExtensionDecls); - code.append(R"( - layout(vertices = 1) out; - - in vec2 P_[]; - - out mat4x2 P[]; - out vec2 fanpoint[]; - - // Wang's formula for cubics (1985) gives us the number of evenly spaced (in the - // parametric sense) line segments that are guaranteed to be within a distance of - // "MAX_LINEARIZATION_ERROR" from the actual curve. - #define MAX_LINEARIZATION_ERROR 0.25 // 1/4 pixel - float wangs_formula_cubic(vec2 p0, vec2 p1, vec2 p2, vec2 p3) { - float k = (3.0 * 2.0) / (8.0 * MAX_LINEARIZATION_ERROR); - float f = sqrt(k * length(max(abs(p2 - p1*2.0 + p0), - abs(p3 - p2*2.0 + p1)))); - return max(1.0, ceil(f)); - } - - void main() { - // Calculate how many triangles we need to linearize the curve. - float num_segments = wangs_formula_cubic(P_[0], P_[1], P_[2], P_[3]); - - // Tessellate the first side of the patch into num_segments triangles. - gl_TessLevelOuter[0] = num_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 num_segments == 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(num_segments, 2.0); - - P[gl_InvocationID /*== 0*/] = mat4x2(P_[0], P_[1], P_[2], P_[3]); - fanpoint[gl_InvocationID /*== 0*/] = P_[4]; - })"); - - return code; -} - - -SkString GrTessellateWedgeShader::getTessEvaluationShaderGLSL(const char* versionAndExtensionDecls, - const GrShaderCaps&) const { - SkString code(versionAndExtensionDecls); - code.append(R"( - layout(triangles, equal_spacing, cw) in; - - uniform vec4 sk_RTAdjust; - - in mat4x2 P[]; - in vec2 fanpoint[]; - - void main() { - vec2 p0 = P[0][0], p1 = P[0][1], p2 = P[0][2], p3 = P[0][3]; - - // 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; - - // Evaluate our point of interest using numerically stable mix() operations. - vec2 ab = mix(p0, p1, T); - vec2 bc = mix(p1, p2, T); - vec2 cd = mix(p2, p3, T); - vec2 abc = mix(ab, bc, T); - vec2 bcd = mix(bc, cd, T); - vec2 vertexpos = mix(abc, bcd, 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 = (p0 + vertexpos + p3) / 3.0; - } - - gl_Position = vec4(vertexpos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0); - })"); - - return code; -} - -void GrTessellateWedgeShader::Impl::setData(const GrGLSLProgramDataManager& pdman, - const GrPrimitiveProcessor& primProc, - const CoordTransformRange& transformRange) { - const GrTessellateWedgeShader& shader = primProc.cast(); - pdman.setSkMatrix(fViewMatrixUniform, shader.fViewMatrix); -} - -GrGLSLPrimitiveProcessor* GrTessellateWedgeShader::createGLSLInstance(const GrShaderCaps&) const { - return new Impl; -} diff --git a/src/gpu/tessellate/GrTessellateWedgeShader.h b/src/gpu/tessellate/GrTessellateWedgeShader.h deleted file mode 100644 index 67d72856312c1..0000000000000 --- a/src/gpu/tessellate/GrTessellateWedgeShader.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2019 Google LLC. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#ifndef GrTessellateWedgeShader_DEFINED -#define GrTessellateWedgeShader_DEFINED - -#include "src/gpu/GrGeometryProcessor.h" - -// 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. We stencil paths by converting lines and quadratics -// to cubics, then rendering a cubic wedge for each verb. -// TODO: Eventually we want to use rational cubic wedges in order to support perspective and conics. -class GrTessellateWedgeShader : public GrGeometryProcessor { -public: - GrTessellateWedgeShader(const SkMatrix& viewMatrix) - : GrGeometryProcessor(kGrTessellateWedgeShader_ClassID) - , fViewMatrix(viewMatrix) { - static constexpr Attribute kPtAttrib = {"P", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; - this->setVertexAttributes(&kPtAttrib, 1); - this->setWillUseTessellationShaders(); - } - - const char* name() const override { return "GrTessellateWedgeShader"; } - void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final {} - GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final; - - SkString getTessControlShaderGLSL(const char* versionAndExtensionDecls, - const GrShaderCaps&) const override; - SkString getTessEvaluationShaderGLSL(const char* versionAndExtensionDecls, - const GrShaderCaps&) const override; - -private: - const SkMatrix fViewMatrix; - - class Impl; -}; - -#endif