Move some v1-only gpu/tessellate files to gpu/ops
This CL just moves the files and renames them. It doesn't move them into the skgpu::v1 namespace.
Bug: skia:11837
Change-Id: Iab322d0dc5b5d1cfd32436785081539dc85c18d3
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/440776
Commit-Queue: Robert Phillips <robertphillips@google.com>
Reviewed-by: Chris Dalton <csmartdalton@google.com>
diff --git a/src/gpu/ops/PathInnerTriangulateOp.cpp b/src/gpu/ops/PathInnerTriangulateOp.cpp
new file mode 100644
index 0000000..86f7c8e
--- /dev/null
+++ b/src/gpu/ops/PathInnerTriangulateOp.cpp
@@ -0,0 +1,441 @@
+/*
+ * 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/ops/PathInnerTriangulateOp.h"
+
+#include "src/gpu/GrEagerVertexAllocator.h"
+#include "src/gpu/GrGpu.h"
+#include "src/gpu/GrOpFlushState.h"
+#include "src/gpu/GrRecordingContextPriv.h"
+#include "src/gpu/GrResourceProvider.h"
+#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
+#include "src/gpu/tessellate/GrPathCurveTessellator.h"
+#include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
+
+using PathFlags = GrTessellationPathFlags;
+
+namespace {
+
+// Fills an array of convex hulls surrounding 4-point cubic or conic instances. This shader is used
+// for the "cover" pass after the curves have been fully stencilled.
+class HullShader : public GrPathTessellationShader {
+public:
+ HullShader(const SkMatrix& viewMatrix, SkPMColor4f color, const GrShaderCaps& shaderCaps)
+ : GrPathTessellationShader(kTessellate_HullShader_ClassID,
+ GrPrimitiveType::kTriangleStrip, 0, viewMatrix, color) {
+ fInstanceAttribs.emplace_back("p01", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
+ fInstanceAttribs.emplace_back("p23", kFloat4_GrVertexAttribType, kFloat4_GrSLType);
+ if (!shaderCaps.infinitySupport()) {
+ // A conic curve is written out with p3=[w,Infinity], but GPUs that don't support
+ // infinity can't detect this. On these platforms we also write out an extra float with
+ // each patch that explicitly tells the shader what type of curve it is.
+ fInstanceAttribs.emplace_back("curveType", kFloat_GrVertexAttribType, kFloat_GrSLType);
+ }
+ this->setInstanceAttributes(fInstanceAttribs.data(), fInstanceAttribs.count());
+ SkASSERT(fInstanceAttribs.count() <= kMaxInstanceAttribCount);
+
+ if (!shaderCaps.vertexIDSupport()) {
+ constexpr static Attribute kVertexIdxAttrib("vertexidx", kFloat_GrVertexAttribType,
+ kFloat_GrSLType);
+ this->setVertexAttributes(&kVertexIdxAttrib, 1);
+ }
+ }
+
+private:
+ const char* name() const final { return "tessellate_HullShader"; }
+ void addToKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const final {}
+ std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const final;
+
+ constexpr static int kMaxInstanceAttribCount = 3;
+ SkSTArray<kMaxInstanceAttribCount, Attribute> fInstanceAttribs;
+};
+
+std::unique_ptr<GrGeometryProcessor::ProgramImpl> HullShader::makeProgramImpl(
+ const GrShaderCaps&) const {
+ class Impl : public GrPathTessellationShader::Impl {
+ void emitVertexCode(const GrShaderCaps& shaderCaps, const GrPathTessellationShader&,
+ GrGLSLVertexBuilder* v, GrGPArgs* gpArgs) override {
+ if (shaderCaps.infinitySupport()) {
+ v->insertFunction(R"(
+ bool is_conic_curve() { return isinf(p23.w); }
+ bool is_non_triangular_conic_curve() {
+ // We consider a conic non-triangular as long as its weight isn't infinity.
+ // NOTE: "isinf == false" works on Mac Radeon GLSL; "!isinf" can get the wrong
+ // answer.
+ return isinf(p23.z) == false;
+ })");
+ } else {
+ v->insertFunction(SkStringPrintf(R"(
+ bool is_conic_curve() { return curveType != %g; })", kCubicCurveType).c_str());
+ v->insertFunction(SkStringPrintf(R"(
+ bool is_non_triangular_conic_curve() {
+ return curveType == %g;
+ })", kConicCurveType).c_str());
+ }
+ v->codeAppend(R"(
+ float2 p0=p01.xy, p1=p01.zw, p2=p23.xy, p3=p23.zw;
+ if (is_conic_curve()) {
+ // Conics are 3 points, with the weight in p3.
+ float w = p3.x;
+ p3 = p2; // Duplicate the endpoint for shared code that also runs on cubics.
+ if (is_non_triangular_conic_curve()) {
+ // Convert the points to a trapeziodal hull that circumcscribes the conic.
+ float2 p1w = p1 * w;
+ float T = .51; // Bias outward a bit to ensure we cover the outermost samples.
+ float2 c1 = mix(p0, p1w, T);
+ float2 c2 = mix(p2, p1w, T);
+ float iw = 1 / mix(1, w, T);
+ p2 = c2 * iw;
+ p1 = c1 * iw;
+ }
+ }
+
+ // Translate the points to v0..3 where v0=0.
+ float2 v1 = p1 - p0;
+ float2 v2 = p2 - p0;
+ float2 v3 = p3 - p0;
+
+ // Reorder the points so v2 bisects v1 and v3.
+ if (sign(cross(v2, v1)) == sign(cross(v2, v3))) {
+ float2 tmp = p2;
+ if (sign(cross(v1, v2)) != sign(cross(v1, v3))) {
+ p2 = p1; // swap(p2, p1)
+ p1 = tmp;
+ } else {
+ p2 = p3; // swap(p2, p3)
+ p3 = tmp;
+ }
+ })");
+
+ if (shaderCaps.vertexIDSupport()) {
+ // If we don't have sk_VertexID support then "vertexidx" already came in as a
+ // vertex attrib.
+ v->codeAppend(R"(
+ // sk_VertexID comes in fan order. Convert to strip order.
+ int vertexidx = sk_VertexID;
+ vertexidx ^= vertexidx >> 1;)");
+ }
+
+ v->codeAppend(R"(
+ // Find the "turn direction" of each corner and net turn direction.
+ float vertexdir = 0;
+ float netdir = 0;
+ float2 prev, next;
+ float dir;
+ float2 localcoord;
+ float2 nextcoord;)");
+
+ for (int i = 0; i < 4; ++i) {
+ v->codeAppendf(R"(
+ prev = p%i - p%i;)", i, (i + 3) % 4);
+ v->codeAppendf(R"(
+ next = p%i - p%i;)", (i + 1) % 4, i);
+ v->codeAppendf(R"(
+ dir = sign(cross(prev, next));
+ if (vertexidx == %i) {
+ vertexdir = dir;
+ localcoord = p%i;
+ nextcoord = p%i;
+ }
+ netdir += dir;)", i, i, (i + 1) % 4);
+ }
+
+ v->codeAppend(R"(
+ // Remove the non-convex vertex, if any.
+ if (vertexdir != sign(netdir)) {
+ localcoord = nextcoord;
+ }
+
+ float2 vertexpos = AFFINE_MATRIX * localcoord + TRANSLATE;)");
+ gpArgs->fLocalCoordVar.set(kFloat2_GrSLType, "localcoord");
+ gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertexpos");
+ }
+ };
+ return std::make_unique<Impl>();
+}
+
+} // namespace
+
+void GrPathInnerTriangulateOp::visitProxies(const GrVisitProxyFunc& func) const {
+ if (fPipelineForFills) {
+ fPipelineForFills->visitProxies(func);
+ } else {
+ fProcessors.visitProxies(func);
+ }
+}
+
+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,
+ GrClampType clampType) {
+ return fProcessors.finalize(fColor, GrProcessorAnalysisCoverage::kNone, clip, nullptr, caps,
+ clampType, &fColor);
+}
+
+void GrPathInnerTriangulateOp::pushFanStencilProgram(const GrTessellationShader::ProgramArgs& args,
+ const GrPipeline* pipelineForStencils,
+ const GrUserStencilSettings* stencil) {
+ SkASSERT(pipelineForStencils);
+ auto shader = GrPathTessellationShader::MakeSimpleTriangleShader(args.fArena, fViewMatrix,
+ SK_PMColor4fTRANSPARENT);
+ fFanPrograms.push_back(GrTessellationShader::MakeProgram(args, shader, pipelineForStencils,
+ stencil)); }
+
+void GrPathInnerTriangulateOp::pushFanFillProgram(const GrTessellationShader::ProgramArgs& args,
+ const GrUserStencilSettings* stencil) {
+ SkASSERT(fPipelineForFills);
+ auto shader = GrPathTessellationShader::MakeSimpleTriangleShader(args.fArena, fViewMatrix,
+ fColor);
+ fFanPrograms.push_back(GrTessellationShader::MakeProgram(args, shader, fPipelineForFills,
+ stencil));
+}
+
+void GrPathInnerTriangulateOp::prePreparePrograms(const GrTessellationShader::ProgramArgs& args,
+ GrAppliedClip&& appliedClip) {
+ SkASSERT(!fFanTriangulator);
+ SkASSERT(!fFanPolys);
+ SkASSERT(!fPipelineForFills);
+ SkASSERT(!fTessellator);
+ SkASSERT(!fStencilCurvesProgram);
+ SkASSERT(fFanPrograms.empty());
+ SkASSERT(!fCoverHullsProgram);
+
+ if (fPath.countVerbs() <= 0) {
+ return;
+ }
+
+ // If using wireframe, we have to fall back on a standard Redbook "stencil then cover" algorithm
+ // instead of bypassing the stencil buffer to fill the fan directly.
+ bool forceRedbookStencilPass = (fPathFlags & (PathFlags::kStencilOnly | PathFlags::kWireframe));
+ bool doFill = !(fPathFlags & PathFlags::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 = GrPathTessellationShader::MakeStencilOnlyPipeline(
+ args, fAAType, fPathFlags, appliedClip.hardClip());
+ }
+
+ // Create a pipeline for fill passes if needed.
+ if (doFill) {
+ fPipelineForFills = GrTessellationShader::MakePipeline(args, fAAType,
+ std::move(appliedClip),
+ std::move(fProcessors));
+ }
+
+ // Pass 1: Tessellate the outer curves into the stencil buffer.
+ if (!isLinear) {
+ fTessellator = GrPathCurveTessellator::Make(args.fArena, fViewMatrix,
+ SK_PMColor4fTRANSPARENT,
+ GrPathCurveTessellator::DrawInnerFan::kNo,
+ fPath.countVerbs(), *pipelineForStencils,
+ *args.fCaps);
+ const GrUserStencilSettings* stencilPathSettings =
+ GrPathTessellationShader::StencilPathSettings(GrFillRuleForSkPath(fPath));
+ fStencilCurvesProgram = GrTessellationShader::MakeProgram(args, fTessellator->shader(),
+ pipelineForStencils,
+ stencilPathSettings);
+ }
+
+ // 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 cover" algorithm instead of bypassing the
+ // stencil buffer to fill the fan directly.
+ const GrUserStencilSettings* stencilPathSettings =
+ GrPathTessellationShader::StencilPathSettings(GrFillRuleForSkPath(fPath));
+ this->pushFanStencilProgram(args, pipelineForStencils, stencilPathSettings);
+ if (doFill) {
+ this->pushFanFillProgram(args,
+ GrPathTessellationShader::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<HullShader>(fViewMatrix, fColor,
+ *args.fCaps->shaderCaps());
+ fCoverHullsProgram = GrTessellationShader::MakeProgram(
+ args, hullShader, fPipelineForFills,
+ GrPathTessellationShader::TestAndResetStencilSettings());
+ }
+}
+
+void GrPathInnerTriangulateOp::onPrePrepare(GrRecordingContext* context,
+ const GrSurfaceProxyView& writeView,
+ GrAppliedClip* clip,
+ const GrDstProxyView& 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 (fCoverHullsProgram) {
+ context->priv().recordProgramInfo(fCoverHullsProgram);
+ }
+}
+
+GR_DECLARE_STATIC_UNIQUE_KEY(gHullVertexBufferKey);
+
+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, this->bounds(), {SkMatrix::I(), fPath},
+ fPath.countVerbs(), &fFanBreadcrumbs);
+ }
+
+ if (!flushState->caps().shaderCaps()->vertexIDSupport()) {
+ constexpr static float kStripOrderIDs[4] = {0, 1, 3, 2};
+
+ GR_DEFINE_STATIC_UNIQUE_KEY(gHullVertexBufferKey);
+
+ fHullVertexBufferIfNoIDSupport = flushState->resourceProvider()->findOrMakeStaticBuffer(
+ GrGpuBufferType::kVertex, sizeof(kStripOrderIDs), kStripOrderIDs,
+ gHullVertexBufferKey);
+ }
+}
+
+void GrPathInnerTriangulateOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
+ if (fStencilCurvesProgram) {
+ SkASSERT(fTessellator);
+ flushState->bindPipelineAndScissorClip(*fStencilCurvesProgram, this->bounds());
+ fTessellator->draw(flushState);
+ if (flushState->caps().requiresManualFBBarrierAfterTessellatedStencilDraw()) {
+ flushState->gpu()->insertManualFramebufferBarrier(); // http://skbug.com/9739
+ }
+ }
+
+ for (const GrProgramInfo* fanProgram : fFanPrograms) {
+ SkASSERT(fFanBuffer);
+ flushState->bindPipelineAndScissorClip(*fanProgram, this->bounds());
+ flushState->bindTextures(fanProgram->geomProc(), nullptr, fanProgram->pipeline());
+ flushState->bindBuffers(nullptr, nullptr, fFanBuffer);
+ flushState->draw(fFanVertexCount, fBaseFanVertex);
+ }
+
+ if (fCoverHullsProgram) {
+ SkASSERT(fTessellator);
+ flushState->bindPipelineAndScissorClip(*fCoverHullsProgram, this->bounds());
+ flushState->bindTextures(fCoverHullsProgram->geomProc(), nullptr, *fPipelineForFills);
+ fTessellator->drawHullInstances(flushState, fHullVertexBufferIfNoIDSupport);
+ }
+}