Yank GrPathTessellationShader out of PatchTessellators

The tessellators are now agnostic of the shader class, which makes
them mostly usable by Graphite now. Also moves the code for writing
the middle-out vertex and index buffers into the PatchTessellators,
which makes sense since they also write the instance buffers.

Bug: skia:12524
Change-Id: I6c415645e389e056c0db1d93663b1b295d6b4535
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/469631
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
This commit is contained in:
Chris Dalton 2021-11-10 17:39:56 -07:00 committed by SkCQ
parent 6d823b35fa
commit f0f447c2f0
15 changed files with 517 additions and 454 deletions

View File

@ -143,13 +143,12 @@ DEF_PATH_TESS_BENCH(GrPathCurveTessellator, make_cubic_path(8), SkMatrix::I()) {
GrPipeline noVaryingsPipeline(GrScissorTest::kDisabled, SkBlendMode::kSrcOver,
GrSwizzle::RGBA());
auto tess = PathCurveTessellator::Make(&arena,
fMatrix,
SK_PMColor4fTRANSPARENT,
PathCurveTessellator::DrawInnerFan::kNo,
fTarget->caps().minPathVerbsForHwTessellation(),
noVaryingsPipeline,
fTarget->caps());
tess->prepare(fTarget.get(), {gAlmostIdentity, fPath, SK_PMColor4fTRANSPARENT},
fTarget->caps().shaderCaps()->infinitySupport());
tess->prepare(fTarget.get(),
1 << PathCurveTessellator::kMaxFixedResolveLevel,
fMatrix,
{gAlmostIdentity, fPath, SK_PMColor4fTRANSPARENT},
fPath.countVerbs());
}
@ -158,12 +157,11 @@ DEF_PATH_TESS_BENCH(GrPathWedgeTessellator, make_cubic_path(8), SkMatrix::I()) {
GrPipeline noVaryingsPipeline(GrScissorTest::kDisabled, SkBlendMode::kSrcOver,
GrSwizzle::RGBA());
auto tess = PathWedgeTessellator::Make(&arena,
fMatrix,
SK_PMColor4fTRANSPARENT,
fTarget->caps().minPathVerbsForHwTessellation(),
noVaryingsPipeline,
fTarget->caps());
tess->prepare(fTarget.get(), {gAlmostIdentity, fPath, SK_PMColor4fTRANSPARENT},
fTarget->caps().shaderCaps()->infinitySupport());
tess->prepare(fTarget.get(),
1 << PathCurveTessellator::kMaxFixedResolveLevel,
fMatrix,
{gAlmostIdentity, fPath, SK_PMColor4fTRANSPARENT},
fPath.countVerbs());
}

View File

@ -80,60 +80,62 @@ private:
const SkMatrix& shaderMatrix = SkMatrix::I();
const SkMatrix& pathMatrix = fMatrix;
const GrCaps& caps = flushState->caps();
const GrShaderCaps& shaderCaps = *caps.shaderCaps();
int numVerbsToGetMiddleOut = 0;
int numVerbsToGetTessellation = caps.minPathVerbsForHwTessellation();
auto pipeline = GrSimpleMeshDrawOpHelper::CreatePipeline(flushState, std::move(fProcessors),
fPipelineFlags);
int numVerbs;
switch (fMode) {
using DrawInnerFan = PathCurveTessellator::DrawInnerFan;
case Mode::kWedgeMiddleOut:
fTessellator = PathWedgeTessellator::Make(alloc,
shaderMatrix,
kCyan,
numVerbsToGetMiddleOut,
*pipeline,
caps);
fTessellator = PathWedgeTessellator::Make(alloc, shaderCaps.infinitySupport());
numVerbs = numVerbsToGetMiddleOut;
break;
case Mode::kCurveMiddleOut:
fTessellator = PathCurveTessellator::Make(alloc,
shaderMatrix,
kCyan,
DrawInnerFan::kYes,
numVerbsToGetMiddleOut,
*pipeline,
caps);
shaderCaps.infinitySupport());
numVerbs = numVerbsToGetMiddleOut;
break;
case Mode::kWedgeTessellate:
fTessellator = PathWedgeTessellator::Make(alloc,
shaderMatrix,
kCyan,
numVerbsToGetTessellation,
*pipeline,
caps);
fTessellator = PathWedgeTessellator::Make(alloc, shaderCaps.infinitySupport());
numVerbs = numVerbsToGetTessellation;
break;
case Mode::kCurveTessellate:
fTessellator = PathCurveTessellator::Make(alloc,
shaderMatrix,
kCyan,
DrawInnerFan::kYes,
numVerbsToGetTessellation,
*pipeline,
caps);
shaderCaps.infinitySupport());
numVerbs = numVerbsToGetTessellation;
break;
}
fTessellator->prepare(flushState, {pathMatrix, fPath, kCyan}, fPath.countVerbs());
auto* tessShader = GrPathTessellationShader::Make(alloc,
shaderMatrix,
kCyan,
numVerbs,
*pipeline,
fTessellator->patchAttribs(),
caps);
fTessellator->prepare(flushState,
tessShader->maxTessellationSegments(*caps.shaderCaps()),
shaderMatrix,
{pathMatrix, fPath, kCyan},
fPath.countVerbs());
if (!tessShader->willUseTessellationShaders()) {
fTessellator->prepareFixedCountBuffers(flushState->resourceProvider());
}
fProgram = GrTessellationShader::MakeProgram({alloc, flushState->writeView(),
flushState->usesMSAASurface(),
&flushState->dstProxyView(),
flushState->renderPassBarriers(),
GrLoadOp::kClear, &flushState->caps()},
fTessellator->shader(), pipeline,
tessShader,
pipeline,
&GrUserStencilSettings::kUnused);
}
void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
flushState->bindPipeline(*fProgram, chainBounds);
fTessellator->draw(flushState);
fTessellator->draw(flushState, fProgram->geomProc().willUseTessellationShaders());
}
const SkPath fPath;

View File

