Revert "Delete HW tessellation shaders for curve/wedge tessellation"

This reverts commit 038818ba8e.

Reason for revert: breaking g3 roll, need to update public bzl file I think

Original change's description:
> Delete HW tessellation shaders for curve/wedge tessellation
>
> GrPathTessellationShader_MiddleOut.cpp is just moved into
> GrPathTessellationShader.cpp, and a few factories are cleaned up since
> we don't have to differentiate between middle-out or hardware.
>
> Bug: skia:13263
> Change-Id: I420faa614a89ef1a2c0f1075d1f8a067d15e9a81
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/534200
> Reviewed-by: Greg Daniel <egdaniel@google.com>
> Commit-Queue: Michael Ludwig <michaelludwig@google.com>

Bug: skia:13263
Change-Id: I42367a21c2bf1a4283e5d9b8d0e00961f9dea2e7
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/536039
Auto-Submit: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
This commit is contained in:
Michael Ludwig 2022-05-02 17:00:04 +00:00 committed by SkCQ
parent 644fc2e540
commit 7d9f07c108
14 changed files with 821 additions and 274 deletions

View File

@ -139,9 +139,11 @@ DEF_PATH_TESS_BENCH(GrPathCurveTessellator, make_cubic_path(8), SkMatrix::I()) {
auto tess = PathCurveTessellator::Make(&arena,
fTarget->caps().shaderCaps()->infinitySupport());
tess->prepare(fTarget.get(),
kMaxParametricSegments,
fMatrix,
{gAlmostIdentity, fPath, SK_PMColor4fTRANSPARENT},
fPath.countVerbs());
fPath.countVerbs(),
true);
}
DEF_PATH_TESS_BENCH(GrPathWedgeTessellator, make_cubic_path(8), SkMatrix::I()) {
@ -151,9 +153,11 @@ DEF_PATH_TESS_BENCH(GrPathWedgeTessellator, make_cubic_path(8), SkMatrix::I()) {
auto tess = PathWedgeTessellator::Make(&arena,
fTarget->caps().shaderCaps()->infinitySupport());
tess->prepare(fTarget.get(),
kMaxParametricSegments,
fMatrix,
{gAlmostIdentity, fPath, SK_PMColor4fTRANSPARENT},
fPath.countVerbs());
fPath.countVerbs(),
true);
}
static void benchmark_wangs_formula_cubic_log2(const SkMatrix& matrix, const SkPath& path) {

View File

@ -316,6 +316,8 @@ skia_gpu_sources = [
# tessellate/shaders
"$_src/gpu/ganesh/tessellate/shaders/GrPathTessellationShader.cpp",
"$_src/gpu/ganesh/tessellate/shaders/GrPathTessellationShader.h",
"$_src/gpu/ganesh/tessellate/shaders/GrPathTessellationShader_Hardware.cpp",
"$_src/gpu/ganesh/tessellate/shaders/GrPathTessellationShader_MiddleOut.cpp",
"$_src/gpu/ganesh/tessellate/shaders/GrStrokeTessellationShader.cpp",
"$_src/gpu/ganesh/tessellate/shaders/GrStrokeTessellationShader.h",
"$_src/gpu/ganesh/tessellate/shaders/GrStrokeTessellationShader_HardwareImpl.cpp",

View File

@ -29,7 +29,9 @@ namespace {
enum class Mode {
kWedgeMiddleOut,
kCurveMiddleOut
kCurveMiddleOut,
kWedgeTessellate,
kCurveTessellate
};
static const char* ModeName(Mode mode) {
@ -38,6 +40,10 @@ static const char* ModeName(Mode mode) {
return "MiddleOutShader (kWedges)";
case Mode::kCurveMiddleOut:
return "MiddleOutShader (kCurves)";
case Mode::kWedgeTessellate:
return "HardwareWedgeShader";
case Mode::kCurveTessellate:
return "HardwareCurveShader";
}
SkUNREACHABLE;
}
@ -76,9 +82,33 @@ private:
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 = (fMode == Mode::kWedgeMiddleOut || fMode == Mode::kCurveMiddleOut) ?
numVerbsToGetMiddleOut : numVerbsToGetTessellation;
auto* tessShader = GrPathTessellationShader::Make(alloc,
shaderMatrix,
kCyan,
numVerbs,
*pipeline,
fTessellator->patchAttribs(),
caps);
fProgram = GrTessellationShader::MakeProgram({alloc, flushState->writeView(),
flushState->usesMSAASurface(),
&flushState->dstProxyView(),
flushState->renderPassBarriers(),
GrLoadOp::kClear, &flushState->caps()},
tessShader,
pipeline,
&GrUserStencilSettings::kUnused);
int maxSegments = tessShader->maxTessellationSegments(*caps.shaderCaps());
PathTessellator::PathDrawList pathList{pathMatrix, fPath, kCyan};
if (fMode == Mode::kCurveMiddleOut) {
if (fMode == Mode::kCurveTessellate || fMode == Mode::kCurveMiddleOut) {
// This emulates what PathStencilCoverOp does when using curves, except we include the
// middle-out triangles directly in the written patches for convenience (normally they
// use a simple triangle pipeline). But PathCurveTessellator only knows how to read
@ -96,35 +126,20 @@ private:
}
auto* tess = PathCurveTessellator::Make(alloc, shaderCaps.infinitySupport());
tess->prepareWithTriangles(flushState, shaderMatrix, &triangles, pathList,
fPath.countVerbs());
tess->prepareWithTriangles(flushState, maxSegments, shaderMatrix, &triangles, pathList,
fPath.countVerbs(),tessShader->willUseTessellationShaders());
fTessellator = tess;
} else {
// This emulates what PathStencilCoverOp does when using wedges.
fTessellator = PathWedgeTessellator::Make(alloc, shaderCaps.infinitySupport());
fTessellator->prepare(flushState, shaderMatrix, pathList, fPath.countVerbs());
fTessellator->prepare(flushState, maxSegments, shaderMatrix, pathList,
fPath.countVerbs(), tessShader->willUseTessellationShaders());
}
auto pipeline = GrSimpleMeshDrawOpHelper::CreatePipeline(flushState, std::move(fProcessors),
fPipelineFlags);
auto* tessShader = GrPathTessellationShader::Make(*caps.shaderCaps(),
alloc,
shaderMatrix,
kCyan,
fTessellator->patchAttribs());
fProgram = GrTessellationShader::MakeProgram({alloc, flushState->writeView(),
flushState->usesMSAASurface(),
&flushState->dstProxyView(),
flushState->renderPassBarriers(),
GrLoadOp::kClear, &flushState->caps()},
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;
@ -191,6 +206,9 @@ void SamplePathTessellators::onDrawContent(SkCanvas* canvas) {
error = "GPU Only.";
} else if (!skgpu::v1::TessellationPathRenderer::IsSupported(*ctx->priv().caps())) {
error = "TessellationPathRenderer not supported.";
} else if (fMode >= Mode::kWedgeTessellate &&
!ctx->priv().caps()->shaderCaps()->tessellationSupport()) {
error.printf("%s requires hardware tessellation support.", ModeName(fMode));
}
if (!error.isEmpty()) {
canvas->clear(SK_ColorRED);
@ -311,6 +329,8 @@ bool SamplePathTessellators::onChar(SkUnichar unichar) {
return true;
case '1':
case '2':
case '3':
case '4':
fMode = (Mode)(unichar - '1');
return true;
}

View File

@ -185,6 +185,8 @@ cc_library(
"//src/gpu/ganesh/ops:TessellationPathRenderer_src",
"//src/gpu/ganesh/ops:TextureOp_src",
"//src/gpu/ganesh/ops:TriangulatingPathRenderer_src",
"//src/gpu/ganesh/tessellate/shaders:GrPathTessellationShader_Hardware_src",
"//src/gpu/ganesh/tessellate/shaders:GrPathTessellationShader_MiddleOut_src",
"//src/gpu/ganesh/tessellate/shaders:GrPathTessellationShader_src",
"//src/gpu/ganesh/tessellate/shaders:GrStrokeTessellationShader_HardwareImpl_src",
"//src/gpu/ganesh/tessellate/shaders:GrStrokeTessellationShader_InstancedImpl_src",

View File

@ -47,6 +47,8 @@ public:
}
}
int maxTessellationSegments(const GrShaderCaps&) const override { SkUNREACHABLE; }
private:
const char* name() const final { return "tessellate_HullShader"; }
void addToKey(const GrShaderCaps&, KeyBuilder*) const final {}
@ -251,11 +253,13 @@ void PathInnerTriangulateOp::prePreparePrograms(const GrTessellationShader::Prog
if (!isLinear) {
fTessellator = PathCurveTessellator::Make(args.fArena,
args.fCaps->shaderCaps()->infinitySupport());
auto* tessShader = GrPathTessellationShader::Make(*args.fCaps->shaderCaps(),
args.fArena,
auto* tessShader = GrPathTessellationShader::Make(args.fArena,
fViewMatrix,
SK_PMColor4fTRANSPARENT,
fTessellator->patchAttribs());
fPath.countVerbs(),
*pipelineForStencils,
fTessellator->patchAttribs(),
*args.fCaps);
const GrUserStencilSettings* stencilPathSettings =
GrPathTessellationShader::StencilPathSettings(GrFillRuleForSkPath(fPath));
fStencilCurvesProgram = GrTessellationShader::MakeProgram(args,
@ -416,11 +420,15 @@ void PathInnerTriangulateOp::onPrepare(GrOpFlushState* flushState) {
if (fTessellator) {
auto tessShader = &fStencilCurvesProgram->geomProc().cast<GrPathTessellationShader>();
int maxSegments = tessShader->maxTessellationSegments(*caps.shaderCaps());
fTessellator->prepareWithTriangles(flushState,
maxSegments,
tessShader->viewMatrix(),
&fFanBreadcrumbs,
{SkMatrix::I(), fPath, SK_PMColor4fTRANSPARENT},
fPath.countVerbs());
fPath.countVerbs(),
tessShader->willUseTessellationShaders());
}
if (!caps.shaderCaps()->vertexIDSupport()) {
@ -444,7 +452,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

@ -163,11 +163,13 @@ void PathStencilCoverOp::prePreparePrograms(const GrTessellationShader::ProgramA
fTessellator = PathWedgeTessellator::Make(args.fArena,
args.fCaps->shaderCaps()->infinitySupport());
}
auto* tessShader = GrPathTessellationShader::Make(*args.fCaps->shaderCaps(),
args.fArena,
auto* tessShader = GrPathTessellationShader::Make(args.fArena,
shaderMatrix,
SK_PMColor4fTRANSPARENT,
fTessellator->patchAttribs());
fTotalCombinedPathVerbCnt,
*stencilPipeline,
fTessellator->patchAttribs(),
*args.fCaps);
fStencilPathProgram = GrTessellationShader::MakeProgram(args,
tessShader,
stencilPipeline,
@ -261,9 +263,11 @@ void PathStencilCoverOp::onPrepare(GrOpFlushState* flushState) {
auto tessShader = &fStencilPathProgram->geomProc().cast<GrPathTessellationShader>();
fTessellator->prepare(flushState,
tessShader->maxTessellationSegments(*flushState->caps().shaderCaps()),
tessShader->viewMatrix(),
*fPathDrawList,
fTotalCombinedPathVerbCnt);
fTotalCombinedPathVerbCnt,
tessShader->willUseTessellationShaders());
if (fCoverBBoxProgram) {
size_t instanceStride = fCoverBBoxProgram->geomProc().instanceStride();
@ -334,7 +338,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

@ -75,11 +75,13 @@ void PathTessellateOp::prepareTessellator(const GrTessellationShader::ProgramArg
fTessellator = PathWedgeTessellator::Make(args.fArena,
args.fCaps->shaderCaps()->infinitySupport(),
fPatchAttribs);
auto* tessShader = GrPathTessellationShader::Make(*args.fCaps->shaderCaps(),
args.fArena,
auto* tessShader = GrPathTessellationShader::Make(args.fArena,
fShaderMatrix,
this->headDraw().fColor,
fTessellator->patchAttribs());
fTotalCombinedPathVerbCnt,
*pipeline,
fTessellator->patchAttribs(),
*args.fCaps);
fTessellationProgram = GrTessellationShader::MakeProgram(args, tessShader, pipeline, fStencil);
}
@ -106,10 +108,13 @@ void PathTessellateOp::onPrepare(GrOpFlushState* flushState) {
&flushState->caps()}, flushState->detachAppliedClip());
SkASSERT(fTessellator);
}
auto tessShader = &fTessellationProgram->geomProc().cast<GrPathTessellationShader>();
fTessellator->prepare(flushState,
tessShader->maxTessellationSegments(*flushState->caps().shaderCaps()),
fShaderMatrix,
*fPathDrawList,
fTotalCombinedPathVerbCnt);
fTotalCombinedPathVerbCnt,
tessShader->willUseTessellationShaders());
}
void PathTessellateOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
@ -118,7 +123,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

@ -156,14 +156,16 @@ SKGPU_DECLARE_STATIC_UNIQUE_KEY(gFixedCountCurveIndexBufferKey);
void PathCurveTessellator::prepareWithTriangles(
GrMeshDrawTarget* target,
int maxTessellationSegments,
const SkMatrix& shaderMatrix,
GrInnerFanTriangulator::BreadcrumbTriangleList* extraTriangles,
const PathDrawList& pathDrawList,
int totalCombinedPathVerbCnt) {
int totalCombinedPathVerbCnt,
bool willUseTessellationShaders) {
int patchPreallocCount = FixedCountCurves::PreallocCount(totalCombinedPathVerbCnt) +
(extraTriangles ? extraTriangles->count() : 0);
if (patchPreallocCount) {
CurveWriter writer{fAttribs, skgpu::kMaxParametricSegments,
CurveWriter writer{fAttribs, maxTessellationSegments,
target, &fVertexChunkArray, patchPreallocCount};
// Write out extra space-filling triangles to connect the curve patches with any external
@ -191,7 +193,12 @@ void PathCurveTessellator::prepareWithTriangles(
int resolveLevel = write_curve_patches(std::move(writer), shaderMatrix, pathDrawList);
this->updateResolveLevel(resolveLevel);
}
if (!willUseTessellationShaders) {
this->prepareFixedCountBuffers(target);
}
}
void PathCurveTessellator::prepareFixedCountBuffers(GrMeshDrawTarget* target) {
GrResourceProvider* rp = target->resourceProvider();
SKGPU_DEFINE_STATIC_UNIQUE_KEY(gFixedCountCurveVertexBufferKey);
@ -209,7 +216,14 @@ void PathCurveTessellator::prepareWithTriangles(
FixedCountCurves::WriteIndexBuffer);
}
void PathCurveTessellator::draw(GrOpFlushState* flushState) const {
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;
}
@ -232,17 +246,7 @@ void PathCurveTessellator::drawHullInstances(GrOpFlushState* flushState,
SKGPU_DECLARE_STATIC_UNIQUE_KEY(gFixedCountWedgesVertexBufferKey);
SKGPU_DECLARE_STATIC_UNIQUE_KEY(gFixedCountWedgesIndexBufferKey);
void PathWedgeTessellator::prepare(GrMeshDrawTarget* target,
const SkMatrix& shaderMatrix,
const PathDrawList& pathDrawList,
int totalCombinedPathVerbCnt) {
if (int patchPreallocCount = FixedCountWedges::PreallocCount(totalCombinedPathVerbCnt)) {
WedgeWriter writer{fAttribs, skgpu::kMaxParametricSegments,
target, &fVertexChunkArray, patchPreallocCount};
int resolveLevel = write_wedge_patches(std::move(writer), shaderMatrix, pathDrawList);
this->updateResolveLevel(resolveLevel);
}
void PathWedgeTessellator::prepareFixedCountBuffers(GrMeshDrawTarget* target) {
GrResourceProvider* rp = target->resourceProvider();
SKGPU_DEFINE_STATIC_UNIQUE_KEY(gFixedCountWedgesVertexBufferKey);
@ -260,7 +264,31 @@ void PathWedgeTessellator::prepare(GrMeshDrawTarget* target,
FixedCountWedges::WriteIndexBuffer);
}
void PathWedgeTessellator::draw(GrOpFlushState* flushState) const {
void PathWedgeTessellator::prepare(GrMeshDrawTarget* target,
int maxTessellationSegments,
const SkMatrix& shaderMatrix,
const PathDrawList& pathDrawList,
int totalCombinedPathVerbCnt,
bool willUseTessellationShaders) {
if (int patchPreallocCount = FixedCountWedges::PreallocCount(totalCombinedPathVerbCnt)) {
WedgeWriter writer{fAttribs, maxTessellationSegments,
target, &fVertexChunkArray, patchPreallocCount};
int resolveLevel = write_wedge_patches(std::move(writer), shaderMatrix, pathDrawList);
this->updateResolveLevel(resolveLevel);
}
if (!willUseTessellationShaders) {
this->prepareFixedCountBuffers(target);
}
}
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;
}

View File

@ -54,15 +54,32 @@ public:
PatchAttribs patchAttribs() const { return fAttribs; }
// Initializes the internal vertex and index buffers required for drawFixedCount().
virtual void prepareFixedCountBuffers(GrMeshDrawTarget*) = 0;
// Called before draw(). Prepares GPU buffers containing the geometry to tessellate.
virtual void prepare(GrMeshDrawTarget* target,
int maxTessellationSegments,
const SkMatrix& shaderMatrix,
const PathDrawList& pathDrawList,
int totalCombinedPathVerbCnt) = 0;
int totalCombinedPathVerbCnt,
bool willUseTessellationShaders) = 0;
// 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 draw(GrOpFlushState* flushState) const = 0;
virtual void drawFixedCount(GrOpFlushState*) const = 0;
void draw(GrOpFlushState* flushState, bool willUseTessellationShaders) {
if (willUseTessellationShaders) {
this->drawTessellated(flushState);
} else {
this->drawFixedCount(flushState);
}
}
protected:
PathTessellator(bool infinitySupport, PatchAttribs attribs) : fAttribs(attribs) {
@ -85,6 +102,7 @@ protected:
GrVertexChunkArray fVertexChunkArray;
// If using fixed-count rendering, these are the vertex and index buffers.
sk_sp<const GrGpuBuffer> fFixedVertexBuffer;
sk_sp<const GrGpuBuffer> fFixedIndexBuffer;
};
@ -105,23 +123,32 @@ public:
: PathTessellator(infinitySupport, attribs) {}
void prepareWithTriangles(GrMeshDrawTarget* target,
int maxTessellationSegments,
const SkMatrix& shaderMatrix,
GrInnerFanTriangulator::BreadcrumbTriangleList* extraTriangles,
const PathDrawList& pathDrawList,
int totalCombinedPathVerbCnt);
int totalCombinedPathVerbCnt,
bool willUseTessellationShaders);
void prepare(GrMeshDrawTarget* target,
int maxTessellationSegments,
const SkMatrix& shaderMatrix,
const PathDrawList& pathDrawList,
int totalCombinedPathVerbCnt) final {
int totalCombinedPathVerbCnt,
bool willUseTessellationShaders) final {
this->prepareWithTriangles(target,
maxTessellationSegments,
shaderMatrix,
nullptr, // no extra triangles by default
pathDrawList,
totalCombinedPathVerbCnt);
totalCombinedPathVerbCnt,
willUseTessellationShaders);
}
void draw(GrOpFlushState*) const final;
void prepareFixedCountBuffers(GrMeshDrawTarget*) 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
@ -147,11 +174,16 @@ public:
}
void prepare(GrMeshDrawTarget* target,
int maxTessellationSegments,
const SkMatrix& shaderMatrix,
const PathDrawList& pathDrawList,
int totalCombinedPathVerbCnt) final;
int totalCombinedPathVerbCnt,
bool willUseTessellationShaders) final;
void draw(GrOpFlushState*) const final;
void prepareFixedCountBuffers(GrMeshDrawTarget*) final;
void drawTessellated(GrOpFlushState*) const final;
void drawFixedCount(GrOpFlushState*) const final;
};
} // namespace skgpu::v1

View File

@ -4,6 +4,33 @@ licenses(["notice"])
exports_files_legacy()
generated_cc_atom(
name = "GrPathTessellationShader_Hardware_src",
srcs = ["GrPathTessellationShader_Hardware.cpp"],
visibility = ["//:__subpackages__"],
deps = [
":GrPathTessellationShader_hdr",
"//src/gpu/ganesh/glsl:GrGLSLVertexGeoBuilder_hdr",
"//src/gpu/tessellate:Tessellation_hdr",
"//src/gpu/tessellate:WangsFormula_hdr",
],
)
generated_cc_atom(
name = "GrPathTessellationShader_MiddleOut_src",
srcs = ["GrPathTessellationShader_MiddleOut.cpp"],
visibility = ["//:__subpackages__"],
deps = [
":GrPathTessellationShader_hdr",
"//src/core:SkMathPriv_hdr",
"//src/gpu:KeyBuilder_hdr",
"//src/gpu/ganesh/glsl:GrGLSLVertexGeoBuilder_hdr",
"//src/gpu/tessellate:FixedCountBufferUtils_hdr",
"//src/gpu/tessellate:Tessellation_hdr",
"//src/gpu/tessellate:WangsFormula_hdr",
],
)
generated_cc_atom(
name = "GrPathTessellationShader_hdr",
hdrs = ["GrPathTessellationShader.h"],
@ -20,15 +47,10 @@ generated_cc_atom(
visibility = ["//:__subpackages__"],
deps = [
":GrPathTessellationShader_hdr",
"//src/core:SkMathPriv_hdr",
"//src/gpu:KeyBuilder_hdr",
"//src/gpu/ganesh/effects:GrDisableColorXP_hdr",
"//src/gpu/ganesh/glsl:GrGLSLFragmentShaderBuilder_hdr",
"//src/gpu/ganesh/glsl:GrGLSLVarying_hdr",
"//src/gpu/ganesh/glsl:GrGLSLVertexGeoBuilder_hdr",
"//src/gpu/tessellate:FixedCountBufferUtils_hdr",
"//src/gpu/tessellate:Tessellation_hdr",
"//src/gpu/tessellate:WangsFormula_hdr",
],
)

View File

@ -7,15 +7,10 @@
#include "src/gpu/ganesh/tessellate/shaders/GrPathTessellationShader.h"
#include "src/core/SkMathPriv.h"
#include "src/gpu/KeyBuilder.h"
#include "src/gpu/ganesh/effects/GrDisableColorXP.h"
#include "src/gpu/ganesh/glsl/GrGLSLFragmentShaderBuilder.h"
#include "src/gpu/ganesh/glsl/GrGLSLVarying.h"
#include "src/gpu/ganesh/glsl/GrGLSLVertexGeoBuilder.h"
#include "src/gpu/tessellate/FixedCountBufferUtils.h"
#include "src/gpu/tessellate/Tessellation.h"
#include "src/gpu/tessellate/WangsFormula.h"
using skgpu::PatchAttribs;
@ -33,6 +28,8 @@ public:
this->setVertexAttributesWithImplicitOffsets(&kInputPointAttrib, 1);
}
int maxTessellationSegments(const GrShaderCaps&) const override { SkUNREACHABLE; }
private:
const char* name() const final { return "tessellate_SimpleTriangleShader"; }
void addToKey(const GrShaderCaps&, skgpu::KeyBuilder*) const final {}
@ -57,207 +54,27 @@ std::unique_ptr<GrGeometryProcessor::ProgramImpl> SimpleTriangleShader::makeProg
return std::make_unique<Impl>();
}
// Uses instanced draws to triangulate standalone closed curves with a "middle-out" topology.
// Middle-out draws a triangle with vertices at T=[0, 1/2, 1] and then recurses breadth first:
//
// depth=0: T=[0, 1/2, 1]
// depth=1: T=[0, 1/4, 2/4], T=[2/4, 3/4, 1]
// depth=2: T=[0, 1/8, 2/8], T=[2/8, 3/8, 4/8], T=[4/8, 5/8, 6/8], T=[6/8, 7/8, 1]
// ...
//
// The shader determines how many segments are required to render each individual curve smoothly,
// and emits empty triangles at any vertices whose sk_VertexIDs are higher than necessary. It is the
// caller's responsibility to draw enough vertices per instance for the most complex curve in the
// batch to render smoothly (i.e., NumTrianglesAtResolveLevel() * 3).
class MiddleOutShader : public GrPathTessellationShader {
public:
MiddleOutShader(const GrShaderCaps& shaderCaps, const SkMatrix& viewMatrix,
const SkPMColor4f& color, PatchAttribs attribs)
: GrPathTessellationShader(kTessellate_MiddleOutShader_ClassID,
GrPrimitiveType::kTriangles, 0, viewMatrix, color, attribs) {
fInstanceAttribs.emplace_back("p01", kFloat4_GrVertexAttribType, SkSLType::kFloat4);
fInstanceAttribs.emplace_back("p23", kFloat4_GrVertexAttribType, SkSLType::kFloat4);
if (fAttribs & PatchAttribs::kFanPoint) {
fInstanceAttribs.emplace_back("fanPointAttrib",
kFloat2_GrVertexAttribType,
SkSLType::kFloat2);
}
if (fAttribs & PatchAttribs::kColor) {
fInstanceAttribs.emplace_back("colorAttrib",
(fAttribs & PatchAttribs::kWideColorIfEnabled)
? kFloat4_GrVertexAttribType
: kUByte4_norm_GrVertexAttribType,
SkSLType::kHalf4);
}
if (fAttribs & PatchAttribs::kExplicitCurveType) {
// A conic curve is written out with p3=[w,Infinity], but GPUs that don't support
// infinity can't detect this. On these platforms we also write out an extra float with
// each patch that explicitly tells the shader what type of curve it is.
fInstanceAttribs.emplace_back("curveType", kFloat_GrVertexAttribType, SkSLType::kFloat);
}
this->setInstanceAttributesWithImplicitOffsets(fInstanceAttribs.data(),
fInstanceAttribs.count());
SkASSERT(fInstanceAttribs.count() <= kMaxInstanceAttribCount);
SkASSERT(this->instanceStride() ==
sizeof(SkPoint) * 4 + skgpu::PatchAttribsStride(fAttribs));
constexpr static Attribute kVertexAttrib("resolveLevel_and_idx", kFloat2_GrVertexAttribType,
SkSLType::kFloat2);
this->setVertexAttributesWithImplicitOffsets(&kVertexAttrib, 1);
}
private:
const char* name() const final { return "tessellate_MiddleOutShader"; }
void addToKey(const GrShaderCaps&, skgpu::KeyBuilder* b) const final {
// When color is in a uniform, it's always wide so we need to ignore kWideColorIfEnabled.
// When color is in an attrib, its wideness is accounted for as part of the attrib key in
// GrGeometryProcessor::getAttributeKey().
// Either way, we get the correct key by ignoring .
b->add32((uint32_t)(fAttribs & ~PatchAttribs::kWideColorIfEnabled));
}
std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const final;
constexpr static int kMaxInstanceAttribCount = 5;
SkSTArray<kMaxInstanceAttribCount, Attribute> fInstanceAttribs;
};
std::unique_ptr<GrGeometryProcessor::ProgramImpl> MiddleOutShader::makeProgramImpl(
const GrShaderCaps&) const {
class Impl : public GrPathTessellationShader::Impl {
void emitVertexCode(const GrShaderCaps& shaderCaps,
const GrPathTessellationShader& shader,
GrGLSLVertexBuilder* v,
GrGLSLVaryingHandler* varyingHandler,
GrGPArgs* gpArgs) override {
const MiddleOutShader& middleOutShader = shader.cast<MiddleOutShader>();
v->defineConstant("PRECISION", skgpu::kTessellationPrecision);
v->defineConstant("MAX_FIXED_RESOLVE_LEVEL",
(float)skgpu::kMaxFixedResolveLevel);
v->defineConstant("MAX_FIXED_SEGMENTS",
(float)(skgpu::kMaxParametricSegments));
v->insertFunction(GrTessellationShader::WangsFormulaSkSL());
if (middleOutShader.fAttribs & PatchAttribs::kExplicitCurveType) {
v->insertFunction(SkStringPrintf(R"(
bool is_conic_curve() {
return curveType != %g;
})", skgpu::kCubicCurveType).c_str());
v->insertFunction(SkStringPrintf(R"(
bool is_triangular_conic_curve() {
return curveType == %g;
})", skgpu::kTriangularConicCurveType).c_str());
} else {
SkASSERT(shaderCaps.infinitySupport());
v->insertFunction(R"(
bool is_conic_curve() { return isinf(p23.w); }
bool is_triangular_conic_curve() { return isinf(p23.z); })");
}
if (shaderCaps.bitManipulationSupport()) {
v->insertFunction(R"(
float ldexp_portable(float x, float p) {
return ldexp(x, int(p));
})");
} else {
v->insertFunction(R"(
float ldexp_portable(float x, float p) {
return x * exp2(p);
})");
}
v->codeAppend(R"(
float resolveLevel = resolveLevel_and_idx.x;
float idxInResolveLevel = resolveLevel_and_idx.y;
float2 localcoord;)");
if (middleOutShader.fAttribs & PatchAttribs::kFanPoint) {
v->codeAppend(R"(
// A negative resolve level means this is the fan point.
if (resolveLevel < 0) {
localcoord = fanPointAttrib;
} else)"); // Fall through to next if ().
}
v->codeAppend(R"(
if (is_triangular_conic_curve()) {
// This patch is an exact triangle.
localcoord = (resolveLevel != 0) ? p01.zw
: (idxInResolveLevel != 0) ? p23.xy
: p01.xy;
} else {
float2 p0=p01.xy, p1=p01.zw, p2=p23.xy, p3=p23.zw;
float w = -1; // w < 0 tells us to treat the instance as an integral cubic.
float maxResolveLevel;
if (is_conic_curve()) {
// Conics are 3 points, with the weight in p3.
w = p3.x;
maxResolveLevel = wangs_formula_conic_log2(PRECISION, AFFINE_MATRIX * p0,
AFFINE_MATRIX * p1,
AFFINE_MATRIX * p2, w);
p1 *= w; // Unproject p1.
p3 = p2; // Duplicate the endpoint for shared code that also runs on cubics.
} else {
// The patch is an integral cubic.
maxResolveLevel = wangs_formula_cubic_log2(PRECISION, p0, p1, p2, p3,
AFFINE_MATRIX);
}
if (resolveLevel > maxResolveLevel) {
// This vertex is at a higher resolve level than we need. Demote to a lower
// resolveLevel, which will produce a degenerate triangle.
idxInResolveLevel = floor(ldexp_portable(idxInResolveLevel,
maxResolveLevel - resolveLevel));
resolveLevel = maxResolveLevel;
}
// Promote our location to a discrete position in the maximum fixed resolve level.
// This is extra paranoia to ensure we get the exact same fp32 coordinates for
// colocated points from different resolve levels (e.g., the vertices T=3/4 and
// T=6/8 should be exactly colocated).
float fixedVertexID = floor(.5 + ldexp_portable(
idxInResolveLevel, MAX_FIXED_RESOLVE_LEVEL - resolveLevel));
if (0 < fixedVertexID && fixedVertexID < MAX_FIXED_SEGMENTS) {
float T = fixedVertexID * (1 / MAX_FIXED_SEGMENTS);
// Evaluate at T. Use De Casteljau's for its accuracy and stability.
float2 ab = mix(p0, p1, T);
float2 bc = mix(p1, p2, T);
float2 cd = mix(p2, p3, T);
float2 abc = mix(ab, bc, T);
float2 bcd = mix(bc, cd, T);
float2 abcd = mix(abc, bcd, T);
// Evaluate the conic weight at T.
float u = mix(1.0, w, T);
float v = w + 1 - u; // == mix(w, 1, T)
float uv = mix(u, v, T);
localcoord = (w < 0) ? /*cubic*/ abcd : /*conic*/ abc/uv;
} else {
localcoord = (fixedVertexID == 0) ? p0.xy : p3.xy;
}
}
float2 vertexpos = AFFINE_MATRIX * localcoord + TRANSLATE;)");
gpArgs->fLocalCoordVar.set(SkSLType::kFloat2, "localcoord");
gpArgs->fPositionVar.set(SkSLType::kFloat2, "vertexpos");
if (middleOutShader.fAttribs & PatchAttribs::kColor) {
GrGLSLVarying colorVarying(SkSLType::kHalf4);
varyingHandler->addVarying("color",
&colorVarying,
GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
v->codeAppendf("%s = colorAttrib;", colorVarying.vsOut());
fVaryingColorName = colorVarying.fsIn();
}
}
};
return std::make_unique<Impl>();
}
} // namespace
GrPathTessellationShader* GrPathTessellationShader::Make(const GrShaderCaps& shaderCaps,
SkArenaAlloc* arena,
GrPathTessellationShader* GrPathTessellationShader::Make(SkArenaAlloc* arena,
const SkMatrix& viewMatrix,
const SkPMColor4f& color,
skgpu::PatchAttribs attribs) {
// 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(shaderCaps.infinitySupport() != (attribs & PatchAttribs::kExplicitCurveType));
return arena->make<MiddleOutShader>(shaderCaps, viewMatrix, color, attribs);
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(

View File

@ -19,6 +19,16 @@ public:
const SkMatrix& viewMatrix,
const SkPMColor4f&);
// 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:
//
@ -34,11 +44,20 @@ public:
//
// If PatchAttribs::kFanPoint is set, an additional triangle is added, connecting the base of
// the curve to the fan point.
static GrPathTessellationShader* Make(const GrShaderCaps&,
SkArenaAlloc*,
const SkMatrix& viewMatrix,
const SkPMColor4f&,
skgpu::PatchAttribs);
static GrPathTessellationShader* MakeMiddleOutFixedCountShader(const GrShaderCaps&,
SkArenaAlloc*,
const SkMatrix& viewMatrix,
const SkPMColor4f&,
skgpu::PatchAttribs);
// Uses GPU tessellation shaders to linearize, triangulate, and render curves.
//
// If PatchAttribs::kFanPoint is set, an additional triangle is added, connecting the base of
// the curve to the fan point.
static GrPathTessellationShader* MakeHardwareTessellationShader(SkArenaAlloc*,
const SkMatrix& viewMatrix,
const SkPMColor4f&,
skgpu::PatchAttribs);
// Returns the stencil settings to use for a standard Redbook "stencil" pass.
static const GrUserStencilSettings* StencilPathSettings(GrFillRule fillRule) {
@ -101,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

@ -0,0 +1,354 @@
/*
* 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/ganesh/tessellate/shaders/GrPathTessellationShader.h"
#include "src/gpu/ganesh/glsl/GrGLSLVertexGeoBuilder.h"
#include "src/gpu/tessellate/Tessellation.h"
#include "src/gpu/tessellate/WangsFormula.h"
using skgpu::PatchAttribs;
namespace {
// Converts keywords from shared SkSL strings to native GLSL keywords.
constexpr static char kSkSLTypeDefs[] = R"(
#define float4x3 mat4x3
#define float4x2 mat4x2
#define float3x2 mat3x2
#define float2x2 mat2
#define float2 vec2
#define float3 vec3
#define float4 vec4
)";
// Uses GPU tessellation shaders to linearize, triangulate, and render cubic "wedge" patches. A
// wedge is a 5-point patch consisting of 4 cubic control points, plus an anchor point fanning from
// the center of the curve's resident contour.
// TODO: Eventually we want to use rational cubic wedges in order to support perspective and conics.
class HardwareWedgeShader : public GrPathTessellationShader {
public:
HardwareWedgeShader(const SkMatrix& viewMatrix,
const SkPMColor4f& color,
PatchAttribs attribs)
: GrPathTessellationShader(kTessellate_HardwareWedgeShader_ClassID,
GrPrimitiveType::kPatches, 5, viewMatrix, color, attribs) {
constexpr static Attribute kInputPointAttrib{"inputPoint", kFloat2_GrVertexAttribType,
SkSLType::kFloat2};
this->setVertexAttributesWithImplicitOffsets(&kInputPointAttrib, 1);
SkASSERT(this->vertexStride() * 5 ==
sizeof(SkPoint) * 4 + skgpu::PatchAttribsStride(fAttribs));
}
int maxTessellationSegments(const GrShaderCaps& shaderCaps) const override {
return shaderCaps.maxTessellationSegments();
}
private:
const char* name() const final { return "tessellate_HardwareWedgeShader"; }
void addToKey(const GrShaderCaps&, skgpu::KeyBuilder*) const final {}
std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const final;
};
std::unique_ptr<GrGeometryProcessor::ProgramImpl> HardwareWedgeShader::makeProgramImpl(
const GrShaderCaps&) const {
class Impl : public GrPathTessellationShader::Impl {
void emitVertexCode(const GrShaderCaps&,
const GrPathTessellationShader&,
GrGLSLVertexBuilder* v,
GrGLSLVaryingHandler*,
GrGPArgs*) override {
v->declareGlobal(GrShaderVar("vsPt", SkSLType::kFloat2, GrShaderVar::TypeModifier::Out));
v->codeAppend(R"(
// If y is infinity then x is a conic weight. Don't transform.
vsPt = (isinf(inputPoint.y)) ? inputPoint : AFFINE_MATRIX * inputPoint + TRANSLATE;)");
}
SkString getTessControlShaderGLSL(const GrGeometryProcessor&,
const char* versionAndExtensionDecls,
const GrGLSLUniformHandler&,
const GrShaderCaps& shaderCaps) const override {
SkString code(versionAndExtensionDecls);
code.appendf(R"(
#define MAX_TESSELLATION_SEGMENTS %i)", shaderCaps.maxTessellationSegments());
code.appendf(R"(
#define PRECISION %f)", skgpu::kTessellationPrecision);
code.append(kSkSLTypeDefs);
code.append(GrTessellationShader::WangsFormulaSkSL());
code.append(R"(
layout(vertices = 1) out;
in vec2 vsPt[];
patch out mat4x2 rationalCubicXY;
patch out float rationalCubicW;
patch out vec2 fanpoint;
void main() {
mat4x2 P = mat4x2(vsPt[0], vsPt[1], vsPt[2], vsPt[3]);
float numSegments;
if (isinf(P[3].y)) {
// This is a conic.
float w = P[3].x;
numSegments = wangs_formula_conic(PRECISION, P[0], P[1], P[2], w);
// Convert to a rational cubic in projected form.
rationalCubicXY = mat4x2(P[0],
mix(vec4(P[0], P[2]), (P[1] * w).xyxy, 2.0/3.0),
P[2]);
rationalCubicW = fma(w, 2.0/3.0, 1.0/3.0);
} else {
// This is a cubic.
numSegments = wangs_formula_cubic(PRECISION, P[0], P[1], P[2], P[3], mat2(1));
rationalCubicXY = P;
rationalCubicW = 1;
}
fanpoint = vsPt[4];
// Tessellate the first side of the patch into numSegments triangles.
gl_TessLevelOuter[0] = min(numSegments, MAX_TESSELLATION_SEGMENTS);
// Leave the other two sides of the patch as single segments.
gl_TessLevelOuter[1] = 1.0;
gl_TessLevelOuter[2] = 1.0;
// Changing the inner level to 1 when numSegments == 1 collapses the entire
// patch to a single triangle. Otherwise, we need an inner level of 2 so our curve
// triangles have an interior point to originate from.
gl_TessLevelInner[0] = min(numSegments, 2.0);
})");
return code;
}
SkString getTessEvaluationShaderGLSL(const GrGeometryProcessor&,
const char* versionAndExtensionDecls,
const GrGLSLUniformHandler&,
const GrShaderCaps&) const override {
SkString code(versionAndExtensionDecls);
code.append(kSkSLTypeDefs);
code.append(kEvalRationalCubicFn);
code.append(R"(
layout(triangles, equal_spacing, ccw) in;
uniform vec4 sk_RTAdjust;
patch in mat4x2 rationalCubicXY;
patch in float rationalCubicW;
patch in vec2 fanpoint;
void main() {
// Locate our parametric point of interest. It is equal to the barycentric
// y-coordinate if we are a vertex on the tessellated edge of the triangle patch,
// 0.5 if we are the patch's interior vertex, or N/A if we are the fan point.
// NOTE: We are on the tessellated edge when the barycentric x-coordinate == 0.
float T = (gl_TessCoord.x == 0.0) ? gl_TessCoord.y : 0.5;
mat4x3 P = mat4x3(rationalCubicXY[0], 1,
rationalCubicXY[1], rationalCubicW,
rationalCubicXY[2], rationalCubicW,
rationalCubicXY[3], 1);
vec2 vertexpos = eval_rational_cubic(P, T);
if (gl_TessCoord.x == 1.0) {
// We are the anchor point that fans from the center of the curve's contour.
vertexpos = fanpoint;
} else if (gl_TessCoord.x != 0.0) {
// We are the interior point of the patch; center it inside [C(0), C(.5), C(1)].
vertexpos = (P[0].xy + vertexpos + P[3].xy) / 3.0;
}
gl_Position = vec4(vertexpos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0);
})");
return code;
}
};
return std::make_unique<Impl>();
}
// Uses GPU tessellation shaders to linearize, triangulate, and render standalone closed cubics.
// TODO: Eventually we want to use rational cubic wedges in order to support perspective and conics.
class HardwareCurveShader : public GrPathTessellationShader {
public:
HardwareCurveShader(const SkMatrix& viewMatrix,
const SkPMColor4f& color,
PatchAttribs attribs)
: GrPathTessellationShader(kTessellate_HardwareCurveShader_ClassID,
GrPrimitiveType::kPatches, 4, viewMatrix, color,
attribs) {
constexpr static Attribute kInputPointAttrib{"inputPoint", kFloat2_GrVertexAttribType,
SkSLType::kFloat2};
this->setVertexAttributesWithImplicitOffsets(&kInputPointAttrib, 1);
SkASSERT(this->vertexStride() * 4 ==
sizeof(SkPoint) * 4 + skgpu::PatchAttribsStride(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:
const char* name() const final { return "tessellate_HardwareCurveShader"; }
void addToKey(const GrShaderCaps&, skgpu::KeyBuilder*) const final {}
std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const final;
};
std::unique_ptr<GrGeometryProcessor::ProgramImpl> HardwareCurveShader::makeProgramImpl(
const GrShaderCaps&) const {
class Impl : public GrPathTessellationShader::Impl {
void emitVertexCode(const GrShaderCaps&,
const GrPathTessellationShader&,
GrGLSLVertexBuilder* v,
GrGLSLVaryingHandler*,
GrGPArgs*) override {
v->declareGlobal(GrShaderVar("P", SkSLType::kFloat2, GrShaderVar::TypeModifier::Out));
v->codeAppend(R"(
// If y is infinity then x is a conic weight. Don't transform.
P = (isinf(inputPoint.y)) ? inputPoint : AFFINE_MATRIX * inputPoint + TRANSLATE;)");
}
SkString getTessControlShaderGLSL(const GrGeometryProcessor&,
const char* versionAndExtensionDecls,
const GrGLSLUniformHandler&,
const GrShaderCaps& shaderCaps) const override {
SkString code(versionAndExtensionDecls);
code.appendf(R"(
#define MAX_TESSELLATION_SEGMENTS %i)", shaderCaps.maxTessellationSegments());
code.appendf(R"(
#define PRECISION %f)", skgpu::kTessellationPrecision);
code.append(kSkSLTypeDefs);
code.append(GrTessellationShader::WangsFormulaSkSL());
code.append(R"(
layout(vertices = 1) out;
in vec2 P[];
patch out mat4x2 rationalCubicXY;
patch out float rationalCubicW;
void main() {
float w = -1; // w<0 means a cubic.
vec2 p1w = P[1];
if (isinf(P[3].y)) {
// This patch is actually a conic. Project to homogeneous space.
w = P[3].x;
p1w *= w;
}
// Chop the curve at T=1/2.
vec2 ab = (P[0] + p1w) * .5;
vec2 bc = (p1w + P[2]) * .5;
vec2 cd = (P[2] + P[3]) * .5;
vec2 abc = (ab + bc) * .5;
vec2 bcd = (bc + cd) * .5;
vec2 abcd = (abc + bcd) * .5;
float n0, n1;
if (w < 0 || isinf(w)) {
if (w < 0) {
// The patch is a cubic. Calculate how many segments are required to
// linearize each half of the curve.
n0 = wangs_formula_cubic(PRECISION, P[0], ab, abc, abcd, mat2(1));
n1 = wangs_formula_cubic(PRECISION, abcd, bcd, cd, P[3], mat2(1));
rationalCubicW = 1;
} else {
// The patch is a triangle (a conic with infinite weight).
n0 = n1 = 1;
rationalCubicW = -1; // In the next stage, rationalCubicW<0 means triangle.
}
rationalCubicXY = mat4x2(P[0], P[1], P[2], P[3]);
} else {
// The patch is a conic. Unproject p0..5. w1 == w2 == w3 when chopping at .5.
// (See SkConic::chopAt().)
float r = 2.0 / (1.0 + w);
ab *= r, bc *= r, abc *= r;
// Put in "standard form" where w0 == w2 == w4 == 1.
float w_ = inversesqrt(r); // Both halves have the same w' when chopping at .5.
// Calculate how many segments are needed to linearize each half of the curve.
n0 = wangs_formula_conic(PRECISION, P[0], ab, abc, w_);
n1 = wangs_formula_conic(PRECISION, abc, bc, P[2], w_);
// Covert the conic to a rational cubic in projected form.
rationalCubicXY = mat4x2(P[0],
mix(float4(P[0],P[2]), p1w.xyxy, 2.0/3.0),
P[2]);
rationalCubicW = fma(w, 2.0/3.0, 1.0/3.0);
}
gl_TessLevelOuter[0] = min(n1, MAX_TESSELLATION_SEGMENTS);
gl_TessLevelOuter[1] = 1.0;
gl_TessLevelOuter[2] = min(n0, MAX_TESSELLATION_SEGMENTS);
// Changing the inner level to 1 when n0 == n1 == 1 collapses the entire patch to a
// single triangle. Otherwise, we need an inner level of 2 so our curve triangles
// have an interior point to originate from.
gl_TessLevelInner[0] = min(max(n0, n1), 2.0);
})");
return code;
}
SkString getTessEvaluationShaderGLSL(const GrGeometryProcessor&,
const char* versionAndExtensionDecls,
const GrGLSLUniformHandler&,
const GrShaderCaps&) const override {
SkString code(versionAndExtensionDecls);
code.append(kSkSLTypeDefs);
code.append(kEvalRationalCubicFn);
code.append(R"(
layout(triangles, equal_spacing, ccw) in;
uniform vec4 sk_RTAdjust;
patch in mat4x2 rationalCubicXY;
patch in float rationalCubicW;
void main() {
vec2 vertexpos;
if (rationalCubicW < 0) { // rationalCubicW < 0 means a triangle now.
vertexpos = (gl_TessCoord.x != 0) ? rationalCubicXY[0]
: (gl_TessCoord.y != 0) ? rationalCubicXY[1]
: rationalCubicXY[2];
} else {
// Locate our parametric point of interest. T ramps from [0..1/2] on the left
// edge of the triangle, and [1/2..1] on the right. If we are the patch's
// interior vertex, then we want T=1/2. Since the barycentric coords are
// (1/3, 1/3, 1/3) at the interior vertex, the below fma() works in all 3
// scenarios.
float T = fma(.5, gl_TessCoord.y, gl_TessCoord.z);
mat4x3 P = mat4x3(rationalCubicXY[0], 1,
rationalCubicXY[1], rationalCubicW,
rationalCubicXY[2], rationalCubicW,
rationalCubicXY[3], 1);
vertexpos = eval_rational_cubic(P, T);
if (all(notEqual(gl_TessCoord.xz, vec2(0)))) {
// We are the interior point of the patch; center it inside
// [C(0), C(.5), C(1)].
vertexpos = (P[0].xy + vertexpos + P[3].xy) / 3.0;
}
}
gl_Position = vec4(vertexpos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0);
})");
return code;
}
};
return std::make_unique<Impl>();
}
} // namespace
GrPathTessellationShader* GrPathTessellationShader::MakeHardwareTessellationShader(
SkArenaAlloc* arena, const SkMatrix& viewMatrix, const SkPMColor4f& color,
PatchAttribs attribs) {
SkASSERT(!(attribs & PatchAttribs::kColor)); // Not yet implemented.
SkASSERT(!(attribs & PatchAttribs::kExplicitCurveType)); // Not yet implemented.
if (attribs & PatchAttribs::kFanPoint) {
return arena->make<HardwareWedgeShader>(viewMatrix, color, attribs);
} else {
return arena->make<HardwareCurveShader>(viewMatrix, color, attribs);
}
}

View File

@ -0,0 +1,227 @@
/*
* 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/ganesh/tessellate/shaders/GrPathTessellationShader.h"
#include "src/core/SkMathPriv.h"
#include "src/gpu/KeyBuilder.h"
#include "src/gpu/ganesh/glsl/GrGLSLVertexGeoBuilder.h"
#include "src/gpu/tessellate/FixedCountBufferUtils.h"
#include "src/gpu/tessellate/Tessellation.h"
#include "src/gpu/tessellate/WangsFormula.h"
using skgpu::PatchAttribs;
using skgpu::VertexWriter;
namespace {
// Uses instanced draws to triangulate standalone closed curves with a "middle-out" topology.
// Middle-out draws a triangle with vertices at T=[0, 1/2, 1] and then recurses breadth first:
//
// depth=0: T=[0, 1/2, 1]
// depth=1: T=[0, 1/4, 2/4], T=[2/4, 3/4, 1]
// depth=2: T=[0, 1/8, 2/8], T=[2/8, 3/8, 4/8], T=[4/8, 5/8, 6/8], T=[6/8, 7/8, 1]
// ...
//
// The shader determines how many segments are required to render each individual curve smoothly,
// and emits empty triangles at any vertices whose sk_VertexIDs are higher than necessary. It is the
// caller's responsibility to draw enough vertices per instance for the most complex curve in the
// batch to render smoothly (i.e., NumTrianglesAtResolveLevel() * 3).
class MiddleOutShader : public GrPathTessellationShader {
public:
MiddleOutShader(const GrShaderCaps& shaderCaps, const SkMatrix& viewMatrix,
const SkPMColor4f& color, PatchAttribs attribs)
: GrPathTessellationShader(kTessellate_MiddleOutShader_ClassID,
GrPrimitiveType::kTriangles, 0, viewMatrix, color, attribs) {
fInstanceAttribs.emplace_back("p01", kFloat4_GrVertexAttribType, SkSLType::kFloat4);
fInstanceAttribs.emplace_back("p23", kFloat4_GrVertexAttribType, SkSLType::kFloat4);
if (fAttribs & PatchAttribs::kFanPoint) {
fInstanceAttribs.emplace_back("fanPointAttrib",
kFloat2_GrVertexAttribType,
SkSLType::kFloat2);
}
if (fAttribs & PatchAttribs::kColor) {
fInstanceAttribs.emplace_back("colorAttrib",
(fAttribs & PatchAttribs::kWideColorIfEnabled)
? kFloat4_GrVertexAttribType
: kUByte4_norm_GrVertexAttribType,
SkSLType::kHalf4);
}
if (fAttribs & PatchAttribs::kExplicitCurveType) {
// A conic curve is written out with p3=[w,Infinity], but GPUs that don't support
// infinity can't detect this. On these platforms we also write out an extra float with
// each patch that explicitly tells the shader what type of curve it is.
fInstanceAttribs.emplace_back("curveType", kFloat_GrVertexAttribType, SkSLType::kFloat);
}
this->setInstanceAttributesWithImplicitOffsets(fInstanceAttribs.data(),
fInstanceAttribs.count());
SkASSERT(fInstanceAttribs.count() <= kMaxInstanceAttribCount);
SkASSERT(this->instanceStride() ==
sizeof(SkPoint) * 4 + skgpu::PatchAttribsStride(fAttribs));
constexpr static Attribute kVertexAttrib("resolveLevel_and_idx", kFloat2_GrVertexAttribType,
SkSLType::kFloat2);
this->setVertexAttributesWithImplicitOffsets(&kVertexAttrib, 1);
}
int maxTessellationSegments(const GrShaderCaps&) const override {
return skgpu::kMaxParametricSegments;
}
private:
const char* name() const final { return "tessellate_MiddleOutShader"; }
void addToKey(const GrShaderCaps&, skgpu::KeyBuilder* b) const final {
// When color is in a uniform, it's always wide so we need to ignore kWideColorIfEnabled.
// When color is in an attrib, its wideness is accounted for as part of the attrib key in
// GrGeometryProcessor::getAttributeKey().
// Either way, we get the correct key by ignoring .
b->add32((uint32_t)(fAttribs & ~PatchAttribs::kWideColorIfEnabled));
}
std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const final;
constexpr static int kMaxInstanceAttribCount = 5;
SkSTArray<kMaxInstanceAttribCount, Attribute> fInstanceAttribs;
};
std::unique_ptr<GrGeometryProcessor::ProgramImpl> MiddleOutShader::makeProgramImpl(
const GrShaderCaps&) const {
class Impl : public GrPathTessellationShader::Impl {
void emitVertexCode(const GrShaderCaps& shaderCaps,
const GrPathTessellationShader& shader,
GrGLSLVertexBuilder* v,
GrGLSLVaryingHandler* varyingHandler,
GrGPArgs* gpArgs) override {
const MiddleOutShader& middleOutShader = shader.cast<MiddleOutShader>();
v->defineConstant("PRECISION", skgpu::kTessellationPrecision);
v->defineConstant("MAX_FIXED_RESOLVE_LEVEL",
(float)skgpu::kMaxFixedResolveLevel);
v->defineConstant("MAX_FIXED_SEGMENTS",
(float)(skgpu::kMaxParametricSegments));
v->insertFunction(GrTessellationShader::WangsFormulaSkSL());
if (middleOutShader.fAttribs & PatchAttribs::kExplicitCurveType) {
v->insertFunction(SkStringPrintf(R"(
bool is_conic_curve() {
return curveType != %g;
})", skgpu::kCubicCurveType).c_str());
v->insertFunction(SkStringPrintf(R"(
bool is_triangular_conic_curve() {
return curveType == %g;
})", skgpu::kTriangularConicCurveType).c_str());
} else {
SkASSERT(shaderCaps.infinitySupport());
v->insertFunction(R"(
bool is_conic_curve() { return isinf(p23.w); }
bool is_triangular_conic_curve() { return isinf(p23.z); })");
}
if (shaderCaps.bitManipulationSupport()) {
v->insertFunction(R"(
float ldexp_portable(float x, float p) {
return ldexp(x, int(p));
})");
} else {
v->insertFunction(R"(
float ldexp_portable(float x, float p) {
return x * exp2(p);
})");
}
v->codeAppend(R"(
float resolveLevel = resolveLevel_and_idx.x;
float idxInResolveLevel = resolveLevel_and_idx.y;
float2 localcoord;)");
if (middleOutShader.fAttribs & PatchAttribs::kFanPoint) {
v->codeAppend(R"(
// A negative resolve level means this is the fan point.
if (resolveLevel < 0) {
localcoord = fanPointAttrib;
} else)"); // Fall through to next if ().
}
v->codeAppend(R"(
if (is_triangular_conic_curve()) {
// This patch is an exact triangle.
localcoord = (resolveLevel != 0) ? p01.zw
: (idxInResolveLevel != 0) ? p23.xy
: p01.xy;
} else {
float2 p0=p01.xy, p1=p01.zw, p2=p23.xy, p3=p23.zw;
float w = -1; // w < 0 tells us to treat the instance as an integral cubic.
float maxResolveLevel;
if (is_conic_curve()) {
// Conics are 3 points, with the weight in p3.
w = p3.x;
maxResolveLevel = wangs_formula_conic_log2(PRECISION, AFFINE_MATRIX * p0,
AFFINE_MATRIX * p1,
AFFINE_MATRIX * p2, w);
p1 *= w; // Unproject p1.
p3 = p2; // Duplicate the endpoint for shared code that also runs on cubics.
} else {
// The patch is an integral cubic.
maxResolveLevel = wangs_formula_cubic_log2(PRECISION, p0, p1, p2, p3,
AFFINE_MATRIX);
}
if (resolveLevel > maxResolveLevel) {
// This vertex is at a higher resolve level than we need. Demote to a lower
// resolveLevel, which will produce a degenerate triangle.
idxInResolveLevel = floor(ldexp_portable(idxInResolveLevel,
maxResolveLevel - resolveLevel));
resolveLevel = maxResolveLevel;
}
// Promote our location to a discrete position in the maximum fixed resolve level.
// This is extra paranoia to ensure we get the exact same fp32 coordinates for
// colocated points from different resolve levels (e.g., the vertices T=3/4 and
// T=6/8 should be exactly colocated).
float fixedVertexID = floor(.5 + ldexp_portable(
idxInResolveLevel, MAX_FIXED_RESOLVE_LEVEL - resolveLevel));
if (0 < fixedVertexID && fixedVertexID < MAX_FIXED_SEGMENTS) {
float T = fixedVertexID * (1 / MAX_FIXED_SEGMENTS);
// Evaluate at T. Use De Casteljau's for its accuracy and stability.
float2 ab = mix(p0, p1, T);
float2 bc = mix(p1, p2, T);
float2 cd = mix(p2, p3, T);
float2 abc = mix(ab, bc, T);
float2 bcd = mix(bc, cd, T);
float2 abcd = mix(abc, bcd, T);
// Evaluate the conic weight at T.
float u = mix(1.0, w, T);
float v = w + 1 - u; // == mix(w, 1, T)
float uv = mix(u, v, T);
localcoord = (w < 0) ? /*cubic*/ abcd : /*conic*/ abc/uv;
} else {
localcoord = (fixedVertexID == 0) ? p0.xy : p3.xy;
}
}
float2 vertexpos = AFFINE_MATRIX * localcoord + TRANSLATE;)");
gpArgs->fLocalCoordVar.set(SkSLType::kFloat2, "localcoord");
gpArgs->fPositionVar.set(SkSLType::kFloat2, "vertexpos");
if (middleOutShader.fAttribs & PatchAttribs::kColor) {
GrGLSLVarying colorVarying(SkSLType::kHalf4);
varyingHandler->addVarying("color",
&colorVarying,
GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
v->codeAppendf("%s = colorAttrib;", colorVarying.vsOut());
fVaryingColorName = colorVarying.fsIn();
}
}
};
return std::make_unique<Impl>();
}
} // namespace
GrPathTessellationShader* GrPathTessellationShader::MakeMiddleOutFixedCountShader(
const GrShaderCaps& shaderCaps,
SkArenaAlloc* arena,
const SkMatrix& viewMatrix,
const SkPMColor4f& color,
PatchAttribs attribs) {
// 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(shaderCaps.infinitySupport() != (attribs & PatchAttribs::kExplicitCurveType));
return arena->make<MiddleOutShader>(shaderCaps, viewMatrix, color, attribs);
}