blob: 8855f87dd55350037da0ed28db62d6b17e5687f8 [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/GrPathTessellateOp.h"
#include "src/gpu/GrEagerVertexAllocator.h"
#include "src/gpu/GrGpu.h"
#include "src/gpu/GrOpFlushState.h"
#include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/GrTriangulator.h"
#include "src/gpu/geometry/GrPathUtils.h"
#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h"
#include "src/gpu/tessellate/GrFillPathShader.h"
#include "src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h"
#include "src/gpu/tessellate/GrMidpointContourParser.h"
#include "src/gpu/tessellate/GrResolveLevelCounter.h"
#include "src/gpu/tessellate/GrStencilPathShader.h"
#include "src/gpu/tessellate/GrTessellationPathRenderer.h"
#include "src/gpu/tessellate/GrWangsFormula.h"
constexpr static float kLinearizationIntolerance =
GrTessellationPathRenderer::kLinearizationIntolerance;
constexpr static int kMaxResolveLevel = GrTessellationPathRenderer::kMaxResolveLevel;
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;
}
namespace {
class CpuTriangleAllocator : public GrEagerVertexAllocator {
public:
CpuTriangleAllocator(SkArenaAlloc* arena, const SkPoint** data) : fArena(arena), fData(data) {}
void* lock(size_t stride, int eagerCount) override {
SkASSERT(!*fData);
SkASSERT(stride == sizeof(SkPoint));
SkPoint* data = fArena->makeArray<SkPoint>(eagerCount);
*fData = data;
return data;
}
void unlock(int actualCount) override { SkASSERT(*fData); }
private:
SkArenaAlloc* const fArena;
const SkPoint** fData;
};
}
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());
CpuTriangleAllocator cpuTriangleAllocator(recordTimeAllocator, &fOffThreadInnerTriangulation);
PrePrepareArgs args{recordTimeAllocator, writeView, &hardClip, clip, &dstProxyView,
renderPassXferBarriers, colorLoadOp, context->priv().caps(),
&cpuTriangleAllocator};
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) {
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);
}
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);
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);
return;
}
// Fastest CPU approach: emit one cubic wedge per verb, fanning out from the center.
this->prePrepareStencilCubicsProgram<GrWedgeTessellateShader>(args);
}
bool GrPathTessellateOp::prePrepareInnerPolygonTriangulation(const PrePrepareArgs& args,
bool* isLinear) {
SkASSERT(!fTriangleBuffer);
SkASSERT(fTriangleVertexCount == 0);
SkASSERT(!fStencilTrianglesProgram);
SkASSERT(!fFillTrianglesProgram);
fTriangleVertexCount = GrTriangulator::TriangulateSimpleInnerPolygons(
fPath, args.fInnerTriangleAllocator, isLinear);
if (fTriangleVertexCount == 0) {
// Mode::kSimpleInnerPolygons causes PathToTriangles to 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.
GrEagerDynamicVertexAllocator innerTriangleAllocator(flushState, &fTriangleBuffer,
&fBaseTriangleVertex);
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(), &innerTriangleAllocator};
this->prePreparePrograms(args);
}
if (fTriangleVertexCount != 0) {
// prePreparePrograms was able to generate an inner polygon triangulation. It will exist in
// either fOffThreadInnerTriangulation or fTriangleBuffer exclusively.
SkASSERT(SkToBool(fOffThreadInnerTriangulation) != SkToBool(fTriangleBuffer));
if (fOffThreadInnerTriangulation) {
// DDL generated the triangle buffer data off thread. Copy it to GPU.
void* data = flushState->makeVertexSpace(sizeof(SkPoint), fTriangleVertexCount,
&fTriangleBuffer, &fBaseTriangleVertex);
memcpy(data, fOffThreadInnerTriangulation, fTriangleVertexCount * sizeof(SkPoint));
}
if (fStencilCubicsProgram) {
// We always use indirect draws for inner-polygon-triangulation mode instead of
// tessellation.
SkASSERT(GrPrimitiveType::kPatches !=
fStencilCubicsProgram->primProc().cast<GrStencilPathShader>().primitiveType());
GrResolveLevelCounter resolveLevelCounter;
resolveLevelCounter.reset(fPath, fViewMatrix, kLinearizationIntolerance);
this->prepareIndirectOuterCubics(flushState, resolveLevelCounter);
}
return;
}
SkASSERT(fStencilCubicsProgram);
const auto& stencilCubicsShader = fStencilCubicsProgram->primProc().cast<GrPathShader>();
if (stencilCubicsShader.primitiveType() != GrPrimitiveType::kPatches) {
// Outer cubics need indirect draws.
GrResolveLevelCounter resolveLevelCounter;
this->prepareMiddleOutTrianglesAndCubics(flushState, &resolveLevelCounter);
return;
}
if (stencilCubicsShader.tessellationPatchVertexCount() == 4) {
// Triangles and tessellated curves will be drawn separately.
this->prepareMiddleOutTrianglesAndCubics(flushState);
return;
}
// We are drawing tessellated wedges.
SkASSERT(stencilCubicsShader.tessellationPatchVertexCount() == 5);
this->prepareTessellatedCubicWedges(flushState);
}
void GrPathTessellateOp::prepareMiddleOutTrianglesAndCubics(
GrMeshDrawOp::Target* target, GrResolveLevelCounter* resolveLevelCounter) {
SkASSERT(fStencilCubicsProgram);
SkASSERT(!fTriangleBuffer);
SkASSERT(!fFillTrianglesProgram);
SkASSERT(!fCubicBuffer);
SkASSERT(!fIndirectDrawBuffer);
SkASSERT(fTriangleVertexCount == 0);
SkASSERT(fCubicVertexCount == 0);
// No initial moveTo, plus an implicit close at the end; n-2 triangles fill an n-gon.
int maxInnerTriangles = fPath.countVerbs() - 1;
int maxCubics = fPath.countVerbs();
SkPoint* vertexData;
int vertexAdvancePerTriangle;
if (!fStencilTrianglesProgram) {
// Allocate the triangles as 4-point instances at the beginning of the cubic buffer.
SkASSERT(resolveLevelCounter);
vertexAdvancePerTriangle = 4;
int baseTriangleInstance;
vertexData = static_cast<SkPoint*>(target->makeVertexSpace(
sizeof(SkPoint) * 4, maxInnerTriangles + maxCubics, &fCubicBuffer,
&baseTriangleInstance));
fBaseCubicVertex = baseTriangleInstance * 4;
} else {
// Allocate the triangles as normal 3-point instances in the triangle buffer.
vertexAdvancePerTriangle = 3;
vertexData = static_cast<SkPoint*>(target->makeVertexSpace(
sizeof(SkPoint), maxInnerTriangles * 3, &fTriangleBuffer, &fBaseTriangleVertex));
}
if (!vertexData) {
return;
}
GrVectorXform xform(fViewMatrix);
GrMiddleOutPolygonTriangulator middleOut(vertexData, vertexAdvancePerTriangle,
fPath.countVerbs());
if (resolveLevelCounter) {
resolveLevelCounter->reset();
}
int numCountedCurves = 0;
for (auto [verb, pts, w] : SkPathPriv::Iterate(fPath)) {
switch (verb) {
case SkPathVerb::kMove:
middleOut.closeAndMove(pts[0]);
break;
case SkPathVerb::kLine:
middleOut.pushVertex(pts[1]);
break;
case SkPathVerb::kConic:
// We use the same quadratic formula for conics, ignoring w. This appears to be an
// upper bound on what the actual number of subdivisions would have been.
[[fallthrough]];
case SkPathVerb::kQuad:
middleOut.pushVertex(pts[2]);
if (resolveLevelCounter) {
resolveLevelCounter->countInstance(GrWangsFormula::quadratic_log2(
kLinearizationIntolerance, pts, xform));
break;
}
++numCountedCurves;
break;
case SkPathVerb::kCubic:
middleOut.pushVertex(pts[3]);
if (resolveLevelCounter) {
resolveLevelCounter->countInstance(GrWangsFormula::cubic_log2(
kLinearizationIntolerance, pts, xform));
break;
}
++numCountedCurves;
break;
case SkPathVerb::kClose:
middleOut.close();
break;
}
}
int triangleCount = middleOut.close();
SkASSERT(triangleCount <= maxInnerTriangles);
if (!fStencilTrianglesProgram) {
SkASSERT(resolveLevelCounter);
int totalInstanceCount = triangleCount + resolveLevelCounter->totalInstanceCount();
SkASSERT(vertexAdvancePerTriangle == 4);
target->putBackVertices(maxInnerTriangles + maxCubics - totalInstanceCount,
sizeof(SkPoint) * 4);
if (totalInstanceCount) {
this->prepareIndirectOuterCubicsAndTriangles(target, *resolveLevelCounter, vertexData,
triangleCount);
}
} else {
SkASSERT(vertexAdvancePerTriangle == 3);
target->putBackVertices(maxInnerTriangles - triangleCount, sizeof(SkPoint) * 3);
fTriangleVertexCount = triangleCount * 3;
if (resolveLevelCounter) {
this->prepareIndirectOuterCubics(target, *resolveLevelCounter);
} else {
this->prepareTessellatedOuterCubics(target, numCountedCurves);
}
}
}
void GrPathTessellateOp::prepareIndirectOuterCubics(
GrMeshDrawOp::Target* target, const GrResolveLevelCounter& resolveLevelCounter) {
SkASSERT(resolveLevelCounter.totalInstanceCount() >= 0);
if (resolveLevelCounter.totalInstanceCount() == 0) {
return;
}
// Allocate a buffer to store the cubic data.
SkPoint* cubicData;
int baseInstance;
cubicData = static_cast<SkPoint*>(target->makeVertexSpace(
sizeof(SkPoint) * 4, resolveLevelCounter.totalInstanceCount(), &fCubicBuffer,
&baseInstance));
if (!cubicData) {
return;
}
fBaseCubicVertex = baseInstance * 4;
this->prepareIndirectOuterCubicsAndTriangles(target, resolveLevelCounter, cubicData,
/*numTrianglesAtBeginningOfData=*/0);
}
void GrPathTessellateOp::prepareIndirectOuterCubicsAndTriangles(
GrMeshDrawOp::Target* target, const GrResolveLevelCounter& resolveLevelCounter,
SkPoint* cubicData, int numTrianglesAtBeginningOfData) {
SkASSERT(target->caps().drawInstancedSupport());
SkASSERT(numTrianglesAtBeginningOfData + resolveLevelCounter.totalInstanceCount() > 0);
SkASSERT(fStencilCubicsProgram);
SkASSERT(cubicData);
SkASSERT(fCubicVertexCount == 0);
fIndirectIndexBuffer = GrMiddleOutCubicShader::FindOrMakeMiddleOutIndexBuffer(
target->resourceProvider());
if (!fIndirectIndexBuffer) {
return;
}
// Here we treat fCubicBuffer as an instance buffer. It should have been prepared with the base
// vertex on an instance boundary in order to accommodate this.
SkASSERT(fBaseCubicVertex % 4 == 0);
int baseInstance = fBaseCubicVertex >> 2;
// Start preparing the indirect draw buffer.
fIndirectDrawCount = resolveLevelCounter.totalIndirectDrawCount();
if (numTrianglesAtBeginningOfData) {
++fIndirectDrawCount; // Add an indirect draw for the triangles at the beginning.
}
// Allocate space for the GrDrawIndexedIndirectCommand structs.
GrDrawIndexedIndirectCommand* indirectData = target->makeDrawIndexedIndirectSpace(
fIndirectDrawCount, &fIndirectDrawBuffer, &fIndirectDrawOffset);
if (!indirectData) {
SkASSERT(!fIndirectDrawBuffer);
return;
}
// Fill out the GrDrawIndexedIndirectCommand structs and determine the starting instance data
// location at each resolve level.
SkPoint* instanceLocations[kMaxResolveLevel + 1];
int indirectIdx = 0;
int runningInstanceCount = 0;
if (numTrianglesAtBeginningOfData) {
// The caller has already packed "triangleInstanceCount" triangles into 4-point instances
// at the beginning of the instance buffer. Add a special-case indirect draw here that will
// emit the triangles [P0, P1, P2] from these 4-point instances.
indirectData[0] = GrMiddleOutCubicShader::MakeDrawTrianglesIndirectCmd(
numTrianglesAtBeginningOfData, baseInstance);
indirectIdx = 1;
runningInstanceCount = numTrianglesAtBeginningOfData;
}
for (int resolveLevel = 1; resolveLevel <= kMaxResolveLevel; ++resolveLevel) {
int instanceCountAtCurrLevel = resolveLevelCounter[resolveLevel];
if (!instanceCountAtCurrLevel) {
SkDEBUGCODE(instanceLocations[resolveLevel] = nullptr;)
continue;
}
instanceLocations[resolveLevel] = cubicData + runningInstanceCount * 4;
indirectData[indirectIdx++] = GrMiddleOutCubicShader::MakeDrawCubicsIndirectCmd(
resolveLevel, instanceCountAtCurrLevel, baseInstance + runningInstanceCount);
runningInstanceCount += instanceCountAtCurrLevel;
}
#ifdef SK_DEBUG
SkASSERT(indirectIdx == fIndirectDrawCount);
SkASSERT(runningInstanceCount == numTrianglesAtBeginningOfData +
resolveLevelCounter.totalInstanceCount());
SkASSERT(fIndirectDrawCount > 0);
SkPoint* endLocations[kMaxResolveLevel + 1];
int lastResolveLevel = 0;
for (int resolveLevel = 1; resolveLevel <= kMaxResolveLevel; ++resolveLevel) {
if (!instanceLocations[resolveLevel]) {
endLocations[resolveLevel] = nullptr;
continue;
}
endLocations[lastResolveLevel] = instanceLocations[resolveLevel];
lastResolveLevel = resolveLevel;
}
int totalInstanceCount = numTrianglesAtBeginningOfData +
resolveLevelCounter.totalInstanceCount();
endLocations[lastResolveLevel] = cubicData + totalInstanceCount * 4;
#endif
fCubicVertexCount = numTrianglesAtBeginningOfData * 4;
if (resolveLevelCounter.totalInstanceCount()) {
GrVectorXform xform(fViewMatrix);
for (auto [verb, pts, w] : SkPathPriv::Iterate(fPath)) {
int level;
switch (verb) {
default:
continue;
case SkPathVerb::kConic:
// We use the same quadratic formula for conics, ignoring w. This appears to be
// an upper bound on what the actual number of subdivisions would have been.
[[fallthrough]];
case SkPathVerb::kQuad:
level = GrWangsFormula::quadratic_log2(kLinearizationIntolerance, pts, xform);
break;
case SkPathVerb::kCubic:
level = GrWangsFormula::cubic_log2(kLinearizationIntolerance, pts, xform);
break;
}
if (level == 0) {
continue;
}
level = std::min(level, kMaxResolveLevel);
switch (verb) {
case SkPathVerb::kQuad:
GrPathUtils::convertQuadToCubic(pts, instanceLocations[level]);
break;
case SkPathVerb::kCubic:
memcpy(instanceLocations[level], pts, sizeof(SkPoint) * 4);
break;
case SkPathVerb::kConic:
GrPathShader::WriteConicPatch(pts, *w, instanceLocations[level]);
break;
default:
SkUNREACHABLE;
}
instanceLocations[level] += 4;
fCubicVertexCount += 4;
}
}
#ifdef SK_DEBUG
for (int i = 1; i <= kMaxResolveLevel; ++i) {
SkASSERT(instanceLocations[i] == endLocations[i]);
}
SkASSERT(fCubicVertexCount == (numTrianglesAtBeginningOfData +
resolveLevelCounter.totalInstanceCount()) * 4);
#endif
}
void GrPathTessellateOp::prepareTessellatedOuterCubics(GrMeshDrawOp::Target* target,
int numCountedCurves) {
SkASSERT(target->caps().shaderCaps()->tessellationSupport());
SkASSERT(numCountedCurves >= 0);
SkASSERT(!fCubicBuffer);
SkASSERT(fStencilCubicsProgram);
SkASSERT(fCubicVertexCount == 0);
if (numCountedCurves == 0) {
return;
}
auto* vertexData = static_cast<SkPoint*>(target->makeVertexSpace(
sizeof(SkPoint), numCountedCurves * 4, &fCubicBuffer, &fBaseCubicVertex));
if (!vertexData) {
return;
}
for (auto [verb, pts, w] : SkPathPriv::Iterate(fPath)) {
switch (verb) {
default:
continue;
case SkPathVerb::kQuad:
SkASSERT(fCubicVertexCount < numCountedCurves * 4);
GrPathUtils::convertQuadToCubic(pts, vertexData + fCubicVertexCount);
break;
case SkPathVerb::kCubic:
SkASSERT(fCubicVertexCount < numCountedCurves * 4);
memcpy(vertexData + fCubicVertexCount, pts, sizeof(SkPoint) * 4);
break;
case SkPathVerb::kConic:
SkASSERT(fCubicVertexCount < numCountedCurves * 4);
GrPathShader::WriteConicPatch(pts, *w, vertexData + fCubicVertexCount);
break;
}
fCubicVertexCount += 4;
}
SkASSERT(fCubicVertexCount == numCountedCurves * 4);
}
void GrPathTessellateOp::prepareTessellatedCubicWedges(GrMeshDrawOp::Target* target) {
SkASSERT(target->caps().shaderCaps()->tessellationSupport());
SkASSERT(!fCubicBuffer);
SkASSERT(fStencilCubicsProgram);
SkASSERT(fCubicVertexCount == 0);
// No initial moveTo, one wedge per verb, plus an implicit close at the end.
// Each wedge has 5 vertices.
int maxVertices = (fPath.countVerbs() + 1) * 5;
GrEagerDynamicVertexAllocator vertexAlloc(target, &fCubicBuffer, &fBaseCubicVertex);
auto* vertexData = vertexAlloc.lock<SkPoint>(maxVertices);
if (!vertexData) {
return;
}
GrMidpointContourParser parser(fPath);
while (parser.parseNextContour()) {
SkPoint midpoint = parser.currentMidpoint();
SkPoint startPoint = {0, 0};
SkPoint lastPoint = startPoint;
for (auto [verb, pts, w] : parser.currentContour()) {
switch (verb) {
case SkPathVerb::kMove:
startPoint = lastPoint = pts[0];
continue;
case SkPathVerb::kClose:
continue; // Ignore. We can assume an implicit close at the end.
case SkPathVerb::kLine:
GrPathUtils::convertLineToCubic(pts[0], pts[1], vertexData + fCubicVertexCount);
lastPoint = pts[1];
break;
case SkPathVerb::kQuad:
GrPathUtils::convertQuadToCubic(pts, vertexData + fCubicVertexCount);
lastPoint = pts[2];
break;
case SkPathVerb::kCubic:
memcpy(vertexData + fCubicVertexCount, pts, sizeof(SkPoint) * 4);
lastPoint = pts[3];
break;
case SkPathVerb::kConic:
GrPathShader::WriteConicPatch(pts, *w, vertexData + fCubicVertexCount);
lastPoint = pts[2];
break;
}
vertexData[fCubicVertexCount + 4] = midpoint;
fCubicVertexCount += 5;
}
if (lastPoint != startPoint) {
GrPathUtils::convertLineToCubic(lastPoint, startPoint, vertexData + fCubicVertexCount);
vertexData[fCubicVertexCount + 4] = midpoint;
fCubicVertexCount += 5;
}
}
vertexAlloc.unlock(fCubicVertexCount);
}
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 (fCubicVertexCount > 0) {
SkASSERT(fStencilCubicsProgram);
SkASSERT(fCubicBuffer);
flushState->bindPipelineAndScissorClip(*fStencilCubicsProgram, this->bounds());
if (fIndirectDrawBuffer) {
SkASSERT(fIndirectIndexBuffer);
flushState->bindBuffers(fIndirectIndexBuffer, fCubicBuffer, nullptr);
flushState->drawIndexedIndirect(fIndirectDrawBuffer.get(), fIndirectDrawOffset,
fIndirectDrawCount);
} else {
flushState->bindBuffers(nullptr, nullptr, fCubicBuffer);
flushState->draw(fCubicVertexCount, fBaseCubicVertex);
if (flushState->caps().requiresManualFBBarrierAfterTessellatedStencilDraw()) {
flushState->gpu()->insertManualFramebufferBarrier(); // http://skbug.com/9739
}
}
}
}
void GrPathTessellateOp::drawCoverPass(GrOpFlushState* flushState) {
if (fFillTrianglesProgram) {
SkASSERT(fTriangleBuffer);
SkASSERT(fTriangleVertexCount > 0);
// We have a triangulation of the path's inner polygon. This is the fast path. Fill those
// triangles directly to the screen.
flushState->bindPipelineAndScissorClip(*fFillTrianglesProgram, this->bounds());
flushState->bindTextures(fFillTrianglesProgram->primProc(), nullptr, *fPipelineForFills);
flushState->bindBuffers(nullptr, nullptr, fTriangleBuffer);
flushState->draw(fTriangleVertexCount, fBaseTriangleVertex);
if (fCubicVertexCount > 0) {
SkASSERT(fFillPathProgram);
SkASSERT(fCubicBuffer);
// 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.
flushState->bindPipelineAndScissorClip(*fFillPathProgram, this->bounds());
flushState->bindTextures(fFillPathProgram->primProc(), nullptr, *fPipelineForFills);
// Here we treat fCubicBuffer as an instance buffer. It should have been prepared with
// the base vertex on an instance boundary in order to accommodate this.
SkASSERT((fCubicVertexCount % 4) == 0);
SkASSERT((fBaseCubicVertex % 4) == 0);
flushState->bindBuffers(nullptr, fCubicBuffer, nullptr);
flushState->drawInstanced(fCubicVertexCount >> 2, fBaseCubicVertex >> 2, 4, 0);
}
} 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);
}
}