| /* |
| * 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/PathCurveTessellator.h" |
| #include "src/gpu/tessellate/shaders/GrPathTessellationShader.h" |
| |
| using PathFlags = skgpu::tess::TessellationPathFlags; |
| |
| 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>(); |
| } |
| |
| } // anonymous namespace |
| |
| namespace skgpu::v1 { |
| |
| void PathInnerTriangulateOp::visitProxies(const GrVisitProxyFunc& func) const { |
| if (fPipelineForFills) { |
| fPipelineForFills->visitProxies(func); |
| } else { |
| fProcessors.visitProxies(func); |
| } |
| } |
| |
| GrDrawOp::FixedFunctionFlags PathInnerTriangulateOp::fixedFunctionFlags() const { |
| auto flags = FixedFunctionFlags::kUsesStencil; |
| if (GrAAType::kNone != fAAType) { |
| flags |= FixedFunctionFlags::kUsesHWAA; |
| } |
| return flags; |
| } |
| |
| GrProcessorSet::Analysis PathInnerTriangulateOp::finalize(const GrCaps& caps, |
| const GrAppliedClip* clip, |
| GrClampType clampType) { |
| return fProcessors.finalize(fColor, GrProcessorAnalysisCoverage::kNone, clip, nullptr, caps, |
| clampType, &fColor); |
| } |
| |
| void PathInnerTriangulateOp::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 PathInnerTriangulateOp::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 PathInnerTriangulateOp::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 = skgpu::tess::PathCurveTessellator::Make( |
| args.fArena, |
| fViewMatrix, |
| SK_PMColor4fTRANSPARENT, |
| skgpu::tess::PathCurveTessellator::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 PathInnerTriangulateOp::onPrePrepare(GrRecordingContext* context, |
| const GrSurfaceProxyView& writeView, |
| GrAppliedClip* clip, |
| const GrDstProxyView& dstProxyView, |
| GrXferBarrierFlags renderPassXferBarriers, |
| GrLoadOp colorLoadOp) { |
| // DMSAA is not supported on DDL. |
| bool usesMSAASurface = writeView.asRenderTargetProxy()->numSamples() > 1; |
| this->prePreparePrograms({context->priv().recordTimeAllocator(), writeView, usesMSAASurface, |
| &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 PathInnerTriangulateOp::onPrepare(GrOpFlushState* flushState) { |
| if (!fFanTriangulator) { |
| this->prePreparePrograms({flushState->allocator(), flushState->writeView(), |
| flushState->usesMSAASurface(), &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 PathInnerTriangulateOp::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); |
| } |
| } |
| |
| } // namespace skgpu::v1 |