@ -44,6 +44,8 @@ public:
}
}
int maxTessellationSegments(const GrShaderCaps&) const override { SkUNREACHABLE; }
private:
const char* name() const final { return "tessellate_HullShader"; }
void addToKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const final {}
@ -249,15 +251,19 @@ void PathInnerTriangulateOp::prePreparePrograms(const GrTessellationShader::Prog
// Pass 1: Tessellate the outer curves into the stencil buffer.
if (!isLinear) {
fTessellator = PathCurveTessellator::Make(args.fArena,
fViewMatrix,
SK_PMColor4fTRANSPARENT,
PathCurveTessellator::DrawInnerFan::kNo,
fPath.countVerbs(),
*pipelineForStencils,
*args.fCaps);
args.fCaps->shaderCaps()->infinitySupport());
auto* tessShader = GrPathTessellationShader::Make(args.fArena,
fViewMatrix,
SK_PMColor4fTRANSPARENT,
fPath.countVerbs(),
*pipelineForStencils,
fTessellator->patchAttribs(),
*args.fCaps);
const GrUserStencilSettings* stencilPathSettings =
GrPathTessellationShader::StencilPathSettings(GrFillRuleForSkPath(fPath));
fStencilCurvesProgram = GrTessellationShader::MakeProgram(args, fTessellator->shader(),
fStencilCurvesProgram = GrTessellationShader::MakeProgram(args,
tessShader,
pipelineForStencils,
stencilPathSettings);
}
@ -412,10 +418,16 @@ void PathInnerTriangulateOp::onPrepare(GrOpFlushState* flushState) {
if (fTessellator) {
// Must be called after polysToTriangles() in order for fFanBreadcrumbs to be complete.
auto tessShader = &fStencilCurvesProgram->geomProc().cast<GrPathTessellationShader>();
fTessellator->prepare(flushState,
tessShader->maxTessellationSegments(*flushState->caps().shaderCaps()),
tessShader->viewMatrix(),
{SkMatrix::I(), fPath, SK_PMColor4fTRANSPARENT},
fPath.countVerbs(),
&fFanBreadcrumbs);
if (!tessShader->willUseTessellationShaders()) {
fTessellator->prepareFixedCountBuffers(flushState->resourceProvider());
}
}
if (!flushState->caps().shaderCaps()->vertexIDSupport()) {
@ -433,7 +445,8 @@ void PathInnerTriangulateOp::onExecute(GrOpFlushState* flushState, const SkRect&
if (fStencilCurvesProgram) {
SkASSERT(fTessellator);
flushState->bindPipelineAndScissorClip(*fStencilCurvesProgram, this->bounds());
fTessellator->draw(flushState);
fTessellator->draw(flushState,
fStencilCurvesProgram->geomProc().willUseTessellationShaders());
if (flushState->caps().requiresManualFBBarrierAfterTessellatedStencilDraw()) {
flushState->gpu()->insertManualFramebufferBarrier(); // http://skbug.com/9739
}

View File

@ -158,22 +158,21 @@ void PathStencilCoverOp::prePreparePrograms(const GrTessellationShader::ProgramA
stencilPipeline,
stencilSettings);
fTessellator = PathCurveTessellator::Make(args.fArena,
shaderMatrix,
SK_PMColor4fTRANSPARENT,
PathCurveTessellator::DrawInnerFan::kNo,
fTotalCombinedPathVerbCnt,
*stencilPipeline,
*args.fCaps);
args.fCaps->shaderCaps()->infinitySupport());
} else {
fTessellator = PathWedgeTessellator::Make(args.fArena,
shaderMatrix,
SK_PMColor4fTRANSPARENT,
fTotalCombinedPathVerbCnt,
*stencilPipeline,
*args.fCaps);
args.fCaps->shaderCaps()->infinitySupport());
}
auto* tessShader = GrPathTessellationShader::Make(args.fArena,
shaderMatrix,
SK_PMColor4fTRANSPARENT,
fTotalCombinedPathVerbCnt,
*stencilPipeline,
fTessellator->patchAttribs(),
*args.fCaps);
fStencilPathProgram = GrTessellationShader::MakeProgram(args,
fTessellator->shader(),
tessShader,
stencilPipeline,
stencilSettings);
@ -260,7 +259,15 @@ void PathStencilCoverOp::onPrepare(GrOpFlushState* flushState) {
vertexAlloc.unlock(fFanVertexCount);
}
fTessellator->prepare(flushState, *fPathDrawList, fTotalCombinedPathVerbCnt);
auto tessShader = &fStencilPathProgram->geomProc().cast<GrPathTessellationShader>();
fTessellator->prepare(flushState,
tessShader->maxTessellationSegments(*flushState->caps().shaderCaps()),
tessShader->viewMatrix(),
*fPathDrawList,
fTotalCombinedPathVerbCnt);
if (!tessShader->willUseTessellationShaders()) {
fTessellator->prepareFixedCountBuffers(flushState->resourceProvider());
}
if (fCoverBBoxProgram) {
size_t instanceStride = fCoverBBoxProgram->geomProc().instanceStride();
@ -325,7 +332,7 @@ void PathStencilCoverOp::onExecute(GrOpFlushState* flushState, const SkRect& cha
// Stencil the rest of the path.
SkASSERT(fStencilPathProgram);
flushState->bindPipelineAndScissorClip(*fStencilPathProgram, this->bounds());
fTessellator->draw(flushState);
fTessellator->draw(flushState, fStencilPathProgram->geomProc().willUseTessellationShaders());
if (flushState->caps().requiresManualFBBarrierAfterTessellatedStencilDraw()) {
flushState->gpu()->insertManualFramebufferBarrier(); // http://skbug.com/9739
}

View File

@ -74,16 +74,16 @@ void PathTessellateOp::prepareTessellator(const GrTessellationShader::ProgramArg
auto* pipeline = GrTessellationShader::MakePipeline(args, fAAType, std::move(appliedClip),
std::move(fProcessors));
fTessellator = PathWedgeTessellator::Make(args.fArena,
fShaderMatrix,
this->headDraw().fColor,
fTotalCombinedPathVerbCnt,
*pipeline,
*args.fCaps,
args.fCaps->shaderCaps()->infinitySupport(),
fPatchAttribs);
fTessellationProgram = GrTessellationShader::MakeProgram(args,
fTessellator->shader(),
pipeline,
fStencil);
auto* tessShader = GrPathTessellationShader::Make(args.fArena,
fShaderMatrix,
this->headDraw().fColor,
fTotalCombinedPathVerbCnt,
*pipeline,
fTessellator->patchAttribs(),
*args.fCaps);
fTessellationProgram = GrTessellationShader::MakeProgram(args, tessShader, pipeline, fStencil);
}
void PathTessellateOp::onPrePrepare(GrRecordingContext* context,
@ -109,7 +109,15 @@ void PathTessellateOp::onPrepare(GrOpFlushState* flushState) {
&flushState->caps()}, flushState->detachAppliedClip());
SkASSERT(fTessellator);
}
fTessellator->prepare(flushState, *fPathDrawList, fTotalCombinedPathVerbCnt);
auto tessShader = &fTessellationProgram->geomProc().cast<GrPathTessellationShader>();
fTessellator->prepare(flushState,
tessShader->maxTessellationSegments(*flushState->caps().shaderCaps()),
fShaderMatrix,
*fPathDrawList,
fTotalCombinedPathVerbCnt);
if (!tessShader->willUseTessellationShaders()) {
fTessellator->prepareFixedCountBuffers(flushState->resourceProvider());
}
}
void PathTessellateOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
@ -118,7 +126,7 @@ void PathTessellateOp::onExecute(GrOpFlushState* flushState, const SkRect& chain
flushState->bindPipelineAndScissorClip(*fTessellationProgram, this->bounds());
flushState->bindTextures(fTessellationProgram->geomProc(), nullptr,
fTessellationProgram->pipeline());
fTessellator->draw(flushState);
fTessellator->draw(flushState, fTessellationProgram->geomProc().willUseTessellationShaders());
}
} // namespace skgpu::v1

View File

@ -7,18 +7,16 @@
#include "src/gpu/tessellate/PathCurveTessellator.h"
#include "src/core/SkUtils.h"
#include "src/gpu/GrMeshDrawTarget.h"
#include "src/gpu/GrResourceProvider.h"
#include "src/gpu/geometry/GrPathUtils.h"
#include "src/gpu/tessellate/AffineMatrix.h"
#include "src/gpu/tessellate/MiddleOutPolygonTriangulator.h"
#include "src/gpu/tessellate/PatchWriter.h"
#include "src/gpu/tessellate/WangsFormula.h"
#include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
#if SK_GPU_V1
#include "src/gpu/GrMeshDrawTarget.h"
#include "src/gpu/GrOpFlushState.h"
#include "src/gpu/GrResourceProvider.h"
#endif
namespace skgpu {
@ -27,45 +25,14 @@ using CubicPatch = PatchWriter::CubicPatch;
using ConicPatch = PatchWriter::ConicPatch;
using TrianglePatch = PatchWriter::TrianglePatch;
PathCurveTessellator* PathCurveTessellator::Make(SkArenaAlloc* arena,
const SkMatrix& viewMatrix,
const SkPMColor4f& color,
DrawInnerFan drawInnerFan,
int numPathVerbs,
const GrPipeline& pipeline,
const GrCaps& caps,
PatchAttribs attribs) {
if (!caps.shaderCaps()->infinitySupport()) {
attribs |= PatchAttribs::kExplicitCurveType;
}
GrPathTessellationShader* shader;
if (caps.shaderCaps()->tessellationSupport() &&
caps.shaderCaps()->infinitySupport() && // The hw tessellation shaders use infinity.
!pipeline.usesLocalCoords() && // Our tessellation back door doesn't handle varyings.
!(attribs & PatchAttribs::kColor) && // Input color isn't implemented for tessellation.
numPathVerbs >= caps.minPathVerbsForHwTessellation()) {
shader = GrPathTessellationShader::MakeHardwareTessellationShader(arena, viewMatrix, color,
attribs);
} else {
shader = GrPathTessellationShader::MakeMiddleOutFixedCountShader(*caps.shaderCaps(), arena,
viewMatrix, color,
attribs);
}
return arena->make([=](void* objStart) {
return new(objStart) PathCurveTessellator(shader, attribs, drawInnerFan);
});
}
GR_DECLARE_STATIC_UNIQUE_KEY(gFixedCountVertexBufferKey);
GR_DECLARE_STATIC_UNIQUE_KEY(gFixedCountIndexBufferKey);
void PathCurveTessellator::prepare(GrMeshDrawTarget* target,
int maxTessellationSegments,
const SkMatrix& shaderMatrix,
const PathDrawList& pathDrawList,
int totalCombinedPathVerbCnt,
const BreadcrumbTriangleList* breadcrumbTriangleList) {
SkASSERT(fVertexChunkArray.empty());
const GrShaderCaps& shaderCaps = *target->caps().shaderCaps();
SkASSERT(!fFixedResolveLevel);
// Determine how many triangles to allocate.
int maxTriangles = 0;
@ -85,10 +52,8 @@ void PathCurveTessellator::prepare(GrMeshDrawTarget* target,
if (!patchAllocCount) {
return;
}
size_t patchStride = fShader->willUseTessellationShaders() ? fShader->vertexStride() * 4
: fShader->instanceStride();
PatchWriter patchWriter(target, &fVertexChunkArray, patchStride, patchAllocCount, fAttribs);
PatchWriter patchWriter(target, &fVertexChunkArray, PatchStride(fAttribs), patchAllocCount,
fAttribs);
// Write out inner fan triangles.
if (fDrawInnerFan) {
@ -139,17 +104,7 @@ void PathCurveTessellator::prepare(GrMeshDrawTarget* target,
SkASSERT(count == breadcrumbTriangleList->count());
}
int maxFixedCountResolveLevel = GrPathTessellationShader::kMaxFixedCountResolveLevel;
int maxSegments;
if (fShader->willUseTessellationShaders()) {
// The curve shader tessellates T=0..(1/2) on the first side of the canonical triangle and
// T=(1/2)..1 on the second side. This means we get double the max tessellation segments
// for the range T=0..1.
maxSegments = shaderCaps.maxTessellationSegments() * 2;
} else {
maxSegments = 1 << maxFixedCountResolveLevel;
}
float maxSegments_pow2 = pow2(maxSegments);
float maxSegments_pow2 = pow2(maxTessellationSegments);
float maxSegments_pow4 = pow2(maxSegments_pow2);
// If using fixed count, this is the number of segments we need to emit per instance. Always
@ -158,7 +113,7 @@ void PathCurveTessellator::prepare(GrMeshDrawTarget* target,
for (auto [pathMatrix, path, color] : pathDrawList) {
AffineMatrix m(pathMatrix);
wangs_formula::VectorXform totalXform(SkMatrix::Concat(fShader->viewMatrix(), pathMatrix));
wangs_formula::VectorXform totalXform(SkMatrix::Concat(shaderMatrix, pathMatrix));
if (fAttribs & PatchAttribs::kColor) {
patchWriter.updateColorAttrib(color);
}
@ -174,7 +129,7 @@ void PathCurveTessellator::prepare(GrMeshDrawTarget* target,
break; // This quad only needs 1 segment, which is empty.
}
if (n4 <= maxSegments_pow4) {
// This quad already fits into "maxSegments" tessellation segments.
// This quad already fits in "maxTessellationSegments".
CubicPatch(patchWriter) << QuadToCubic{p0, p1, p2};
} else {
// Chop until each quad tessellation requires "maxSegments" or fewer.
@ -197,7 +152,7 @@ void PathCurveTessellator::prepare(GrMeshDrawTarget* target,
break; // This conic only needs 1 segment, which is empty.
}
if (n2 <= maxSegments_pow2) {
// This conic already fits into "maxSegments" tessellation segments.
// This conic already fits in "maxTessellationSegments".
ConicPatch(patchWriter) << p0 << p1 << p2 << *w;
} else {
// Chop until each conic tessellation requires "maxSegments" or fewer.
@ -218,7 +173,7 @@ void PathCurveTessellator::prepare(GrMeshDrawTarget* target,
break; // This cubic only needs 1 segment, which is empty.
}
if (n4 <= maxSegments_pow4) {
// This cubic already fits into "maxSegments" tessellation segments.
// This cubic already fits in "maxTessellationSegments".
CubicPatch(patchWriter) << p0 << p1 << p2 << p3;
} else {
// Chop until each cubic tessellation requires "maxSegments" or fewer.
@ -235,46 +190,127 @@ void PathCurveTessellator::prepare(GrMeshDrawTarget* target,
}
}
if (!fShader->willUseTessellationShaders()) {
// log16(n^4) == log2(n).
// We already chopped curves to make sure none needed a higher resolveLevel than
// kMaxFixedCountResolveLevel.
int fixedResolveLevel = std::min(wangs_formula::nextlog16(numFixedSegments_pow4),
maxFixedCountResolveLevel);
fFixedIndexCount =
GrPathTessellationShader::NumCurveTrianglesAtResolveLevel(fixedResolveLevel) * 3;
// log16(n^4) == log2(n).
// We already chopped curves to make sure none needed a higher resolveLevel than
// kMaxFixedResolveLevel.
fFixedResolveLevel = std::min(wangs_formula::nextlog16(numFixedSegments_pow4),
int(kMaxFixedResolveLevel));
}
GR_DEFINE_STATIC_UNIQUE_KEY(gFixedCountVertexBufferKey);
void PathCurveTessellator::WriteFixedVertexBuffer(VertexWriter vertexWriter, size_t bufferSize) {
SkASSERT(bufferSize >= sizeof(SkPoint) * 2);
SkASSERT(bufferSize % sizeof(SkPoint) == 0);
int vertexCount = bufferSize / sizeof(SkPoint);
SkASSERT(vertexCount > 3);
SkDEBUGCODE(VertexWriter end = vertexWriter.makeOffset(vertexCount * sizeof(SkPoint));)
fFixedCountVertexBuffer = target->resourceProvider()->findOrMakeStaticBuffer(
GrGpuBufferType::kVertex,
GrPathTessellationShader::SizeOfVertexBufferForMiddleOutCurves(),
gFixedCountVertexBufferKey,
GrPathTessellationShader::InitializeVertexBufferForMiddleOutCurves);
// Lay out the vertices in "middle-out" order:
//
// T= 0/1, 1/1, ; resolveLevel=0
// 1/2, ; resolveLevel=1 (0/2 and 2/2 are already in resolveLevel 0)
// 1/4, 3/4, ; resolveLevel=2 (2/4 is already in resolveLevel 1)
// 1/8, 3/8, 5/8, 7/8, ; resolveLevel=3 (2/8 and 6/8 are already in resolveLevel 2)
// ... ; resolveLevel=...
//
// Resolve level 0 is just the beginning and ending vertices.
vertexWriter << (float)0/*resolveLevel*/ << (float)0/*idx*/;
vertexWriter << (float)0/*resolveLevel*/ << (float)1/*idx*/;
GR_DEFINE_STATIC_UNIQUE_KEY(gFixedCountIndexBufferKey);
fFixedCountIndexBuffer = target->resourceProvider()->findOrMakeStaticBuffer(
GrGpuBufferType::kIndex,
GrPathTessellationShader::SizeOfIndexBufferForMiddleOutCurves(),
gFixedCountIndexBufferKey,
GrPathTessellationShader::InitializeIndexBufferForMiddleOutCurves);
// Resolve levels 1..kMaxResolveLevel.
int maxResolveLevel = SkPrevLog2(vertexCount - 1);
SkASSERT((1 << maxResolveLevel) + 1 == vertexCount);
for (int resolveLevel = 1; resolveLevel <= maxResolveLevel; ++resolveLevel) {
int numSegmentsInResolveLevel = 1 << resolveLevel;
// Write out the odd vertices in this resolveLevel. The even vertices were already written
// out in previous resolveLevels and will be indexed from there.
for (int i = 1; i < numSegmentsInResolveLevel; i += 2) {
vertexWriter << (float)resolveLevel << (float)i;
}
}
SkASSERT(vertexWriter == end);
}
void PathCurveTessellator::WriteFixedIndexBufferBaseIndex(VertexWriter vertexWriter,
size_t bufferSize,
uint16_t baseIndex) {
SkASSERT(bufferSize % (sizeof(uint16_t) * 3) == 0);
int triangleCount = bufferSize / (sizeof(uint16_t) * 3);
SkASSERT(triangleCount >= 1);
SkTArray<std::array<uint16_t, 3>> indexData(triangleCount);
// Connect the vertices with a middle-out triangulation. Refer to InitFixedCountVertexBuffer()
// for the exact vertex ordering.
//
// Resolve level 1 is just a single triangle at T=[0, 1/2, 1].
const auto* neighborInLastResolveLevel = &indexData.push_back({baseIndex,
(uint16_t)(baseIndex + 2),
(uint16_t)(baseIndex + 1)});
// Resolve levels 2..maxResolveLevel
int maxResolveLevel = SkPrevLog2(triangleCount + 1);
uint16_t nextIndex = baseIndex + 3;
SkASSERT(NumCurveTrianglesAtResolveLevel(maxResolveLevel) == triangleCount);
for (int resolveLevel = 2; resolveLevel <= maxResolveLevel; ++resolveLevel) {
SkDEBUGCODE(auto* firstTriangleInCurrentResolveLevel = indexData.end());
int numOuterTrianglelsInResolveLevel = 1 << (resolveLevel - 1);
SkASSERT(numOuterTrianglelsInResolveLevel % 2 == 0);
int numTrianglePairsInResolveLevel = numOuterTrianglelsInResolveLevel >> 1;
for (int i = 0; i < numTrianglePairsInResolveLevel; ++i) {
// First triangle shares the left edge of "neighborInLastResolveLevel".
indexData.push_back({(*neighborInLastResolveLevel)[0],
nextIndex++,
(*neighborInLastResolveLevel)[1]});
// Second triangle shares the right edge of "neighborInLastResolveLevel".
indexData.push_back({(*neighborInLastResolveLevel)[1],
nextIndex++,
(*neighborInLastResolveLevel)[2]});
++neighborInLastResolveLevel;
}
SkASSERT(neighborInLastResolveLevel == firstTriangleInCurrentResolveLevel);
}
SkASSERT(indexData.count() == triangleCount);
SkASSERT(nextIndex == baseIndex + triangleCount + 2);
vertexWriter.writeArray(indexData.data(), indexData.count());
}
#if SK_GPU_V1
void PathCurveTessellator::draw(GrOpFlushState* flushState) const {
if (fShader->willUseTessellationShaders()) {
for (const GrVertexChunk& chunk : fVertexChunkArray) {
flushState->bindBuffers(nullptr, nullptr, chunk.fBuffer);
flushState->draw(chunk.fCount * 4, chunk.fBase * 4);
}
} else {
SkASSERT(fShader->hasInstanceAttributes());
for (const GrVertexChunk& chunk : fVertexChunkArray) {
flushState->bindBuffers(fFixedCountIndexBuffer, chunk.fBuffer, fFixedCountVertexBuffer);
flushState->drawIndexedInstanced(fFixedIndexCount, 0, chunk.fCount, chunk.fBase, 0);
}
GR_DECLARE_STATIC_UNIQUE_KEY(gFixedVertexBufferKey);
GR_DECLARE_STATIC_UNIQUE_KEY(gFixedIndexBufferKey);
void PathCurveTessellator::prepareFixedCountBuffers(GrResourceProvider* rp) {
GR_DEFINE_STATIC_UNIQUE_KEY(gFixedVertexBufferKey);
fFixedVertexBuffer = rp->findOrMakeStaticBuffer(GrGpuBufferType::kVertex,
FixedVertexBufferSize(kMaxFixedResolveLevel),
gFixedVertexBufferKey,
WriteFixedVertexBuffer);
GR_DEFINE_STATIC_UNIQUE_KEY(gFixedIndexBufferKey);
fFixedIndexBuffer = rp->findOrMakeStaticBuffer(GrGpuBufferType::kIndex,
FixedIndexBufferSize(kMaxFixedResolveLevel),
gFixedIndexBufferKey,
WriteFixedIndexBuffer);
}
void PathCurveTessellator::drawTessellated(GrOpFlushState* flushState) const {
for (const GrVertexChunk& chunk : fVertexChunkArray) {
flushState->bindBuffers(nullptr, nullptr, chunk.fBuffer);
flushState->draw(chunk.fCount * 4, chunk.fBase * 4);
}
}
void PathCurveTessellator::drawFixedCount(GrOpFlushState* flushState) const {
if (!fFixedVertexBuffer || !fFixedIndexBuffer) {
return;
}
int fixedIndexCount = NumCurveTrianglesAtResolveLevel(fFixedResolveLevel) * 3;
for (const GrVertexChunk& chunk : fVertexChunkArray) {
flushState->bindBuffers(fFixedIndexBuffer, chunk.fBuffer, fFixedVertexBuffer);
flushState->drawIndexedInstanced(fixedIndexCount, 0, chunk.fCount, chunk.fBase, 0);
}
}
@ -285,6 +321,7 @@ void PathCurveTessellator::drawHullInstances(GrOpFlushState* flushState,
flushState->drawInstanced(chunk.fCount, chunk.fBase, 4, 0);
}
}
#endif
} // namespace skgpu

View File

@ -12,7 +12,6 @@
#include "src/gpu/tessellate/PathTessellator.h"
class GrCaps;
class GrGpuBuffer;
class GrPipeline;
namespace skgpu {
@ -20,7 +19,7 @@ namespace skgpu {
// Draws an array of "outer curve" patches and, optionally, inner fan triangles for
// GrCubicTessellateShader. Each patch is an independent 4-point curve, representing either a cubic
// or a conic. Quadratics are converted to cubics and triangles are converted to conics with w=Inf.
class PathCurveTessellator : public PathTessellator {
class PathCurveTessellator final : public PathTessellator {
public:
// If DrawInnerFan is kNo, this class only emits the path's outer curves. In that case the
// caller is responsible to handle the path's inner fan.
@ -29,20 +28,26 @@ public:
kYes
};
// Creates a curve tessellator with the shader type best suited for the given path description.
static PathCurveTessellator* Make(SkArenaAlloc*,
const SkMatrix& viewMatrix,
const SkPMColor4f&,
DrawInnerFan,
int numPathVerbs,
const GrPipeline&,
const GrCaps&,
PatchAttribs = PatchAttribs::kNone);
static PathCurveTessellator* Make(SkArenaAlloc* arena,
DrawInnerFan drawInnerFan,
bool infinitySupport,
PatchAttribs attribs = PatchAttribs::kNone) {
return arena->make<PathCurveTessellator>(drawInnerFan, infinitySupport, attribs);
}
PathCurveTessellator(DrawInnerFan drawInnerFan,
bool infinitySupport,
PatchAttribs attribs = PatchAttribs::kNone)
: PathTessellator(infinitySupport, attribs)
, fDrawInnerFan(drawInnerFan != DrawInnerFan::kNo) {}
void prepare(GrMeshDrawTarget* target,
int maxTessellationSegments,
const SkMatrix& shaderMatrix,
const PathDrawList& pathDrawList,
int totalCombinedPathVerbCnt) override {
this->prepare(target, pathDrawList, totalCombinedPathVerbCnt, nullptr);
int totalCombinedPathVerbCnt) final {
this->prepare(target, maxTessellationSegments, shaderMatrix, pathDrawList,
totalCombinedPathVerbCnt, nullptr);
}
// Implements PathTessellator::prepare(), also sending an additional list of breadcrumb
@ -51,33 +56,47 @@ public:
// ALSO NOTE: The breadcrumb triangles do not have a matrix. These need to be pre-transformed by
// the caller if a CPU-side transformation is desired.
void prepare(GrMeshDrawTarget*,
int maxTessellationSegments,
const SkMatrix& shaderMatrix,
const PathDrawList&,
int totalCombinedPathVerbCnt,
const BreadcrumbTriangleList*);
#if SK_GPU_V1
void draw(GrOpFlushState*) const override;
// Size of the vertex buffer to use when rendering with a fixed count shader.
constexpr static int FixedVertexBufferSize(int maxFixedResolveLevel) {
return ((1 << maxFixedResolveLevel) + 1) * sizeof(SkPoint);
}
// Draws a 4-point instance for each curve. This method is used for drawing convex hulls over
// Writes the vertex buffer to use when rendering with a fixed count shader.
static void WriteFixedVertexBuffer(VertexWriter, size_t bufferSize);
// Size of the index buffer to use when rendering with a fixed count shader.
constexpr static int FixedIndexBufferSize(int maxFixedResolveLevel) {
return NumCurveTrianglesAtResolveLevel(maxFixedResolveLevel) * 3 * sizeof(uint16_t);
}
// Writes the index buffer to use when rendering with a fixed count shader.
static void WriteFixedIndexBuffer(VertexWriter vertexWriter, size_t bufferSize) {
WriteFixedIndexBufferBaseIndex(std::move(vertexWriter), bufferSize, 0);
}
static void WriteFixedIndexBufferBaseIndex(VertexWriter, size_t bufferSize, uint16_t baseIndex);
#if SK_GPU_V1
void prepareFixedCountBuffers(GrResourceProvider*) final;
void drawTessellated(GrOpFlushState*) const final;
void drawFixedCount(GrOpFlushState*) const final;
// Draws a 4-point instance for each patch. This method is used for drawing convex hulls over
// each cubic with GrFillCubicHullShader. The caller is responsible for binding its desired
// pipeline ahead of time.
void drawHullInstances(GrOpFlushState*, sk_sp<const GrGpuBuffer> vertexBufferIfNeeded) const;
#endif
private:
PathCurveTessellator(GrPathTessellationShader* shader,
PatchAttribs attribs,
DrawInnerFan drawInnerFan)
: PathTessellator(shader, attribs)
, fDrawInnerFan(drawInnerFan == DrawInnerFan::kYes) {}
const bool fDrawInnerFan;
GrVertexChunkArray fVertexChunkArray;
// If using fixed count, this is the number of vertices we need to emit per instance.
int fFixedIndexCount;
sk_sp<const GrGpuBuffer> fFixedCountVertexBuffer;
sk_sp<const GrGpuBuffer> fFixedCountIndexBuffer;
};
} // namespace skgpu

View File

@ -16,9 +16,13 @@
class SkPath;
class GrMeshDrawTarget;
#if SK_GPU_V1
class GrGpuBuffer;
class GrOpFlushState;
class GrPathTessellationShader;
class GrResourceProvider;
#endif
namespace skgpu {
@ -28,6 +32,11 @@ class PathTessellator {
public:
using BreadcrumbTriangleList = GrInnerFanTriangulator::BreadcrumbTriangleList;
// This is the maximum number of segments contained in our vertex and index buffers for
// fixed-count rendering. If rendering in fixed-count mode and a curve requires more segments,
// it must be chopped.
constexpr static int kMaxFixedResolveLevel = 5;
struct PathDrawList {
PathDrawList(const SkMatrix& pathMatrix,
const SkPath& path,
@ -54,18 +63,36 @@ public:
virtual ~PathTessellator() {}
const GrPathTessellationShader* shader() const { return fShader; }
PatchAttribs patchAttribs() const { return fAttribs; }
// Called before draw(). Prepares GPU buffers containing the geometry to tessellate.
//
// Each path's fPathMatrix in the list is applied on the CPU while the geometry is being written
// out. This is a tool for batching, and is applied in addition to the shader's on-GPU matrix.
virtual void prepare(GrMeshDrawTarget*, const PathDrawList&, int totalCombinedPathVerbCnt) = 0;
virtual void prepare(GrMeshDrawTarget*,
int maxTessellationSegments,
const SkMatrix& shaderMatrix,
const PathDrawList&,
int totalCombinedPathVerbCnt) = 0;
#if SK_GPU_V1
// Issues draw calls for the tessellated geometry. The caller is responsible for binding its
// desired pipeline ahead of time.
virtual void draw(GrOpFlushState*) const = 0;
virtual void prepareFixedCountBuffers(GrResourceProvider*) = 0;
void draw(GrOpFlushState* flushState, bool willUseTessellationShaders) {
if (willUseTessellationShaders) {
this->drawTessellated(flushState);
} else {
this->drawFixedCount(flushState);
}
}
// Issues hardware tessellation draw calls over the patches. The caller is responsible for
// binding its desired pipeline ahead of time.
virtual void drawTessellated(GrOpFlushState*) const = 0;
// Issues fixed-count instanced draw calls over the patches. The caller is responsible for
// binding its desired pipeline ahead of time.
virtual void drawFixedCount(GrOpFlushState*) const = 0;
#endif
// Returns an upper bound on the number of combined edges there might be from all inner fans in
@ -79,11 +106,33 @@ public:
}
protected:
PathTessellator(GrPathTessellationShader* shader, PatchAttribs attribs)
: fShader(shader), fAttribs(attribs) {}
// How many triangles are in a curve with 2^resolveLevel line segments?
constexpr static int NumCurveTrianglesAtResolveLevel(int resolveLevel) {
// resolveLevel=0 -> 0 line segments -> 0 triangles
// resolveLevel=1 -> 2 line segments -> 1 triangle
// resolveLevel=2 -> 4 line segments -> 3 triangles
// resolveLevel=3 -> 8 line segments -> 7 triangles
// ...
return (1 << resolveLevel) - 1;
}
GrPathTessellationShader* const fShader;
const PatchAttribs fAttribs;
PathTessellator(bool infinitySupport, PatchAttribs attribs) : fAttribs(attribs) {
if (!infinitySupport) {
attribs |= PatchAttribs::kExplicitCurveType;
}
}
PatchAttribs fAttribs;
// Calculated during prepare(). If using fixed count, this is the resolveLevel to use on our
// instanced draws. 2^resolveLevel == numSegments.
int fFixedResolveLevel = 0;
#if SK_GPU_V1
// If using fixed-count rendering, these are the vertex and index buffers.
sk_sp<const GrGpuBuffer> fFixedVertexBuffer;
sk_sp<const GrGpuBuffer> fFixedIndexBuffer;
#endif
};
} // namespace skgpu

View File

@ -7,17 +7,15 @@
#include "src/gpu/tessellate/PathWedgeTessellator.h"
#include "src/gpu/GrMeshDrawTarget.h"
#include "src/gpu/GrResourceProvider.h"
#include "src/gpu/geometry/GrPathUtils.h"
#include "src/gpu/tessellate/AffineMatrix.h"
#include "src/gpu/tessellate/PatchWriter.h"
#include "src/gpu/tessellate/Tessellation.h"
#include "src/gpu/tessellate/PathCurveTessellator.h"
#include "src/gpu/tessellate/WangsFormula.h"
#include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
#if SK_GPU_V1
#include "src/gpu/GrMeshDrawTarget.h"
#include "src/gpu/GrOpFlushState.h"
#include "src/gpu/GrResourceProvider.h"
#endif
namespace skgpu {
@ -128,44 +126,13 @@ private:
} // namespace
PathTessellator* PathWedgeTessellator::Make(SkArenaAlloc* arena,
const SkMatrix& viewMatrix,
const SkPMColor4f& color,
int numPathVerbs,
const GrPipeline& pipeline,
const GrCaps& caps,
PatchAttribs attribs) {
GrPathTessellationShader* shader;
attribs |= PatchAttribs::kFanPoint;
if (!caps.shaderCaps()->infinitySupport()) {
attribs |= PatchAttribs::kExplicitCurveType;
}
if (caps.shaderCaps()->tessellationSupport() &&
caps.shaderCaps()->infinitySupport() && // The hw tessellation shaders use infinity.
!pipeline.usesLocalCoords() && // Our tessellation back door doesn't handle varyings.
!(attribs & PatchAttribs::kColor) && // Input color isn't implemented for tessellation.
numPathVerbs >= caps.minPathVerbsForHwTessellation()) {
shader = GrPathTessellationShader::MakeHardwareTessellationShader(arena, viewMatrix, color,
attribs);
} else {
shader = GrPathTessellationShader::MakeMiddleOutFixedCountShader(*caps.shaderCaps(), arena,
viewMatrix, color,
attribs);
}
return arena->make([=](void* objStart) {
return new(objStart) PathWedgeTessellator(shader, attribs);
});
}
GR_DECLARE_STATIC_UNIQUE_KEY(gFixedCountVertexBufferKey);
GR_DECLARE_STATIC_UNIQUE_KEY(gFixedCountIndexBufferKey);
void PathWedgeTessellator::prepare(GrMeshDrawTarget* target,
int maxTessellationSegments,
const SkMatrix& shaderMatrix,
const PathDrawList& pathDrawList,
int totalCombinedPathVerbCnt) {
SkASSERT(fVertexChunkArray.empty());
const GrShaderCaps& shaderCaps = *target->caps().shaderCaps();
SkASSERT(!fFixedResolveLevel);
// Over-allocate enough wedges for 1 in 4 to chop.
int maxWedges = MaxCombinedFanEdgesInPathDrawList(totalCombinedPathVerbCnt);
@ -173,19 +140,10 @@ void PathWedgeTessellator::prepare(GrMeshDrawTarget* target,
if (!wedgeAllocCount) {
return;
}
size_t patchStride = fShader->willUseTessellationShaders() ? fShader->vertexStride() * 5
: fShader->instanceStride();
PatchWriter patchWriter(target, &fVertexChunkArray, PatchStride(fAttribs), wedgeAllocCount,
fAttribs);
PatchWriter patchWriter(target, &fVertexChunkArray, patchStride, wedgeAllocCount, fAttribs);
int maxFixedCountResolveLevel = GrPathTessellationShader::kMaxFixedCountResolveLevel;
int maxSegments;
if (fShader->willUseTessellationShaders()) {
maxSegments = shaderCaps.maxTessellationSegments();
} else {
maxSegments = 1 << maxFixedCountResolveLevel;
}
float maxSegments_pow2 = pow2(maxSegments);
float maxSegments_pow2 = pow2(maxTessellationSegments);
float maxSegments_pow4 = pow2(maxSegments_pow2);
// If using fixed count, this is the number of segments we need to emit per instance. Always
@ -194,7 +152,7 @@ void PathWedgeTessellator::prepare(GrMeshDrawTarget* target,
for (auto [pathMatrix, path, color] : pathDrawList) {
AffineMatrix m(pathMatrix);
wangs_formula::VectorXform totalXform(SkMatrix::Concat(fShader->viewMatrix(), pathMatrix));
wangs_formula::VectorXform totalXform(SkMatrix::Concat(shaderMatrix, pathMatrix));
if (fAttribs & PatchAttribs::kColor) {
patchWriter.updateColorAttrib(color);
}
@ -223,7 +181,7 @@ void PathWedgeTessellator::prepare(GrMeshDrawTarget* target,
pts,
totalXform);
if (n4 <= maxSegments_pow4) {
// This quad already fits into "maxSegments" tessellation segments.
// This quad already fits in "maxTessellationSegments".
CubicPatch(patchWriter) << QuadToCubic{p0, p1, p2};
} else {
// Chop until each quad tessellation requires "maxSegments" or fewer.
@ -244,7 +202,7 @@ void PathWedgeTessellator::prepare(GrMeshDrawTarget* target,
*w,
totalXform);
if (n2 <= maxSegments_pow2) {
// This conic already fits into "maxSegments" tessellation segments.
// This conic already fits in "maxTessellationSegments".
ConicPatch(patchWriter) << p0 << p1 << p2 << *w;
} else {
// Chop until each conic tessellation requires "maxSegments" or fewer.
@ -263,7 +221,7 @@ void PathWedgeTessellator::prepare(GrMeshDrawTarget* target,
pts,
totalXform);
if (n4 <= maxSegments_pow4) {
// This cubic already fits into "maxSegments" tessellation segments.
// This cubic already fits in "maxTessellationSegments".
CubicPatch(patchWriter) << p0 << p1 << p2 << p3;
} else {
// Chop until each cubic tessellation requires "maxSegments" or fewer.
@ -288,50 +246,76 @@ void PathWedgeTessellator::prepare(GrMeshDrawTarget* target,
}
}
if (!fShader->willUseTessellationShaders()) {
// log16(n^4) == log2(n).
// We already chopped curves to make sure none needed a higher resolveLevel than
// kMaxFixedCountResolveLevel.
int fixedResolveLevel = std::min(wangs_formula::nextlog16(numFixedSegments_pow4),
maxFixedCountResolveLevel);
int numCurveTriangles =
GrPathTessellationShader::NumCurveTrianglesAtResolveLevel(fixedResolveLevel);
// Emit 3 vertices per curve triangle, plus 3 more for the fan triangle.
fFixedIndexCount = numCurveTriangles * 3 + 3;
// log16(n^4) == log2(n).
// We already chopped curves to make sure none needed a higher resolveLevel than
// kMaxFixedResolveLevel.
fFixedResolveLevel = std::min(wangs_formula::nextlog16(numFixedSegments_pow4),
int(kMaxFixedResolveLevel));
}
GR_DEFINE_STATIC_UNIQUE_KEY(gFixedCountVertexBufferKey);
void PathWedgeTessellator::WriteFixedVertexBuffer(VertexWriter vertexWriter, size_t bufferSize) {
SkASSERT(bufferSize >= sizeof(SkPoint));
fFixedCountVertexBuffer = target->resourceProvider()->findOrMakeStaticBuffer(
GrGpuBufferType::kVertex,
GrPathTessellationShader::SizeOfVertexBufferForMiddleOutWedges(),
gFixedCountVertexBufferKey,
GrPathTessellationShader::InitializeVertexBufferForMiddleOutWedges);
// Start out with the fan point. A negative resolve level indicates the fan point.
vertexWriter << -1.f/*resolveLevel*/ << -1.f/*idx*/;
GR_DEFINE_STATIC_UNIQUE_KEY(gFixedCountIndexBufferKey);
// The rest is the same as for curves.
PathCurveTessellator::WriteFixedVertexBuffer(std::move(vertexWriter),
bufferSize - sizeof(SkPoint));
}
fFixedCountIndexBuffer = target->resourceProvider()->findOrMakeStaticBuffer(
GrGpuBufferType::kIndex,
GrPathTessellationShader::SizeOfIndexBufferForMiddleOutWedges(),
gFixedCountIndexBufferKey,
GrPathTessellationShader::InitializeIndexBufferForMiddleOutWedges);
}
void PathWedgeTessellator::WriteFixedIndexBuffer(VertexWriter vertexWriter, size_t bufferSize) {
SkASSERT(bufferSize >= sizeof(uint16_t) * 3);
// Start out with the fan triangle.
vertexWriter << (uint16_t)0 << (uint16_t)1 << (uint16_t)2;
// The rest is the same as for curves, with a baseIndex of 1.
PathCurveTessellator::WriteFixedIndexBufferBaseIndex(std::move(vertexWriter),
bufferSize - sizeof(uint16_t) * 3,
1);
}
#if SK_GPU_V1
void PathWedgeTessellator::draw(GrOpFlushState* flushState) const {
if (fShader->willUseTessellationShaders()) {
for (const GrVertexChunk& chunk : fVertexChunkArray) {
flushState->bindBuffers(nullptr, nullptr, chunk.fBuffer);
flushState->draw(chunk.fCount * 5, chunk.fBase * 5);
}
} else {
SkASSERT(fShader->hasInstanceAttributes());
for (const GrVertexChunk& chunk : fVertexChunkArray) {
flushState->bindBuffers(fFixedCountIndexBuffer, chunk.fBuffer, fFixedCountVertexBuffer);
flushState->drawIndexedInstanced(fFixedIndexCount, 0, chunk.fCount, chunk.fBase, 0);
}
GR_DECLARE_STATIC_UNIQUE_KEY(gFixedVertexBufferKey);
GR_DECLARE_STATIC_UNIQUE_KEY(gFixedIndexBufferKey);
void PathWedgeTessellator::prepareFixedCountBuffers(GrResourceProvider* rp) {
GR_DEFINE_STATIC_UNIQUE_KEY(gFixedVertexBufferKey);
fFixedVertexBuffer = rp->findOrMakeStaticBuffer(GrGpuBufferType::kVertex,
FixedVertexBufferSize(kMaxFixedResolveLevel),
gFixedVertexBufferKey,
WriteFixedVertexBuffer);
GR_DEFINE_STATIC_UNIQUE_KEY(gFixedIndexBufferKey);
fFixedIndexBuffer = rp->findOrMakeStaticBuffer(GrGpuBufferType::kIndex,
FixedIndexBufferSize(kMaxFixedResolveLevel),
gFixedIndexBufferKey,
WriteFixedIndexBuffer);
}
void PathWedgeTessellator::drawTessellated(GrOpFlushState* flushState) const {
for (const GrVertexChunk& chunk : fVertexChunkArray) {
flushState->bindBuffers(nullptr, nullptr, chunk.fBuffer);
flushState->draw(chunk.fCount * 5, chunk.fBase * 5);
}
}
void PathWedgeTessellator::drawFixedCount(GrOpFlushState* flushState) const {
if (!fFixedVertexBuffer || !fFixedIndexBuffer) {
return;
}
// Emit 3 vertices per curve triangle, plus 3 more for the fan triangle.
int fixedIndexCount = (NumCurveTrianglesAtResolveLevel(fFixedResolveLevel) + 1) * 3;
for (const GrVertexChunk& chunk : fVertexChunkArray) {
flushState->bindBuffers(fFixedIndexBuffer, chunk.fBuffer, fFixedVertexBuffer);
flushState->drawIndexedInstanced(fixedIndexCount, 0, chunk.fCount, chunk.fBase, 0);
}
}
#endif
} // namespace skgpu

View File

@ -12,43 +12,64 @@
#include "src/gpu/tessellate/PathTessellator.h"
class GrCaps;
class GrGpuBuffer;
class GrPipeline;
#if SK_GPU_V1
class GrGpuBuffer;
class GrResourceProvider;
#endif
namespace skgpu {
// Prepares an array of "wedge" patches for GrWedgeTessellateShader. A wedge is an independent,
// 5-point closed contour consisting of 4 control points plus an anchor point fanning from the
// center of the curve's resident contour. A wedge can be either a cubic or a conic. Quadratics and
// lines are converted to cubics. Once stencilled, these wedges alone define the complete path.
class PathWedgeTessellator : public PathTessellator {
class PathWedgeTessellator final : public PathTessellator {
public:
// Creates a wedge tessellator with the shader type best suited for the given path description.
static PathTessellator* Make(SkArenaAlloc*,
const SkMatrix& viewMatrix,
const SkPMColor4f&,
int numPathVerbs,
const GrPipeline&,
const GrCaps&,
PatchAttribs = PatchAttribs::kNone);
static PathWedgeTessellator* Make(SkArenaAlloc* arena,
bool infinitySupport,
PatchAttribs attribs = PatchAttribs::kNone) {
return arena->make<PathWedgeTessellator>(infinitySupport, attribs);
}
void prepare(GrMeshDrawTarget*, const PathDrawList&, int totalCombinedPathVerbCnt) override;
PathWedgeTessellator(bool infinitySupport, PatchAttribs attribs = PatchAttribs::kNone)
: PathTessellator(infinitySupport, attribs) {
fAttribs |= PatchAttribs::kFanPoint;
}
void prepare(GrMeshDrawTarget*,
int maxTessellationSegments,
const SkMatrix& shaderMatrix,
const PathDrawList&,
int totalCombinedPathVerbCnt) final;
// Size of the vertex buffer to use when rendering with a fixed count shader.
constexpr static int FixedVertexBufferSize(int maxFixedResolveLevel) {
return (((1 << maxFixedResolveLevel) + 1) + 1/*fan vertex*/) * sizeof(SkPoint);
}
// Writes the vertex buffer to use when rendering with a fixed count shader.
static void WriteFixedVertexBuffer(VertexWriter, size_t bufferSize);
// Size of the index buffer to use when rendering with a fixed count shader.
constexpr static int FixedIndexBufferSize(int maxFixedResolveLevel) {
return (NumCurveTrianglesAtResolveLevel(maxFixedResolveLevel) + 1/*fan triangle*/) *
3 * sizeof(uint16_t);
}
// Writes the index buffer to use when rendering with a fixed count shader.
static void WriteFixedIndexBuffer(VertexWriter vertexWriter, size_t bufferSize);
#if SK_GPU_V1
void draw(GrOpFlushState*) const override;
void prepareFixedCountBuffers(GrResourceProvider*) final;
void drawTessellated(GrOpFlushState*) const final;
void drawFixedCount(GrOpFlushState*) const final;
#endif
private:
PathWedgeTessellator(GrPathTessellationShader* shader, PatchAttribs attribs)
: PathTessellator(shader, attribs) {}
GrVertexChunkArray fVertexChunkArray;
// If using fixed count, this is the number of vertices we need to emit per instance.
int fFixedIndexCount;
sk_sp<const GrGpuBuffer> fFixedCountVertexBuffer;
sk_sp<const GrGpuBuffer> fFixedCountIndexBuffer;
};
} // namespace skgpu

View File

@ -38,6 +38,16 @@ enum class PatchAttribs {
GR_MAKE_BITFIELD_CLASS_OPS(PatchAttribs)
// Returns the packed size in bytes of a tessellation patch (or instance) in GPU buffers.
constexpr size_t PatchStride(PatchAttribs attribs) {
return sizeof(float) * 8 + // 4 control points
(attribs & PatchAttribs::kFanPoint ? sizeof(float) * 2 : 0) +
(attribs & PatchAttribs::kColor
? (attribs & PatchAttribs::kWideColorIfEnabled ? sizeof(float)
: sizeof(uint8_t)) * 4 : 0) +
(attribs & PatchAttribs::kExplicitCurveType ? sizeof(float) : 0);
}
// Use familiar type names from SkSL.
template<int N> using vec = skvx::Vec<N, float>;
using float2 = vec<2>;

View File

@ -28,6 +28,8 @@ public:
this->setVertexAttributes(&kInputPointAttrib, 1);
}
int maxTessellationSegments(const GrShaderCaps&) const override { SkUNREACHABLE; }
private:
const char* name() const final { return "tessellate_SimpleTriangleShader"; }
void addToKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const final {}
@ -54,6 +56,27 @@ std::unique_ptr<GrGeometryProcessor::ProgramImpl> SimpleTriangleShader::makeProg
} // namespace
GrPathTessellationShader* GrPathTessellationShader::Make(SkArenaAlloc* arena,
const SkMatrix& viewMatrix,
const SkPMColor4f& color,
int totalCombinedPathVerbCnt,
const GrPipeline& pipeline,
skgpu::PatchAttribs attribs,
const GrCaps& caps) {
if (caps.shaderCaps()->tessellationSupport() &&
totalCombinedPathVerbCnt >= caps.minPathVerbsForHwTessellation() &&
!pipeline.usesLocalCoords() && // Our tessellation back door doesn't handle varyings.
// Input color and explicit curve type workarounds aren't implemented yet for tessellation.
!(attribs & (PatchAttribs::kColor | PatchAttribs::kExplicitCurveType))) {
return GrPathTessellationShader::MakeHardwareTessellationShader(arena, viewMatrix, color,
attribs);
} else {
return GrPathTessellationShader::MakeMiddleOutFixedCountShader(*caps.shaderCaps(), arena,
viewMatrix, color,
attribs);
}
}
GrPathTessellationShader* GrPathTessellationShader::MakeSimpleTriangleShader(
SkArenaAlloc* arena, const SkMatrix& viewMatrix, const SkPMColor4f& color) {
return arena->make<SimpleTriangleShader>(viewMatrix, color);
@ -92,6 +115,12 @@ float2 eval_rational_cubic(float4x3 P, float T) {
void GrPathTessellationShader::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
const auto& shader = args.fGeomProc.cast<GrPathTessellationShader>();
// We should use explicit curve type when, and only when, there isn't infinity support.
// Otherwise the GPU can infer curve type based on infinity.
SkASSERT(args.fShaderCaps->infinitySupport() !=
(shader.fAttribs & PatchAttribs::kExplicitCurveType));
args.fVaryingHandler->emitAttributes(shader);
// Vertex shader.

View File

@ -19,15 +19,15 @@ public:
const SkMatrix& viewMatrix,
const SkPMColor4f&);
// How many triangles are in a curve with 2^resolveLevel line segments?
constexpr static int NumCurveTrianglesAtResolveLevel(int resolveLevel) {
// resolveLevel=0 -> 0 line segments -> 0 triangles
// resolveLevel=1 -> 2 line segments -> 1 triangle
// resolveLevel=2 -> 4 line segments -> 3 triangles
// resolveLevel=3 -> 8 line segments -> 7 triangles
// ...
return (1 << resolveLevel) - 1;
}
// Creates either a hardware tessellation or middle-out instanced shader, depending on support
// and which is expected to perform better.
static GrPathTessellationShader* Make(SkArenaAlloc*,
const SkMatrix& viewMatrix,
const SkPMColor4f&,
int totalCombinedPathVerbCnt,
const GrPipeline&,
skgpu::PatchAttribs,
const GrCaps&);
// Uses instanced draws to triangulate curves with a "middle-out" topology. Middle-out draws a
// triangle with vertices at T=[0, 1/2, 1] and then recurses breadth first:
@ -50,39 +50,6 @@ public:
const SkPMColor4f&,
skgpu::PatchAttribs);
// This is the largest number of segments the middle-out shader will accept in a single
// instance. If a curve requires more segments, it needs to be chopped.
constexpr static int kMaxFixedCountSegments = 32;
constexpr static int kMaxFixedCountResolveLevel = 5; // log2(kMaxFixedCountSegments)
static_assert(kMaxFixedCountSegments == 1 << kMaxFixedCountResolveLevel);
// These functions define the vertex and index buffers that should be bound when drawing with
// the middle-out fixed count shader. The data sequence is identical for any length of
// tessellation segments, so the caller can use them with any instance length (up to
// kMaxFixedCountResolveLevel).
constexpr static int SizeOfVertexBufferForMiddleOutCurves() {
constexpr int kMaxVertexCount = (1 << kMaxFixedCountResolveLevel) + 1;
return kMaxVertexCount * kMiddleOutVertexStride;
}
static void InitializeVertexBufferForMiddleOutCurves(skgpu::VertexWriter, size_t bufferSize);
constexpr static size_t SizeOfIndexBufferForMiddleOutCurves() {
constexpr int kMaxTriangleCount =
NumCurveTrianglesAtResolveLevel(kMaxFixedCountResolveLevel);
return kMaxTriangleCount * 3 * sizeof(uint16_t);
}
static void InitializeIndexBufferForMiddleOutCurves(skgpu::VertexWriter, size_t bufferSize);
constexpr static int SizeOfVertexBufferForMiddleOutWedges() {
return SizeOfVertexBufferForMiddleOutCurves() + kMiddleOutVertexStride;
}
static void InitializeVertexBufferForMiddleOutWedges(skgpu::VertexWriter, size_t bufferSize);
constexpr static size_t SizeOfIndexBufferForMiddleOutWedges() {
return SizeOfIndexBufferForMiddleOutCurves() + 3 * sizeof(uint16_t);
}
static void InitializeIndexBufferForMiddleOutWedges(skgpu::VertexWriter, size_t bufferSize);
// Uses GPU tessellation shaders to linearize, triangulate, and render curves.
//
// If PatchAttribs::kFanPoint is set, an additional triangle is added, connecting the base of
@ -153,6 +120,8 @@ public:
const GrAppliedHardClip&,
GrPipeline::InputFlags = GrPipeline::InputFlags::kNone);
virtual int maxTessellationSegments(const GrShaderCaps&) const = 0;
protected:
constexpr static size_t kMiddleOutVertexStride = 2 * sizeof(float);

View File

@ -40,6 +40,11 @@ public:
constexpr static Attribute kInputPointAttrib{"inputPoint", kFloat2_GrVertexAttribType,
kFloat2_GrSLType};
this->setVertexAttributes(&kInputPointAttrib, 1);
SkASSERT(this->vertexStride() * 5 == skgpu::PatchStride(fAttribs));
}
int maxTessellationSegments(const GrShaderCaps& shaderCaps) const override {
return shaderCaps.maxTessellationSegments();
}
private:
@ -175,6 +180,14 @@ public:
constexpr static Attribute kInputPointAttrib{"inputPoint", kFloat2_GrVertexAttribType,
kFloat2_GrSLType};
this->setVertexAttributes(&kInputPointAttrib, 1);
SkASSERT(this->vertexStride() * 4 == skgpu::PatchStride(fAttribs));
}
int maxTessellationSegments(const GrShaderCaps& shaderCaps) const override {
// This shader tessellates T=0..(1/2) on the first side of the canonical triangle and
// T=(1/2)..1 on the second side. This means we get double the max tessellation segments for
// the range T=0..1.
return shaderCaps.maxTessellationSegments() * 2;
}
private:

View File

@ -9,6 +9,7 @@
#include "src/core/SkMathPriv.h"
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
#include "src/gpu/tessellate/PathTessellator.h"
#include "src/gpu/tessellate/Tessellation.h"
#include "src/gpu/tessellate/WangsFormula.h"
@ -57,12 +58,17 @@ public:
}
this->setInstanceAttributes(fInstanceAttribs.data(), fInstanceAttribs.count());
SkASSERT(fInstanceAttribs.count() <= kMaxInstanceAttribCount);
SkASSERT(this->instanceStride() == skgpu::PatchStride(fAttribs));
constexpr static Attribute kVertexAttrib("resolveLevel_and_idx", kFloat2_GrVertexAttribType,
kFloat2_GrSLType);
this->setVertexAttributes(&kVertexAttrib, 1);
}
int maxTessellationSegments(const GrShaderCaps&) const override {
return 1 << skgpu::PathTessellator::kMaxFixedResolveLevel;
}
private:
const char* name() const final { return "tessellate_MiddleOutShader"; }
void addToKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final {
@ -88,8 +94,10 @@ std::unique_ptr<GrGeometryProcessor::ProgramImpl> MiddleOutShader::makeProgramIm
GrGPArgs* gpArgs) override {
const MiddleOutShader& middleOutShader = shader.cast<MiddleOutShader>();
v->defineConstant("PRECISION", skgpu::kTessellationPrecision);
v->defineConstant("MAX_FIXED_RESOLVE_LEVEL", (float)kMaxFixedCountResolveLevel);
v->defineConstant("MAX_FIXED_SEGMENTS", (float)kMaxFixedCountSegments);
v->defineConstant("MAX_FIXED_RESOLVE_LEVEL",
(float)skgpu::PathTessellator::kMaxFixedResolveLevel);
v->defineConstant("MAX_FIXED_SEGMENTS",
(float)(1 << skgpu::PathTessellator::kMaxFixedResolveLevel));
v->insertFunction(skgpu::wangs_formula::as_sksl().c_str());
if (middleOutShader.fAttribs & PatchAttribs::kExplicitCurveType) {
v->insertFunction(SkStringPrintf(R"(
@ -206,107 +214,3 @@ GrPathTessellationShader* GrPathTessellationShader::MakeMiddleOutFixedCountShade
const SkPMColor4f& color, PatchAttribs attribs) {
return arena->make<MiddleOutShader>(shaderCaps, viewMatrix, color, attribs);
}
void GrPathTessellationShader::InitializeVertexBufferForMiddleOutCurves(VertexWriter vertexWriter,
size_t bufferSize) {
SkASSERT(bufferSize >= kMiddleOutVertexStride * 2);
SkASSERT(bufferSize % kMiddleOutVertexStride == 0);
int vertexCount = bufferSize / kMiddleOutVertexStride;
SkASSERT(vertexCount > 3);
SkDEBUGCODE(VertexWriter end = vertexWriter.makeOffset(vertexCount * kMiddleOutVertexStride);)
// Lay out the vertices in "middle-out" order:
//
// T= 0/1, 1/1, ; resolveLevel=0
// 1/2, ; resolveLevel=1 (0/2 and 2/2 are already in resolveLevel 0)
// 1/4, 3/4, ; resolveLevel=2 (2/4 is already in resolveLevel 1)
// 1/8, 3/8, 5/8, 7/8, ; resolveLevel=3 (2/8 and 6/8 are already in resolveLevel 2)
// ... ; resolveLevel=...
//
// Resolve level 0 is just the beginning and ending vertices.
vertexWriter << (float)0/*resolveLevel*/ << (float)0/*idx*/;
vertexWriter << (float)0/*resolveLevel*/ << (float)1/*idx*/;
// Resolve levels 1..kMaxResolveLevel.
int maxResolveLevel = SkPrevLog2(vertexCount - 1);
SkASSERT((1 << maxResolveLevel) + 1 == vertexCount);
for (int resolveLevel = 1; resolveLevel <= maxResolveLevel; ++resolveLevel) {
int numSegmentsInResolveLevel = 1 << resolveLevel;
// Write out the odd vertices in this resolveLevel. The even vertices were already written
// out in previous resolveLevels and will be indexed from there.
for (int i = 1; i < numSegmentsInResolveLevel; i += 2) {
vertexWriter << (float)resolveLevel << (float)i;
}
}
SkASSERT(vertexWriter == end);
}
void GrPathTessellationShader::InitializeVertexBufferForMiddleOutWedges(VertexWriter vertexWriter,
size_t bufferSize) {
SkASSERT(bufferSize >= kMiddleOutVertexStride);
// Start out with the fan point. A negative resolve level indicates the fan point.
vertexWriter << (float)-1/*resolveLevel*/ << (float)-1/*idx*/;
InitializeVertexBufferForMiddleOutCurves(std::move(vertexWriter),
bufferSize - kMiddleOutVertexStride);
}
static void fill_index_buffer_for_curves(VertexWriter vertexWriter,
size_t bufferSize,
uint16_t baseIndex) {
SkASSERT(bufferSize % (sizeof(uint16_t) * 3) == 0);
int triangleCount = bufferSize / (sizeof(uint16_t) * 3);
SkASSERT(triangleCount >= 1);
SkTArray<std::array<uint16_t, 3>> indexData(triangleCount);
// Connect the vertices with a middle-out triangulation. Refer to
// InitializeVertexBufferForMiddleOutCurves() for the exact vertex ordering.
//
// Resolve level 1 is just a single triangle at T=[0, 1/2, 1].
const auto* neighborInLastResolveLevel = &indexData.push_back({baseIndex,
(uint16_t)(baseIndex + 2),
(uint16_t)(baseIndex + 1)});
// Resolve levels 2..maxResolveLevel
int maxResolveLevel = SkPrevLog2(triangleCount + 1);
uint16_t nextIndex = baseIndex + 3;
SkASSERT(GrPathTessellationShader::NumCurveTrianglesAtResolveLevel(maxResolveLevel) ==
triangleCount);
for (int resolveLevel = 2; resolveLevel <= maxResolveLevel; ++resolveLevel) {
SkDEBUGCODE(auto* firstTriangleInCurrentResolveLevel = indexData.end());
int numOuterTrianglelsInResolveLevel = 1 << (resolveLevel - 1);
SkASSERT(numOuterTrianglelsInResolveLevel % 2 == 0);
int numTrianglePairsInResolveLevel = numOuterTrianglelsInResolveLevel >> 1;
for (int i = 0; i < numTrianglePairsInResolveLevel; ++i) {
// First triangle shares the left edge of "neighborInLastResolveLevel".
indexData.push_back({(*neighborInLastResolveLevel)[0],
nextIndex++,
(*neighborInLastResolveLevel)[1]});
// Second triangle shares the right edge of "neighborInLastResolveLevel".
indexData.push_back({(*neighborInLastResolveLevel)[1],
nextIndex++,
(*neighborInLastResolveLevel)[2]});
++neighborInLastResolveLevel;
}
SkASSERT(neighborInLastResolveLevel == firstTriangleInCurrentResolveLevel);
}
SkASSERT(indexData.count() == triangleCount);
SkASSERT(nextIndex == baseIndex + triangleCount + 2);
vertexWriter.writeArray(indexData.data(), indexData.count());
}
void GrPathTessellationShader::InitializeIndexBufferForMiddleOutCurves(VertexWriter vertexWriter,
size_t bufferSize) {
fill_index_buffer_for_curves(std::move(vertexWriter), bufferSize, 0);
}
void GrPathTessellationShader::InitializeIndexBufferForMiddleOutWedges(VertexWriter vertexWriter,
size_t bufferSize) {
SkASSERT(bufferSize >= sizeof(uint16_t) * 3);
// Start out with the fan triangle.
vertexWriter << (uint16_t)0 << (uint16_t)1 << (uint16_t)2;
fill_index_buffer_for_curves(std::move(vertexWriter), bufferSize - sizeof(uint16_t) * 3, 1);
}