blob: 6d29b0ad4468a5b112936cdb20b3bf885a71fcbc [file] [log] [blame]
/*
* 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->geomProc(), nullptr, fanProgram->pipeline());
flushState->bindBuffers(nullptr, nullptr, fFanBuffer);
flushState->draw(fFanVertexCount, fBaseFanVertex);
}
if (fFillHullsProgram) {
SkASSERT(fTessellator);
flushState->bindPipelineAndScissorClip(*fFillHullsProgram, this->bounds());
flushState->bindTextures(fFillHullsProgram->geomProc(), nullptr, *fPipelineForFills);
fTessellator->drawHullInstances(flushState);
}
}