Rewrite GrPathTessellateOp as GrPathInnerTriangulateOp
Simplifies the op to always triangulate the inner fan. We ensure this
will always work by using breadcrumb triangles. Also removes the
fail-on-non-simple mode from GrInnerFanTriangulator since it isn't
being used anymore.
Bug: skia:10419
Change-Id: Idb849712bf2bc4e5ef785bc3f9b8db03119d230e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/359565
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
diff --git a/bench/TessellateBench.cpp b/bench/TessellateBench.cpp
index a8dc005..51223dd 100644
--- a/bench/TessellateBench.cpp
+++ b/bench/TessellateBench.cpp
@@ -12,7 +12,7 @@
#include "src/gpu/GrOpFlushState.h"
#include "src/gpu/mock/GrMockOpTarget.h"
#include "src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h"
-#include "src/gpu/tessellate/GrPathTessellateOp.h"
+#include "src/gpu/tessellate/GrPathTessellator.h"
#include "src/gpu/tessellate/GrStrokeIndirectOp.h"
#include "src/gpu/tessellate/GrStrokeTessellateOp.h"
#include "src/gpu/tessellate/GrWangsFormula.h"
@@ -96,17 +96,17 @@
DEF_PATH_TESS_BENCH(GrPathIndirectTessellator, make_cubic_path(), SkMatrix::I()) {
GrPathIndirectTessellator tess(fMatrix, fPath, GrPathIndirectTessellator::DrawInnerFan::kNo);
- tess.prepare(fTarget.get(), fMatrix, fPath);
+ tess.prepare(fTarget.get(), fMatrix, fPath, nullptr);
}
DEF_PATH_TESS_BENCH(GrPathOuterCurveTessellator, make_cubic_path(), SkMatrix::I()) {
GrPathOuterCurveTessellator tess;
- tess.prepare(fTarget.get(), fMatrix, fPath);
+ tess.prepare(fTarget.get(), fMatrix, fPath, nullptr);
}
DEF_PATH_TESS_BENCH(GrPathWedgeTessellator, make_cubic_path(), SkMatrix::I()) {
GrPathWedgeTessellator tess;
- tess.prepare(fTarget.get(), fMatrix, fPath);
+ tess.prepare(fTarget.get(), fMatrix, fPath, nullptr);
}
static void benchmark_wangs_formula_cubic_log2(const SkMatrix& matrix, const SkPath& path) {
diff --git a/gm/largeclippedpath.cpp b/gm/largeclippedpath.cpp
new file mode 100644
index 0000000..9af338c
--- /dev/null
+++ b/gm/largeclippedpath.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2021 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm/gm.h"
+#include "include/core/SkCanvas.h"
+#include "include/core/SkPath.h"
+
+constexpr int kSize = 1000;
+
+// Makes sure GrPathInnerTriangulateOp uses correct stencil settings when there is a clip in the
+// stencil buffer.
+static void draw_clipped_flower(SkCanvas* canvas, SkPathFillType fillType) {
+ canvas->clear(SK_ColorCYAN);
+ SkPath clip;
+ clip.setFillType(SkPathFillType::kWinding);
+ constexpr static int kGridCount = 50;
+ constexpr static float kCellSize = (float)kSize / kGridCount;
+ for (int y = 0; y < kGridCount; ++y) {
+ clip.addRect(0, y * kCellSize, kSize, (y + 1) * kCellSize, SkPathDirection(y & 1));
+ }
+ for (int x = 0; x < kGridCount; ++x) {
+ clip.addRect(x * kCellSize, 0, (x + 1) * kCellSize, kSize, SkPathDirection(x & 1));
+ }
+ canvas->clipPath(clip);
+ SkPath flower;
+ flower.setFillType(fillType);
+ flower.moveTo(1, 0);
+ constexpr static int kNumPetals = 9;
+ for (int i = 1; i <= kNumPetals; ++i) {
+ float c = 2*SK_ScalarPI*(i - .5f) / kNumPetals;
+ float theta = 2*SK_ScalarPI*i / kNumPetals;
+ flower.quadTo(cosf(c)*2, sinf(c)*2, cosf(theta), sinf(theta));
+ }
+ flower.close();
+ flower.addArc(SkRect::MakeLTRB(-.75f, -.75f, .75f, .75f), 0, 360);
+ canvas->translate(kSize/2.f, kSize/2.f);
+ canvas->scale(kSize/3.f, kSize/3.f);
+ SkPaint p;
+ p.setAntiAlias(true);
+ p.setColor(SK_ColorMAGENTA);
+ canvas->drawPath(flower, p);
+}
+
+DEF_SIMPLE_GM(largeclippedpath_winding, canvas, kSize, kSize) {
+ draw_clipped_flower(canvas, SkPathFillType::kWinding);
+}
+
+DEF_SIMPLE_GM(largeclippedpath_evenodd, canvas, kSize, kSize) {
+ draw_clipped_flower(canvas, SkPathFillType::kEvenOdd);
+}
diff --git a/gn/gm.gni b/gn/gm.gni
index 0c05e95..d6cfe85 100644
--- a/gn/gm.gni
+++ b/gn/gm.gni
@@ -236,6 +236,7 @@
"$_gm/inversepaths.cpp",
"$_gm/jpg_color_cube.cpp",
"$_gm/labyrinth.cpp",
+ "$_gm/largeclippedpath.cpp",
"$_gm/largeglyphblur.cpp",
"$_gm/lattice.cpp",
"$_gm/lazytiling.cpp",
diff --git a/gn/gpu.gni b/gn/gpu.gni
index 620772f..24c0f02 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -472,9 +472,9 @@
"$_src/gpu/tessellate/GrFillPathShader.h",
"$_src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h",
"$_src/gpu/tessellate/GrMidpointContourParser.h",
+ "$_src/gpu/tessellate/GrPathInnerTriangulateOp.cpp",
+ "$_src/gpu/tessellate/GrPathInnerTriangulateOp.h",
"$_src/gpu/tessellate/GrPathShader.h",
- "$_src/gpu/tessellate/GrPathTessellateOp.cpp",
- "$_src/gpu/tessellate/GrPathTessellateOp.h",
"$_src/gpu/tessellate/GrPathTessellator.cpp",
"$_src/gpu/tessellate/GrPathTessellator.h",
"$_src/gpu/tessellate/GrStencilPathShader.cpp",
diff --git a/src/gpu/GrAATriangulator.cpp b/src/gpu/GrAATriangulator.cpp
index a058fad..9659548 100644
--- a/src/gpu/GrAATriangulator.cpp
+++ b/src/gpu/GrAATriangulator.cpp
@@ -617,10 +617,8 @@
this->mergeCoincidentVertices(&innerMesh, c, nullptr);
bool was_complex = this->mergeCoincidentVertices(&fOuterMesh, c, nullptr);
auto result = this->simplify(&innerMesh, c, nullptr);
- SkASSERT(SimplifyResult::kAbort != result);
was_complex = (SimplifyResult::kFoundSelfIntersection == result) || was_complex;
result = this->simplify(&fOuterMesh, c, nullptr);
- SkASSERT(SimplifyResult::kAbort != result);
was_complex = (SimplifyResult::kFoundSelfIntersection == result) || was_complex;
TESS_LOG("\ninner mesh before:\n");
DUMP_MESH(innerMesh);
@@ -642,7 +640,6 @@
SortedMerge(&innerMesh, &fOuterMesh, &aaMesh, c);
this->mergeCoincidentVertices(&aaMesh, c, nullptr);
result = this->simplify(&aaMesh, c, nullptr);
- SkASSERT(SimplifyResult::kAbort != result);
TESS_LOG("combined and simplified mesh:\n");
DUMP_MESH(aaMesh);
fOuterMesh.fHead = fOuterMesh.fTail = nullptr;
diff --git a/src/gpu/GrInnerFanTriangulator.h b/src/gpu/GrInnerFanTriangulator.h
index 2f98a87..fd1aeaa 100644
--- a/src/gpu/GrInnerFanTriangulator.h
+++ b/src/gpu/GrInnerFanTriangulator.h
@@ -17,10 +17,8 @@
public:
using GrTriangulator::BreadcrumbTriangleList;
- GrInnerFanTriangulator(const SkPath& path, SkArenaAlloc* alloc,
- bool disallowSelfIntersection = false)
+ GrInnerFanTriangulator(const SkPath& path, SkArenaAlloc* alloc)
: GrTriangulator(path, alloc) {
- fDisallowSelfIntersection = disallowSelfIntersection;
fCullCollinearVertices = false;
}
diff --git a/src/gpu/GrTriangulator.cpp b/src/gpu/GrTriangulator.cpp
index 00f275d..4c73d08 100644
--- a/src/gpu/GrTriangulator.cpp
+++ b/src/gpu/GrTriangulator.cpp
@@ -1197,9 +1197,6 @@
leftEnclosingEdge, edge, &activeEdges, &v, mesh, c, breadcrumbList) ||
this->checkForIntersection(
edge, rightEnclosingEdge, &activeEdges, &v, mesh, c, breadcrumbList)) {
- if (fDisallowSelfIntersection) {
- return SimplifyResult::kAbort;
- }
result = SimplifyResult::kFoundSelfIntersection;
restartChecks = true;
break;
@@ -1208,9 +1205,6 @@
} else {
if (this->checkForIntersection(leftEnclosingEdge, rightEnclosingEdge, &activeEdges,
&v, mesh, c, breadcrumbList)) {
- if (fDisallowSelfIntersection) {
- return SimplifyResult::kAbort;
- }
result = SimplifyResult::kFoundSelfIntersection;
restartChecks = true;
}
@@ -1237,10 +1231,6 @@
Poly* GrTriangulator::tessellate(const VertexList& vertices, const Comparator&) const {
TESS_LOG("\ntessellating simple polygons\n");
- int maxWindMagnitude = std::numeric_limits<int>::max();
- if (fDisallowSelfIntersection && !SkPathFillType_IsEvenOdd(fPath.getFillType())) {
- maxWindMagnitude = 1;
- }
EdgeList activeEdges;
Poly* polys = nullptr;
for (Vertex* v = vertices.fHead; v != nullptr; v = v->fNext) {
@@ -1333,9 +1323,6 @@
int winding = leftEdge->fLeftPoly ? leftEdge->fLeftPoly->fWinding : 0;
winding += leftEdge->fWinding;
if (winding != 0) {
- if (abs(winding) > maxWindMagnitude) {
- return nullptr; // We can't have weighted wind in kSimpleInnerPolygons mode
- }
Poly* poly = this->makePoly(&polys, v, winding);
leftEdge->fRightPoly = rightEdge->fLeftPoly = poly;
}
@@ -1403,9 +1390,7 @@
this->contoursToMesh(contours, contourCnt, &mesh, c, breadcrumbList);
SortMesh(&mesh, c);
this->mergeCoincidentVertices(&mesh, c, breadcrumbList);
- if (SimplifyResult::kAbort == this->simplify(&mesh, c, breadcrumbList)) {
- return nullptr;
- }
+ this->simplify(&mesh, c, breadcrumbList);
TESS_LOG("\nsimplified mesh:\n");
DUMP_MESH(mesh);
return this->tessellate(mesh, c);
diff --git a/src/gpu/GrTriangulator.h b/src/gpu/GrTriangulator.h
index 33f30657..1cc31e2 100644
--- a/src/gpu/GrTriangulator.h
+++ b/src/gpu/GrTriangulator.h
@@ -135,10 +135,9 @@
static void SortMesh(VertexList* vertices, const Comparator&);
// 4) Simplify the mesh by inserting new vertices at intersecting edges:
- enum class SimplifyResult {
+ enum class SimplifyResult : bool {
kAlreadySimple,
- kFoundSelfIntersection,
- kAbort
+ kFoundSelfIntersection
};
SimplifyResult simplify(VertexList* mesh, const Comparator&, BreadcrumbTriangleList*) const;
@@ -249,7 +248,6 @@
bool fRoundVerticesToQuarterPixel = false;
bool fEmitCoverage = false;
bool fCullCollinearVertices = true;
- bool fDisallowSelfIntersection = false;
};
/**
diff --git a/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp b/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp
new file mode 100644
index 0000000..a47a3cb
--- /dev/null
+++ b/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp
@@ -0,0 +1,279 @@
+/*
+ * 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/GrPathInnerTriangulateOp.h"
+
+#include "src/gpu/GrEagerVertexAllocator.h"
+#include "src/gpu/GrInnerFanTriangulator.h"
+#include "src/gpu/GrOpFlushState.h"
+#include "src/gpu/GrRecordingContextPriv.h"
+#include "src/gpu/tessellate/GrFillPathShader.h"
+#include "src/gpu/tessellate/GrPathTessellator.h"
+#include "src/gpu/tessellate/GrStencilPathShader.h"
+#include "src/gpu/tessellate/GrTessellationPathRenderer.h"
+
+using OpFlags = GrTessellationPathRenderer::OpFlags;
+
+void GrPathInnerTriangulateOp::visitProxies(const VisitProxyFunc& fn) const {
+ if (fPipelineForFills) {
+ fPipelineForFills->visitProxies(fn);
+ } else {
+ fProcessors.visitProxies(fn);
+ }
+}
+
+GrDrawOp::FixedFunctionFlags GrPathInnerTriangulateOp::fixedFunctionFlags() const {
+ auto flags = FixedFunctionFlags::kUsesStencil;
+ if (GrAAType::kNone != fAAType) {
+ flags |= FixedFunctionFlags::kUsesHWAA;
+ }
+ return flags;
+}
+
+GrProcessorSet::Analysis GrPathInnerTriangulateOp::finalize(const GrCaps& caps,
+ const GrAppliedClip* clip,
+ bool hasMixedSampledCoverage,
+ GrClampType clampType) {
+ return fProcessors.finalize(fColor, GrProcessorAnalysisCoverage::kNone, clip, nullptr,
+ hasMixedSampledCoverage, caps, clampType, &fColor);
+}
+
+void GrPathInnerTriangulateOp::pushFanStencilProgram(const GrPathShader::ProgramArgs& args,
+ const GrPipeline* pipelineForStencils,
+ const GrUserStencilSettings* stencil) {
+ SkASSERT(pipelineForStencils);
+ fFanPrograms.push_back(GrStencilPathShader::MakeStencilProgram<GrStencilTriangleShader>(
+ args, fViewMatrix, pipelineForStencils, stencil));
+}
+
+void GrPathInnerTriangulateOp::pushFanFillProgram(const GrPathShader::ProgramArgs& args,
+ const GrUserStencilSettings* stencil) {
+ SkASSERT(fPipelineForFills);
+ auto* shader = args.fArena->make<GrFillTriangleShader>(fViewMatrix, fColor);
+ fFanPrograms.push_back(GrPathShader::MakeProgram(args, shader, fPipelineForFills, stencil));
+}
+
+void GrPathInnerTriangulateOp::prePreparePrograms(const GrPathShader::ProgramArgs& args,
+ GrAppliedClip&& appliedClip) {
+ SkASSERT(!fFanTriangulator);
+ SkASSERT(!fFanPolys);
+ SkASSERT(!fPipelineForFills);
+ SkASSERT(!fTessellator);
+ SkASSERT(!fStencilCurvesProgram);
+ SkASSERT(fFanPrograms.empty());
+ SkASSERT(!fFillHullsProgram);
+
+ if (fPath.countVerbs() <= 0) {
+ return;
+ }
+
+ // If using wireframe or mixed samples, we have to fall back on a standard Redbook "stencil then
+ // fill" algorithm instead of bypassing the stencil buffer to fill the fan directly.
+ bool forceRedbookStencilPass = (fOpFlags & (OpFlags::kStencilOnly | OpFlags::kWireframe)) ||
+ fAAType == GrAAType::kCoverage; // i.e., mixed samples!
+ bool doFill = !(fOpFlags & OpFlags::kStencilOnly);
+
+ bool isLinear;
+ fFanTriangulator = args.fArena->make<GrInnerFanTriangulator>(fPath, args.fArena);
+ fFanPolys = fFanTriangulator->pathToPolys(&fFanBreadcrumbs, &isLinear);
+
+ // 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());
+ }
+
+ // Create a pipeline for fill passes if needed.
+ if (doFill) {
+ fPipelineForFills = GrFillPathShader::MakeFillPassPipeline(args, fAAType,
+ std::move(appliedClip),
+ std::move(fProcessors));
+ }
+
+ // Pass 1: Tessellate the outer curves into the stencil buffer.
+ if (!isLinear) {
+ // Always use indirect draws for now. Our goal in this op is to maximize GPU performance,
+ // 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.
+ using DrawInnerFan = GrPathIndirectTessellator::DrawInnerFan;
+ fTessellator = args.fArena->make<GrPathIndirectTessellator>(fViewMatrix, fPath,
+ DrawInnerFan::kNo);
+ fStencilCurvesProgram = GrStencilPathShader::MakeStencilProgram<GrMiddleOutCubicShader>(
+ args, fViewMatrix, pipelineForStencils, fPath.getFillType());
+ }
+
+ // Pass 2: Fill the path's inner fan with a stencil test against the curves.
+ if (fFanPolys) {
+ 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()));
+ if (doFill) {
+ this->pushFanFillProgram(args, GrFillPathShader::TestAndResetStencilSettings());
+ }
+ } else if (isLinear) {
+ // There are no outer curves! Ignore stencil and fill the path directly.
+ SkASSERT(!pipelineForStencils);
+ this->pushFanFillProgram(args, &GrUserStencilSettings::kUnused);
+ } else if (!fPipelineForFills->hasStencilClip()) {
+ // These are a twist on the standard Redbook stencil settings that allow us to fill the
+ // inner polygon directly to the final render target. By the time these programs
+ // execute, the outer curves will already be stencilled in. So if the stencil value is
+ // zero, then it means the sample in question is not affected by any curves and we can
+ // fill it in directly. If the stencil value is nonzero, then we don't fill and instead
+ // continue the standard Redbook counting process.
+ constexpr static GrUserStencilSettings kFillOrIncrDecrStencil(
+ GrUserStencilSettings::StaticInitSeparate<
+ 0x0000, 0x0000,
+ GrUserStencilTest::kEqual, GrUserStencilTest::kEqual,
+ 0xffff, 0xffff,
+ GrUserStencilOp::kKeep, GrUserStencilOp::kKeep,
+ GrUserStencilOp::kIncWrap, GrUserStencilOp::kDecWrap,
+ 0xffff, 0xffff>());
+
+ constexpr static GrUserStencilSettings kFillOrInvertStencil(
+ GrUserStencilSettings::StaticInit<
+ 0x0000,
+ GrUserStencilTest::kEqual,
+ 0xffff,
+ GrUserStencilOp::kKeep,
+ // "Zero" instead of "Invert" because the fan only touches any given pixel once.
+ GrUserStencilOp::kZero,
+ 0xffff>());
+
+ auto* stencil = (fPath.getFillType() == SkPathFillType::kWinding)
+ ? &kFillOrIncrDecrStencil
+ : &kFillOrInvertStencil;
+ this->pushFanFillProgram(args, stencil);
+ } else {
+ // This is the same idea as above, but we use two passes instead of one because there is
+ // a stencil clip. The stencil test isn't expressive enough to do the above tests and
+ // also check the clip bit in a single pass.
+ constexpr static GrUserStencilSettings kFillIfZeroAndInClip(
+ GrUserStencilSettings::StaticInit<
+ 0x0000,
+ GrUserStencilTest::kEqualIfInClip,
+ 0xffff,
+ GrUserStencilOp::kKeep,
+ GrUserStencilOp::kKeep,
+ 0xffff>());
+
+ constexpr static GrUserStencilSettings kIncrDecrStencilIfNonzero(
+ GrUserStencilSettings::StaticInitSeparate<
+ 0x0000, 0x0000,
+ // No need to check the clip because the previous stencil pass will have only
+ // written to samples already inside the clip.
+ GrUserStencilTest::kNotEqual, GrUserStencilTest::kNotEqual,
+ 0xffff, 0xffff,
+ GrUserStencilOp::kIncWrap, GrUserStencilOp::kDecWrap,
+ GrUserStencilOp::kKeep, GrUserStencilOp::kKeep,
+ 0xffff, 0xffff>());
+
+ constexpr static GrUserStencilSettings kInvertStencilIfNonZero(
+ GrUserStencilSettings::StaticInit<
+ 0x0000,
+ // No need to check the clip because the previous stencil pass will have only
+ // written to samples already inside the clip.
+ GrUserStencilTest::kNotEqual,
+ 0xffff,
+ // "Zero" instead of "Invert" because the fan only touches any given pixel once.
+ GrUserStencilOp::kZero,
+ GrUserStencilOp::kKeep,
+ 0xffff>());
+
+ // Pass 2a: Directly fill fan samples whose stencil values (from curves) are zero.
+ this->pushFanFillProgram(args, &kFillIfZeroAndInClip);
+
+ // Pass 2b: Redbook counting on fan samples whose stencil values (from curves) != 0.
+ auto* stencil = (fPath.getFillType() == SkPathFillType::kWinding)
+ ? &kIncrDecrStencilIfNonzero
+ : &kInvertStencilIfNonZero;
+ this->pushFanStencilProgram(args, pipelineForStencils, stencil);
+ }
+ }
+
+ // Pass 3: Draw convex hulls around each curve.
+ if (doFill && !isLinear) {
+ // By the time this program executes, every pixel will be filled in except the ones touched
+ // 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(
+ args, hullShader, fPipelineForFills,
+ GrFillPathShader::TestAndResetStencilSettings());
+ }
+}
+
+void GrPathInnerTriangulateOp::onPrePrepare(GrRecordingContext* context,
+ const GrSurfaceProxyView& writeView,
+ GrAppliedClip* clip,
+ const GrXferProcessor::DstProxyView& dstProxyView,
+ GrXferBarrierFlags renderPassXferBarriers,
+ GrLoadOp colorLoadOp) {
+ this->prePreparePrograms({context->priv().recordTimeAllocator(), writeView, &dstProxyView,
+ renderPassXferBarriers, colorLoadOp, context->priv().caps()},
+ (clip) ? std::move(*clip) : GrAppliedClip::Disabled());
+ if (fStencilCurvesProgram) {
+ context->priv().recordProgramInfo(fStencilCurvesProgram);
+ }
+ for (const GrProgramInfo* fanProgram : fFanPrograms) {
+ context->priv().recordProgramInfo(fanProgram);
+ }
+ if (fFillHullsProgram) {
+ context->priv().recordProgramInfo(fFillHullsProgram);
+ }
+}
+
+void GrPathInnerTriangulateOp::onPrepare(GrOpFlushState* flushState) {
+ if (!fFanTriangulator) {
+ this->prePreparePrograms({flushState->allocator(), flushState->writeView(),
+ &flushState->dstProxyView(), flushState->renderPassBarriers(),
+ flushState->colorLoadOp(), &flushState->caps()},
+ flushState->detachAppliedClip());
+ if (!fFanTriangulator) {
+ return;
+ }
+ }
+
+ if (fFanPolys) {
+ GrEagerDynamicVertexAllocator alloc(flushState, &fFanBuffer, &fBaseFanVertex);
+ fFanVertexCount = fFanTriangulator->polysToTriangles(fFanPolys, &alloc, &fFanBreadcrumbs);
+ }
+
+ if (fTessellator) {
+ // Must be called after polysToTriangles() in order for fFanBreadcrumbs to be complete.
+ fTessellator->prepare(flushState, fViewMatrix, fPath, &fFanBreadcrumbs);
+ }
+}
+
+void GrPathInnerTriangulateOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
+ if (fStencilCurvesProgram) {
+ SkASSERT(fTessellator);
+ flushState->bindPipelineAndScissorClip(*fStencilCurvesProgram, this->bounds());
+ fTessellator->draw(flushState);
+ }
+
+ for (const GrProgramInfo* fanProgram : fFanPrograms) {
+ SkASSERT(fFanBuffer);
+ flushState->bindPipelineAndScissorClip(*fanProgram, this->bounds());
+ flushState->bindTextures(fanProgram->primProc(), nullptr, fanProgram->pipeline());
+ flushState->bindBuffers(nullptr, nullptr, fFanBuffer);
+ flushState->draw(fFanVertexCount, fBaseFanVertex);
+ }
+
+ if (fFillHullsProgram) {
+ SkASSERT(fTessellator);
+ flushState->bindPipelineAndScissorClip(*fFillHullsProgram, this->bounds());
+ flushState->bindTextures(fFillHullsProgram->primProc(), nullptr, *fPipelineForFills);
+ fTessellator->drawHullInstances(flushState);
+ }
+}
diff --git a/src/gpu/tessellate/GrPathInnerTriangulateOp.h b/src/gpu/tessellate/GrPathInnerTriangulateOp.h
new file mode 100644
index 0000000..70ffa7f
--- /dev/null
+++ b/src/gpu/tessellate/GrPathInnerTriangulateOp.h
@@ -0,0 +1,98 @@
+/*
+ * 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 GrPathInnerTriangulateOp_DEFINED
+#define GrPathInnerTriangulateOp_DEFINED
+
+#include "src/gpu/GrInnerFanTriangulator.h"
+#include "src/gpu/ops/GrDrawOp.h"
+#include "src/gpu/tessellate/GrPathShader.h"
+#include "src/gpu/tessellate/GrTessellationPathRenderer.h"
+
+class GrPathTessellator;
+
+// This op is a 3-pass twist on the standard Redbook "stencil then fill" algorithm:
+//
+// 1) Tessellate the path's outer curves into the stencil buffer.
+// 2) Triangulate the path's inner fan and fill it with a stencil test against the curves.
+// 3) Draw convex hulls around each curve that fill in remaining samples.
+//
+// In practice, a path's inner fan takes up a large majority of its pixels. So from a GPU load
+// perspective, this op is effectively as fast as a single-pass algorithm.
+class GrPathInnerTriangulateOp : public GrDrawOp {
+private:
+ DEFINE_OP_CLASS_ID
+
+ GrPathInnerTriangulateOp(const SkMatrix& viewMatrix, const SkPath& path, GrPaint&& paint,
+ GrAAType aaType, GrTessellationPathRenderer::OpFlags opFlags)
+ : GrDrawOp(ClassID())
+ , fOpFlags(opFlags)
+ , fViewMatrix(viewMatrix)
+ , fPath(path)
+ , fAAType(aaType)
+ , fColor(paint.getColor4f())
+ , fProcessors(std::move(paint)) {
+ SkRect devBounds;
+ fViewMatrix.mapRect(&devBounds, path.getBounds());
+ this->setBounds(devBounds, HasAABloat(GrAAType::kCoverage == fAAType), IsHairline::kNo);
+ }
+
+ const char* name() const override { return "GrPathInnerTriangulateOp"; }
+ void visitProxies(const VisitProxyFunc& fn) const override;
+ FixedFunctionFlags fixedFunctionFlags() const override;
+ GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*,
+ bool hasMixedSampledCoverage, GrClampType) override;
+
+ // These calls set up the stencil & fill programs we will use prior to preparing and executing.
+ void pushFanStencilProgram(const GrPathShader::ProgramArgs&,
+ const GrPipeline* pipelineForStencils, const GrUserStencilSettings*);
+ void pushFanFillProgram(const GrPathShader::ProgramArgs&, const GrUserStencilSettings*);
+ void prePreparePrograms(const GrPathShader::ProgramArgs&, GrAppliedClip&&);
+
+ void onPrePrepare(GrRecordingContext*, const GrSurfaceProxyView&, GrAppliedClip*,
+ const GrXferProcessor::DstProxyView&, GrXferBarrierFlags,
+ GrLoadOp colorLoadOp) override;
+ void onPrepare(GrOpFlushState*) override;
+ void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
+
+ const GrTessellationPathRenderer::OpFlags fOpFlags;
+ const SkMatrix fViewMatrix;
+ const SkPath fPath;
+ const GrAAType fAAType;
+ SkPMColor4f fColor;
+ GrProcessorSet fProcessors;
+
+ // Triangulates the inner fan.
+ GrInnerFanTriangulator* fFanTriangulator = nullptr;
+ GrTriangulator::Poly* fFanPolys = nullptr;
+ GrInnerFanTriangulator::BreadcrumbTriangleList fFanBreadcrumbs;
+
+ // This pipeline is shared by all programs that do filling.
+ const GrPipeline* fPipelineForFills = nullptr;
+
+ // Tessellates the outer curves.
+ GrPathTessellator* fTessellator = nullptr;
+
+ // Pass 1: Tessellate the outer curves into the stencil buffer.
+ const GrProgramInfo* fStencilCurvesProgram = nullptr;
+
+ // Pass 2: Fill the path's inner fan with a stencil test against the curves. (In extenuating
+ // circumstances this might require two separate draws.)
+ SkSTArray<2, const GrProgramInfo*> fFanPrograms;
+
+ // Pass 3: Draw convex hulls around each curve.
+ const GrProgramInfo* fFillHullsProgram = nullptr;
+
+ // This buffer gets created by fFanTriangulator during onPrepare.
+ sk_sp<const GrBuffer> fFanBuffer;
+ int fBaseFanVertex = 0;
+ int fFanVertexCount = 0;
+
+ friend class GrOp; // For ctor.
+};
+
+#endif
diff --git a/src/gpu/tessellate/GrPathTessellateOp.cpp b/src/gpu/tessellate/GrPathTessellateOp.cpp
deleted file mode 100644
index ce449ea..0000000
--- a/src/gpu/tessellate/GrPathTessellateOp.cpp
+++ /dev/null
@@ -1,457 +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/GrPathTessellateOp.h"
-
-#include "src/gpu/GrEagerVertexAllocator.h"
-#include "src/gpu/GrInnerFanTriangulator.h"
-#include "src/gpu/GrOpFlushState.h"
-#include "src/gpu/GrRecordingContextPriv.h"
-#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h"
-#include "src/gpu/tessellate/GrFillPathShader.h"
-#include "src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h"
-#include "src/gpu/tessellate/GrStencilPathShader.h"
-#include "src/gpu/tessellate/GrTessellationPathRenderer.h"
-
-using OpFlags = GrTessellationPathRenderer::OpFlags;
-
-void GrPathTessellateOp::visitProxies(const VisitProxyFunc& fn) const {
- if (fPipelineForFills) {
- fPipelineForFills->visitProxies(fn);
- } else {
- fProcessors.visitProxies(fn);
- }
-}
-
-GrPathTessellateOp::FixedFunctionFlags GrPathTessellateOp::fixedFunctionFlags() const {
- auto flags = FixedFunctionFlags::kUsesStencil;
- if (GrAAType::kNone != fAAType) {
- flags |= FixedFunctionFlags::kUsesHWAA;
- }
- return flags;
-}
-
-void GrPathTessellateOp::onPrePrepare(GrRecordingContext* context,
- const GrSurfaceProxyView& writeView, GrAppliedClip* clip,
- const GrXferProcessor::DstProxyView& dstProxyView,
- GrXferBarrierFlags renderPassXferBarriers,
- GrLoadOp colorLoadOp) {
- SkArenaAlloc* recordTimeAllocator = context->priv().recordTimeAllocator();
- GrAppliedHardClip hardClip = GrAppliedHardClip(
- (clip) ? clip->hardClip() : GrAppliedHardClip::Disabled());
- PrePrepareArgs args{recordTimeAllocator, writeView, &hardClip, clip, &dstProxyView,
- renderPassXferBarriers, colorLoadOp, context->priv().caps()};
-
- this->prePreparePrograms(args);
-
- if (fStencilTrianglesProgram) {
- context->priv().recordProgramInfo(fStencilTrianglesProgram);
- }
- if (fStencilCubicsProgram) {
- context->priv().recordProgramInfo(fStencilCubicsProgram);
- }
- if (fFillTrianglesProgram) {
- context->priv().recordProgramInfo(fFillTrianglesProgram);
- }
- if (fFillPathProgram) {
- context->priv().recordProgramInfo(fFillPathProgram);
- }
-}
-
-void GrPathTessellateOp::prePreparePrograms(const PrePrepareArgs& args) {
- using DrawInnerFan = GrPathIndirectTessellator::DrawInnerFan;
- int numVerbs = fPath.countVerbs();
- if (numVerbs <= 0) {
- return;
- }
-
- // First check if the path is large and/or simple enough that we can actually triangulate the
- // inner polygon(s) on the CPU. This is our fastest approach. It allows us to stencil only the
- // curves, and then fill the internal polygons directly to the final render target, thus drawing
- // the majority of pixels in a single render pass.
- SkScalar scales[2];
- SkAssertResult(fViewMatrix.getMinMaxScales(scales)); // Will fail if perspective.
- const SkRect& bounds = fPath.getBounds();
- float gpuFragmentWork = bounds.height() * scales[0] * bounds.width() * scales[1];
- float cpuTessellationWork = (float)numVerbs * SkNextLog2(numVerbs); // N log N.
- if (cpuTessellationWork * 500 + (256 * 256) < gpuFragmentWork) { // Don't try below 256x256.
- bool isLinear;
- // This will fail if the inner triangles do not form a simple polygon (e.g., self
- // intersection, double winding).
- if (this->prePrepareInnerPolygonTriangulation(args, &isLinear)) {
- if (!isLinear) {
- // Always use indirect draws for cubics instead of tessellation here. Our goal in
- // this mode is to maximize GPU performance, and the middle-out topology used by our
- // indirect draws is easier on the rasterizer than a tessellated fan. There also
- // seems to be a small amount of fixed tessellation overhead that this avoids.
- this->prePrepareStencilCubicsProgram<GrMiddleOutCubicShader>(args);
- // We will need one final pass to cover the convex hulls of the cubics after
- // drawing the inner triangles.
- this->prePrepareFillCubicHullsProgram(args);
- fTessellator = args.fArena->make<GrPathIndirectTessellator>(fViewMatrix, fPath,
- DrawInnerFan::kNo);
- }
- return;
- }
- }
-
- // If we didn't triangulate the inner fan then the fill program will be a simple bounding box.
- this->prePrepareFillBoundingBoxProgram(args);
-
- // When there are only a few verbs, it seems to always be fastest to make a single indirect draw
- // that contains both the inner triangles and the outer cubics, instead of using hardware
- // tessellation. Also take this path if tessellation is not supported.
- bool drawTrianglesAsIndirectCubicDraw = (numVerbs < 50);
- if (drawTrianglesAsIndirectCubicDraw || (fOpFlags & OpFlags::kDisableHWTessellation)) {
- if (!drawTrianglesAsIndirectCubicDraw) {
- this->prePrepareStencilTrianglesProgram(args);
- }
- this->prePrepareStencilCubicsProgram<GrMiddleOutCubicShader>(args);
- fTessellator = args.fArena->make<GrPathIndirectTessellator>(
- fViewMatrix, fPath, DrawInnerFan(drawTrianglesAsIndirectCubicDraw));
- return;
- }
-
- // The caller should have sent Flags::kDisableHWTessellation if it was not supported.
- SkASSERT(args.fCaps->shaderCaps()->tessellationSupport());
-
- // Next see if we can split up the inner triangles and outer cubics into two draw calls. This
- // allows for a more efficient inner triangle topology that can reduce the rasterizer load by a
- // large margin on complex paths, but also causes greater CPU overhead due to the extra shader
- // switches and draw calls.
- // NOTE: Raster-edge work is 1-dimensional, so we sum height and width instead of multiplying.
- float rasterEdgeWork = (bounds.height() + bounds.width()) * scales[1] * fPath.countVerbs();
- if (rasterEdgeWork > 300 * 300) {
- this->prePrepareStencilTrianglesProgram(args);
- this->prePrepareStencilCubicsProgram<GrCubicTessellateShader>(args);
- fTessellator = args.fArena->make<GrPathOuterCurveTessellator>();
- return;
- }
-
- // Fastest CPU approach: emit one cubic wedge per verb, fanning out from the center.
- this->prePrepareStencilCubicsProgram<GrWedgeTessellateShader>(args);
- fTessellator = args.fArena->make<GrPathWedgeTessellator>();
-}
-
-bool GrPathTessellateOp::prePrepareInnerPolygonTriangulation(const PrePrepareArgs& args,
- bool* isLinear) {
- SkASSERT(!fTriangleBuffer);
- SkASSERT(fTriangleVertexCount == 0);
- SkASSERT(!fStencilTrianglesProgram);
- SkASSERT(!fFillTrianglesProgram);
- fInnerFanTriangulator = args.fArena->make<GrInnerFanTriangulator>(fPath, args.fArena, true);
- fInnerFanPolys = fInnerFanTriangulator->pathToPolys(nullptr, isLinear);
- if (!fInnerFanPolys) {
- // pathToPolys will fail if the inner polygon(s) are not simple.
- return false;
- }
- if ((fOpFlags & (OpFlags::kStencilOnly | OpFlags::kWireframe)) ||
- GrAAType::kCoverage == fAAType ||
- (args.fClip && args.fClip->hasStencilClip())) {
- // If we have certain flags, mixed samples, or a stencil clip then we unfortunately
- // can't fill the inner polygon directly. Indicate that these triangles need to be
- // stencilled.
- this->prePrepareStencilTrianglesProgram(args);
- }
- this->prePrepareFillTrianglesProgram(args, *isLinear);
- return true;
-}
-
-// Increments clockwise triangles and decrements counterclockwise. Used for "winding" fill.
-constexpr static GrUserStencilSettings kIncrDecrStencil(
- GrUserStencilSettings::StaticInitSeparate<
- 0x0000, 0x0000,
- GrUserStencilTest::kAlwaysIfInClip, GrUserStencilTest::kAlwaysIfInClip,
- 0xffff, 0xffff,
- GrUserStencilOp::kIncWrap, GrUserStencilOp::kDecWrap,
- GrUserStencilOp::kKeep, GrUserStencilOp::kKeep,
- 0xffff, 0xffff>());
-
-// Inverts the bottom stencil bit. Used for "even/odd" fill.
-constexpr static GrUserStencilSettings kInvertStencil(
- GrUserStencilSettings::StaticInit<
- 0x0000,
- GrUserStencilTest::kAlwaysIfInClip,
- 0xffff,
- GrUserStencilOp::kInvert,
- GrUserStencilOp::kKeep,
- 0x0001>());
-
-constexpr static const GrUserStencilSettings* stencil_pass_settings(SkPathFillType fillType) {
- return (fillType == SkPathFillType::kWinding) ? &kIncrDecrStencil : &kInvertStencil;
-}
-
-void GrPathTessellateOp::prePrepareStencilTrianglesProgram(const PrePrepareArgs& args) {
- SkASSERT(!fStencilTrianglesProgram);
-
- this->prePreparePipelineForStencils(args);
-
- auto* shader = args.fArena->make<GrStencilTriangleShader>(fViewMatrix);
- fStencilTrianglesProgram = GrPathShader::MakeProgramInfo(
- shader, args.fArena, args.fWriteView, fPipelineForStencils, *args.fDstProxyView,
- args.fXferBarrierFlags, args.fColorLoadOp, stencil_pass_settings(fPath.getFillType()),
- *args.fCaps);
-}
-
-template<typename ShaderType>
-void GrPathTessellateOp::prePrepareStencilCubicsProgram(const PrePrepareArgs& args) {
- SkASSERT(!fStencilCubicsProgram);
-
- this->prePreparePipelineForStencils(args);
-
- auto* shader = args.fArena->make<ShaderType>(fViewMatrix);
- fStencilCubicsProgram = GrPathShader::MakeProgramInfo(
- shader, args.fArena, args.fWriteView, fPipelineForStencils, *args.fDstProxyView,
- args.fXferBarrierFlags, args.fColorLoadOp, stencil_pass_settings(fPath.getFillType()),
- *args.fCaps);
-}
-
-void GrPathTessellateOp::prePreparePipelineForStencils(const PrePrepareArgs& args) {
- if (fPipelineForStencils) {
- return;
- }
-
- GrPipeline::InitArgs initArgs;
- if (GrAAType::kNone != fAAType) {
- initArgs.fInputFlags |= GrPipeline::InputFlags::kHWAntialias;
- }
- if (args.fCaps->wireframeSupport() && (OpFlags::kWireframe & fOpFlags)) {
- initArgs.fInputFlags |= GrPipeline::InputFlags::kWireframe;
- }
- SkASSERT(SkPathFillType::kWinding == fPath.getFillType() ||
- SkPathFillType::kEvenOdd == fPath.getFillType());
- initArgs.fCaps = args.fCaps;
- fPipelineForStencils = args.fArena->make<GrPipeline>(
- initArgs, GrDisableColorXPFactory::MakeXferProcessor(), *args.fHardClip);
-}
-
-// Allows non-zero stencil values to pass and write a color, and resets the stencil value back to
-// zero; discards immediately on stencil values of zero.
-// NOTE: It's ok to not check the clip here because the previous stencil pass will have only written
-// to samples already inside the clip.
-constexpr static GrUserStencilSettings kTestAndResetStencil(
- GrUserStencilSettings::StaticInit<
- 0x0000,
- GrUserStencilTest::kNotEqual,
- 0xffff,
- GrUserStencilOp::kZero,
- GrUserStencilOp::kKeep,
- 0xffff>());
-
-void GrPathTessellateOp::prePrepareFillTrianglesProgram(const PrePrepareArgs& args, bool isLinear) {
- SkASSERT(!fFillTrianglesProgram);
-
- if (fOpFlags & OpFlags::kStencilOnly) {
- return;
- }
-
- // These are a twist on the standard red book stencil settings that allow us to fill the inner
- // polygon directly to the final render target. At this point, the curves are already stencilled
- // in. So if the stencil value is zero, then it means the path at our sample is not affected by
- // any curves and we fill the path in directly. If the stencil value is nonzero, then we don't
- // fill and instead continue the standard red book stencil process.
- //
- // NOTE: These settings are currently incompatible with a stencil clip.
- constexpr static GrUserStencilSettings kFillOrIncrDecrStencil(
- GrUserStencilSettings::StaticInitSeparate<
- 0x0000, 0x0000,
- GrUserStencilTest::kEqual, GrUserStencilTest::kEqual,
- 0xffff, 0xffff,
- GrUserStencilOp::kKeep, GrUserStencilOp::kKeep,
- GrUserStencilOp::kIncWrap, GrUserStencilOp::kDecWrap,
- 0xffff, 0xffff>());
-
- constexpr static GrUserStencilSettings kFillOrInvertStencil(
- GrUserStencilSettings::StaticInit<
- 0x0000,
- GrUserStencilTest::kEqual,
- 0xffff,
- GrUserStencilOp::kKeep,
- GrUserStencilOp::kZero,
- 0xffff>());
-
- this->prePreparePipelineForFills(args);
-
- const GrUserStencilSettings* stencil;
- if (fStencilTrianglesProgram) {
- // The path was already stencilled. Here we just need to do a cover pass.
- stencil = &kTestAndResetStencil;
- } else if (isLinear) {
- // There are no stencilled curves. We can ignore stencil and fill the path directly.
- stencil = &GrUserStencilSettings::kUnused;
- } else if (SkPathFillType::kWinding == fPath.getFillType()) {
- // Fill in the path pixels not touched by curves, incr/decr stencil otherwise.
- SkASSERT(!fPipelineForFills->hasStencilClip());
- stencil = &kFillOrIncrDecrStencil;
- } else {
- // Fill in the path pixels not touched by curves, invert stencil otherwise.
- SkASSERT(!fPipelineForFills->hasStencilClip());
- stencil = &kFillOrInvertStencil;
- }
-
- auto* fillTriangleShader = args.fArena->make<GrFillTriangleShader>(fViewMatrix, fColor);
- fFillTrianglesProgram = GrPathShader::MakeProgramInfo(
- fillTriangleShader, args.fArena, args.fWriteView, fPipelineForFills,
- *args.fDstProxyView, args.fXferBarrierFlags, args.fColorLoadOp, stencil, *args.fCaps);
-}
-
-void GrPathTessellateOp::prePrepareFillCubicHullsProgram(const PrePrepareArgs& args) {
- SkASSERT(!fFillPathProgram);
-
- if (fOpFlags & OpFlags::kStencilOnly) {
- return;
- }
-
- this->prePreparePipelineForFills(args);
-
- auto* fillCubicHullsShader = args.fArena->make<GrFillCubicHullShader>(fViewMatrix, fColor);
- fFillPathProgram = GrPathShader::MakeProgramInfo(
- fillCubicHullsShader, args.fArena, args.fWriteView, fPipelineForFills,
- *args.fDstProxyView, args.fXferBarrierFlags, args.fColorLoadOp, &kTestAndResetStencil,
- *args.fCaps);
-}
-
-void GrPathTessellateOp::prePrepareFillBoundingBoxProgram(const PrePrepareArgs& args) {
- SkASSERT(!fFillPathProgram);
-
- if (fOpFlags & OpFlags::kStencilOnly) {
- return;
- }
-
- this->prePreparePipelineForFills(args);
-
- auto* fillBoundingBoxShader = args.fArena->make<GrFillBoundingBoxShader>(fViewMatrix, fColor,
- fPath.getBounds());
- fFillPathProgram = GrPathShader::MakeProgramInfo(
- fillBoundingBoxShader, args.fArena, args.fWriteView, fPipelineForFills,
- *args.fDstProxyView, args.fXferBarrierFlags, args.fColorLoadOp, &kTestAndResetStencil,
- *args.fCaps);
-}
-
-void GrPathTessellateOp::prePreparePipelineForFills(const PrePrepareArgs& args) {
- SkASSERT(!(fOpFlags & OpFlags::kStencilOnly));
-
- if (fPipelineForFills) {
- return;
- }
-
- auto pipelineFlags = GrPipeline::InputFlags::kNone;
- if (GrAAType::kNone != fAAType) {
- if (args.fWriteView.asRenderTargetProxy()->numSamples() == 1) {
- // We are mixed sampled. We need to either enable conservative raster (preferred) or
- // disable MSAA in order to avoid double blend artifacts. (Even if we disable MSAA for
- // the cover geometry, the stencil test is still multisampled and will still produce
- // smooth results.)
- SkASSERT(GrAAType::kCoverage == fAAType);
- if (args.fCaps->conservativeRasterSupport()) {
- pipelineFlags |= GrPipeline::InputFlags::kHWAntialias;
- pipelineFlags |= GrPipeline::InputFlags::kConservativeRaster;
- }
- } else {
- // We are standard MSAA. Leave MSAA enabled for the cover geometry.
- pipelineFlags |= GrPipeline::InputFlags::kHWAntialias;
- }
- }
-
- fPipelineForFills = GrSimpleMeshDrawOpHelper::CreatePipeline(
- args.fCaps, args.fArena, args.fWriteView.swizzle(),
- (args.fClip) ? std::move(*args.fClip) : GrAppliedClip::Disabled(), *args.fDstProxyView,
- std::move(fProcessors), pipelineFlags);
-}
-
-void GrPathTessellateOp::onPrepare(GrOpFlushState* flushState) {
- int numVerbs = fPath.countVerbs();
- if (numVerbs <= 0) {
- return;
- }
-
- if (!fPipelineForStencils && !fPipelineForFills) {
- // Nothing has been prePrepared yet. Do it now.
- GrAppliedHardClip hardClip = GrAppliedHardClip(flushState->appliedHardClip());
- GrAppliedClip clip = flushState->detachAppliedClip();
- PrePrepareArgs args{flushState->allocator(), flushState->writeView(), &hardClip,
- &clip, &flushState->dstProxyView(),
- flushState->renderPassBarriers(), flushState->colorLoadOp(),
- &flushState->caps()};
- this->prePreparePrograms(args);
- }
-
- if (fInnerFanPolys) {
- // prePreparePrograms was able to generate an inner polygon triangulation. It will exist in
- // either fOffThreadInnerTriangulation or fTriangleBuffer exclusively.
- SkASSERT(fInnerFanTriangulator);
- GrEagerDynamicVertexAllocator alloc(flushState, &fTriangleBuffer, &fBaseTriangleVertex);
- fTriangleVertexCount = fInnerFanTriangulator->polysToTriangles(fInnerFanPolys, &alloc,
- nullptr);
- } else if (fStencilTrianglesProgram) {
- // The inner fan isn't built into the tessellator. Generate a standard Redbook fan with a
- // middle-out topology.
- GrEagerDynamicVertexAllocator vertexAlloc(flushState, &fTriangleBuffer,
- &fBaseTriangleVertex);
- // No initial moveTo, plus an implicit close at the end; n-2 triangles fill an n-gon.
- int maxInnerTriangles = fPath.countVerbs() - 1;
- auto* triangleVertexData = vertexAlloc.lock<SkPoint>(maxInnerTriangles * 3);
- fTriangleVertexCount = GrMiddleOutPolygonTriangulator::WritePathInnerFan(
- triangleVertexData, 3/*perTriangleVertexAdvance*/, fPath) * 3;
- vertexAlloc.unlock(fTriangleVertexCount);
- }
-
- if (fTessellator) {
- fTessellator->prepare(flushState, fViewMatrix, fPath);
- }
-}
-
-void GrPathTessellateOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
- this->drawStencilPass(flushState);
- this->drawCoverPass(flushState);
-}
-
-void GrPathTessellateOp::drawStencilPass(GrOpFlushState* flushState) {
- if (fStencilTrianglesProgram && fTriangleVertexCount > 0) {
- SkASSERT(fTriangleBuffer);
- flushState->bindPipelineAndScissorClip(*fStencilTrianglesProgram, this->bounds());
- flushState->bindBuffers(nullptr, nullptr, fTriangleBuffer);
- flushState->draw(fTriangleVertexCount, fBaseTriangleVertex);
- }
-
- if (fTessellator) {
- flushState->bindPipelineAndScissorClip(*fStencilCubicsProgram, this->bounds());
- fTessellator->draw(flushState);
- }
-}
-
-void GrPathTessellateOp::drawCoverPass(GrOpFlushState* flushState) {
- if (fFillTrianglesProgram) {
- SkASSERT(fTriangleBuffer);
-
- // We have a triangulation of the path's inner polygon. This is the fast path. Fill those
- // triangles directly to the screen.
- if (fTriangleVertexCount > 0) {
- flushState->bindPipelineAndScissorClip(*fFillTrianglesProgram, this->bounds());
- flushState->bindTextures(fFillTrianglesProgram->primProc(), nullptr,
- *fPipelineForFills);
- flushState->bindBuffers(nullptr, nullptr, fTriangleBuffer);
- flushState->draw(fTriangleVertexCount, fBaseTriangleVertex);
- }
-
- if (fTessellator) {
- // At this point, every pixel is filled in except the ones touched by curves.
- // fFillPathProgram will issue a final cover pass over the curves by drawing their
- // convex hulls. This will fill in any remaining samples and reset the stencil buffer.
- SkASSERT(fFillPathProgram);
- flushState->bindPipelineAndScissorClip(*fFillPathProgram, this->bounds());
- flushState->bindTextures(fFillPathProgram->primProc(), nullptr, *fPipelineForFills);
- fTessellator->drawHullInstances(flushState);
- }
- } else if (fFillPathProgram) {
- // There are no triangles to fill. Just draw a bounding box.
- flushState->bindPipelineAndScissorClip(*fFillPathProgram, this->bounds());
- flushState->bindTextures(fFillPathProgram->primProc(), nullptr, *fPipelineForFills);
- flushState->bindBuffers(nullptr, nullptr, nullptr);
- flushState->draw(4, 0);
- }
-}
diff --git a/src/gpu/tessellate/GrPathTessellateOp.h b/src/gpu/tessellate/GrPathTessellateOp.h
deleted file mode 100644
index a94ca26..0000000
--- a/src/gpu/tessellate/GrPathTessellateOp.h
+++ /dev/null
@@ -1,153 +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 GrPathTessellateOp_DEFINED
-#define GrPathTessellateOp_DEFINED
-
-#include "src/gpu/GrInnerFanTriangulator.h"
-#include "src/gpu/ops/GrMeshDrawOp.h"
-#include "src/gpu/tessellate/GrPathTessellator.h"
-#include "src/gpu/tessellate/GrTessellationPathRenderer.h"
-
-class GrAppliedHardClip;
-class GrEagerVertexAllocator;
-class GrStencilPathShader;
-class GrResolveLevelCounter;
-
-// Renders paths using a hybrid "Red Book" (stencil, then cover) method. Curves get linearized by
-// either GPU tessellation shaders or indirect draws. This Op doesn't apply analytic AA, so it
-// requires a render target that supports either MSAA or mixed samples if AA is desired.
-class GrPathTessellateOp : public GrDrawOp {
-private:
- DEFINE_OP_CLASS_ID
-
- GrPathTessellateOp(const SkMatrix& viewMatrix, const SkPath& path, GrPaint&& paint,
- GrAAType aaType, GrTessellationPathRenderer::OpFlags opFlags)
- : GrDrawOp(ClassID())
- , fOpFlags(opFlags)
- , fViewMatrix(viewMatrix)
- , fPath(path)
- , fAAType(aaType)
- , fColor(paint.getColor4f())
- , fProcessors(std::move(paint)) {
- SkRect devBounds;
- fViewMatrix.mapRect(&devBounds, path.getBounds());
- this->setBounds(devBounds, HasAABloat(GrAAType::kCoverage == fAAType), IsHairline::kNo);
- }
-
- const char* name() const override { return "GrPathTessellateOp"; }
- void visitProxies(const VisitProxyFunc& fn) const override;
- GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
- bool hasMixedSampledCoverage,
- GrClampType clampType) override {
- return fProcessors.finalize(
- fColor, GrProcessorAnalysisCoverage::kNone, clip, &GrUserStencilSettings::kUnused,
- hasMixedSampledCoverage, caps, clampType, &fColor);
- }
-
- FixedFunctionFlags fixedFunctionFlags() const override;
-
- void onPrePrepare(GrRecordingContext*, const GrSurfaceProxyView&, GrAppliedClip*,
- const GrXferProcessor::DstProxyView&, GrXferBarrierFlags,
- GrLoadOp colorLoadOp) override;
-
- struct PrePrepareArgs {
- SkArenaAlloc* fArena;
- const GrSurfaceProxyView& fWriteView;
- const GrAppliedHardClip* fHardClip;
- GrAppliedClip* fClip;
- const GrXferProcessor::DstProxyView* fDstProxyView;
- GrXferBarrierFlags fXferBarrierFlags;
- GrLoadOp fColorLoadOp;
- const GrCaps* fCaps;
- };
-
- // Chooses the rendering method we will use and creates the corresponding stencil and fill
- // programs up front.
- void prePreparePrograms(const PrePrepareArgs&);
-
- // Produces a non-overlapping triangulation of the path's inner polygon(s). 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.) If this succeeds, then we will be able to fill the
- // triangles directly and bypass stencilling them.
- //
- // Returns false if the inner triangles do not form a simple polygon (e.g., self intersection,
- // double winding). Non-simple polygons would need to split edges in order to avoid overlap,
- // and this is not an option as it would introduce T-junctions with the outer cubics.
- bool prePrepareInnerPolygonTriangulation(const PrePrepareArgs&, bool* isLinear);
-
- void prePrepareStencilTrianglesProgram(const PrePrepareArgs&);
- template<typename ShaderType> void prePrepareStencilCubicsProgram(const PrePrepareArgs&);
- void prePreparePipelineForStencils(const PrePrepareArgs&);
-
- void prePrepareFillTrianglesProgram(const PrePrepareArgs&, bool isLinear);
- void prePrepareFillCubicHullsProgram(const PrePrepareArgs&);
- void prePrepareFillBoundingBoxProgram(const PrePrepareArgs&);
- void prePreparePipelineForFills(const PrePrepareArgs&);
-
- void onPrepare(GrOpFlushState*) override;
-
- void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
- void drawStencilPass(GrOpFlushState*);
- void drawCoverPass(GrOpFlushState*);
-
- const GrTessellationPathRenderer::OpFlags fOpFlags;
- const SkMatrix fViewMatrix;
- const SkPath fPath;
- const GrAAType fAAType;
- SkPMColor4f fColor;
- GrProcessorSet fProcessors;
-
- GrInnerFanTriangulator* fInnerFanTriangulator = nullptr;
- GrTriangulator::Poly* fInnerFanPolys = nullptr;
- sk_sp<const GrBuffer> fTriangleBuffer;
- int fBaseTriangleVertex = 0;
- int fTriangleVertexCount = 0;
-
- // This pipeline is used by all programInfos in the stencil step.
- const GrPipeline* fPipelineForStencils = nullptr;
-
- // This pipeline is used by all programInfos in the fill step.
- const GrPipeline* fPipelineForFills = nullptr;
-
- // These switches specify how the above fTriangleBuffer should be drawn (if at all).
- //
- // If stencil and !fill:
- //
- // We just stencil the triangles (with cubics) during the stencil step. The path gets filled
- // later using a simple bounding box if needed.
- //
- // If stencil and fill:
- //
- // We still stencil the triangles and cubics normally, but during the *fill* step we fill
- // the triangles plus local convex hulls around each cubic instead of a bounding box.
- //
- // If !stencil and fill:
- //
- // This means that fTriangleBuffer contains non-overlapping geometry that can be filled
- // directly to the final render target. We only need to stencil *curves*, and during the
- // fill step we draw the triangles directly with a stencil test that accounts for curves
- // (see drawCoverPass()), and then finally fill the curves with local convex hulls.
- const GrProgramInfo* fStencilTrianglesProgram = nullptr;
- const GrProgramInfo* fFillTrianglesProgram = nullptr;
-
- const GrProgramInfo* fStencilCubicsProgram = nullptr;
-
- // This will draw either a bounding box, or if fFillTrianglesProgram exists, individual convex
- // hulls covering each cubic.
- const GrProgramInfo* fFillPathProgram = nullptr;
-
- GrPathTessellator* fTessellator = nullptr;
-
- friend class GrOp; // For ctor.
-
-public:
- // This serves as a base class for benchmarking individual methods on GrPathTessellateOp.
- class TestingOnly_Benchmark;
-};
-
-#endif
diff --git a/src/gpu/tessellate/GrPathTessellator.cpp b/src/gpu/tessellate/GrPathTessellator.cpp
index 91b3409..adeceb9 100644
--- a/src/gpu/tessellate/GrPathTessellator.cpp
+++ b/src/gpu/tessellate/GrPathTessellator.cpp
@@ -47,7 +47,8 @@
}
void GrPathIndirectTessellator::prepare(GrMeshDrawOp::Target* target, const SkMatrix& viewMatrix,
- const SkPath& path) {
+ const SkPath& path,
+ const BreadcrumbTriangleList* breadcrumbTriangleList) {
SkASSERT(fTotalInstanceCount == 0);
SkASSERT(fIndirectDrawCount == 0);
SkASSERT(target->caps().drawInstancedSupport());
@@ -58,6 +59,9 @@
int maxInnerTriangles = path.countVerbs() - 1;
instanceLockCount += maxInnerTriangles;
}
+ if (breadcrumbTriangleList) {
+ instanceLockCount += breadcrumbTriangleList->count();
+ }
if (instanceLockCount == 0) {
return;
}
@@ -74,7 +78,26 @@
int numTrianglesAtBeginningOfData = 0;
if (fDrawInnerFan) {
numTrianglesAtBeginningOfData = GrMiddleOutPolygonTriangulator::WritePathInnerFan(
- instanceData, 4/*perTriangleVertexAdvance*/, path);
+ instanceData + numTrianglesAtBeginningOfData * 4, 4/*stride*/, path);
+ }
+ if (breadcrumbTriangleList) {
+ SkDEBUGCODE(int count = 0;)
+ for (const auto* tri = breadcrumbTriangleList->head(); tri; tri = tri->fNext) {
+ SkDEBUGCODE(++count;)
+ const SkPoint* p = tri->fPts;
+ if ((p[0].fX == p[1].fX && p[1].fX == p[2].fX) ||
+ (p[0].fY == p[1].fY && p[1].fY == p[2].fY)) {
+ // Completely degenerate triangles have undefined winding. And T-junctions shouldn't
+ // happen on axis-aligned edges.
+ continue;
+ }
+ SkPoint* breadcrumbData = instanceData + numTrianglesAtBeginningOfData * 4;
+ memcpy(breadcrumbData, p, sizeof(SkPoint) * 3);
+ // Duplicate the final point since it will also be used by the convex hull shader.
+ breadcrumbData[3] = p[2];
+ ++numTrianglesAtBeginningOfData;
+ }
+ SkASSERT(count == breadcrumbTriangleList->count());
}
fIndirectIndexBuffer = GrMiddleOutCubicShader::FindOrMakeMiddleOutIndexBuffer(
@@ -209,8 +232,10 @@
}
void GrPathOuterCurveTessellator::prepare(GrMeshDrawOp::Target* target, const SkMatrix& matrix,
- const SkPath& path) {
+ const SkPath& path,
+ const BreadcrumbTriangleList* breadcrumbTriangleList) {
SkASSERT(target->caps().shaderCaps()->tessellationSupport());
+ SkASSERT(!breadcrumbTriangleList);
SkASSERT(!fPatchBuffer);
SkASSERT(fPatchVertexCount == 0);
@@ -245,8 +270,10 @@
}
void GrPathWedgeTessellator::prepare(GrMeshDrawOp::Target* target, const SkMatrix& matrix,
- const SkPath& path) {
+ const SkPath& path,
+ const BreadcrumbTriangleList* breadcrumbTriangleList) {
SkASSERT(target->caps().shaderCaps()->tessellationSupport());
+ SkASSERT(!breadcrumbTriangleList);
SkASSERT(!fPatchBuffer);
SkASSERT(fPatchVertexCount == 0);
diff --git a/src/gpu/tessellate/GrPathTessellator.h b/src/gpu/tessellate/GrPathTessellator.h
index 461916c..2545e60 100644
--- a/src/gpu/tessellate/GrPathTessellator.h
+++ b/src/gpu/tessellate/GrPathTessellator.h
@@ -8,6 +8,7 @@
#ifndef GrPathTessellator_DEFINED
#define GrPathTessellator_DEFINED
+#include "src/gpu/GrInnerFanTriangulator.h"
#include "src/gpu/ops/GrMeshDrawOp.h"
#include "src/gpu/tessellate/GrTessellationPathRenderer.h"
@@ -17,8 +18,13 @@
// the caller may or may not be required to draw the path's inner fan separately.
class GrPathTessellator {
public:
- // Called before draw(). Prepares GPU buffers containing the geometry to tessellate.
- virtual void prepare(GrMeshDrawOp::Target*, const SkMatrix&, const SkPath&) = 0;
+ using BreadcrumbTriangleList = GrInnerFanTriangulator::BreadcrumbTriangleList;
+
+ // Called before draw(). Prepares GPU buffers containing the geometry to tessellate. If the
+ // given BreadcrumbTriangleList is non-null, then this class will also include the breadcrumb
+ // triangles in its draw.
+ virtual void prepare(GrMeshDrawOp::Target*, const SkMatrix&, const SkPath&,
+ const BreadcrumbTriangleList* = nullptr) = 0;
// Issues draw calls for the tessellated geometry. The caller is responsible for binding its
// desired pipeline ahead of time.
@@ -44,7 +50,8 @@
enum class DrawInnerFan : bool { kNo = false, kYes };
GrPathIndirectTessellator(const SkMatrix&, const SkPath&, DrawInnerFan);
- void prepare(GrMeshDrawOp::Target*, const SkMatrix&, const SkPath&) override;
+ void prepare(GrMeshDrawOp::Target*, const SkMatrix&, const SkPath&,
+ const BreadcrumbTriangleList*) override;
void draw(GrOpFlushState*) const override;
void drawHullInstances(GrOpFlushState*) const override;
@@ -87,7 +94,8 @@
public:
GrPathOuterCurveTessellator() = default;
- void prepare(GrMeshDrawOp::Target*, const SkMatrix&, const SkPath&) override;
+ void prepare(GrMeshDrawOp::Target*, const SkMatrix&, const SkPath&,
+ const BreadcrumbTriangleList*) override;
};
// Draws an array of "wedge" patches for GrWedgeTessellateShader. A wedge is an independent, 5-point
@@ -98,7 +106,8 @@
public:
GrPathWedgeTessellator() = default;
- void prepare(GrMeshDrawOp::Target*, const SkMatrix&, const SkPath&) override;
+ void prepare(GrMeshDrawOp::Target*, const SkMatrix&, const SkPath&,
+ const BreadcrumbTriangleList*) override;
};
#endif
diff --git a/src/gpu/tessellate/GrTessellationPathRenderer.cpp b/src/gpu/tessellate/GrTessellationPathRenderer.cpp
index 1055d14..b525c38 100644
--- a/src/gpu/tessellate/GrTessellationPathRenderer.cpp
+++ b/src/gpu/tessellate/GrTessellationPathRenderer.cpp
@@ -17,7 +17,7 @@
#include "src/gpu/geometry/GrStyledShape.h"
#include "src/gpu/ops/GrFillRectOp.h"
#include "src/gpu/tessellate/GrDrawAtlasPathOp.h"
-#include "src/gpu/tessellate/GrPathTessellateOp.h"
+#include "src/gpu/tessellate/GrPathInnerTriangulateOp.h"
#include "src/gpu/tessellate/GrStrokeIndirectOp.h"
#include "src/gpu/tessellate/GrStrokeTessellateOp.h"
#include "src/gpu/tessellate/GrTessellatingStencilFillOp.h"
@@ -247,8 +247,8 @@
constexpr static float kCpuWeight = 512;
constexpr static float kMinNumPixelsToTriangulate = 256 * 256;
if (cpuTessellationWork * kCpuWeight + kMinNumPixelsToTriangulate < gpuFragmentWork) {
- return GrOp::Make<GrPathTessellateOp>(rContext, viewMatrix, path, std::move(paint),
- aaType, opFlags);
+ return GrOp::Make<GrPathInnerTriangulateOp>(rContext, viewMatrix, path,
+ std::move(paint), aaType, opFlags);
}
}
return GrOp::Make<GrTessellatingStencilFillOp>(rContext, viewMatrix, path, std::move(paint),