Replace the indirect patch tessellator with fixed count
Bug: skia:10419
Change-Id: Icb3395565060d736624d03ba1465926bd9188e44
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/416078
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
diff --git a/bench/TessellateBench.cpp b/bench/TessellateBench.cpp
index 334e5bb..ffd8e5c 100644
--- a/bench/TessellateBench.cpp
+++ b/bench/TessellateBench.cpp
@@ -15,7 +15,6 @@
#include "src/gpu/mock/GrMockOpTarget.h"
#include "src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h"
#include "src/gpu/tessellate/GrPathCurveTessellator.h"
-#include "src/gpu/tessellate/GrPathIndirectTessellator.h"
#include "src/gpu/tessellate/GrPathWedgeTessellator.h"
#include "src/gpu/tessellate/GrStrokeFixedCountTessellator.h"
#include "src/gpu/tessellate/GrStrokeHardwareTessellator.h"
@@ -112,17 +111,12 @@
DEF_BENCH( return new PathTessellateBenchmark_##NAME(); ); \
void PathTessellateBenchmark_##NAME::runBench()
-DEF_PATH_TESS_BENCH(GrPathIndirectTessellator, make_cubic_path(18), SkMatrix::I()) {
- SkArenaAlloc arena(1024);
- auto tess = GrPathIndirectTessellator::Make(&arena, fPath, fMatrix, SK_PMColor4fTRANSPARENT,
- GrPathIndirectTessellator::DrawInnerFan::kNo);
- tess->prepare(fTarget.get(), SkRectPriv::MakeLargest(), fPath, nullptr);
-}
-
DEF_PATH_TESS_BENCH(GrPathOuterCurveTessellator, make_cubic_path(8), SkMatrix::I()) {
SkArenaAlloc arena(1024);
auto tess = GrPathCurveTessellator::Make(&arena, fMatrix, SK_PMColor4fTRANSPARENT,
- GrPathTessellator::DrawInnerFan::kNo);
+ GrPathTessellator::DrawInnerFan::kNo,
+ fTarget->caps().minPathVerbsForHwTessellation(),
+ fTarget->caps());
tess->prepare(fTarget.get(), SkRectPriv::MakeLargest(), fPath, nullptr);
}
diff --git a/gn/gpu.gni b/gn/gpu.gni
index 64b46f8..e3bd08a 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -456,8 +456,6 @@
"$_src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h",
"$_src/gpu/tessellate/GrPathCurveTessellator.cpp",
"$_src/gpu/tessellate/GrPathCurveTessellator.h",
- "$_src/gpu/tessellate/GrPathIndirectTessellator.cpp",
- "$_src/gpu/tessellate/GrPathIndirectTessellator.h",
"$_src/gpu/tessellate/GrPathInnerTriangulateOp.cpp",
"$_src/gpu/tessellate/GrPathInnerTriangulateOp.h",
"$_src/gpu/tessellate/GrPathStencilFillOp.cpp",
diff --git a/samplecode/SamplePathTessellators.cpp b/samplecode/SamplePathTessellators.cpp
index 7901542..762d07c 100644
--- a/samplecode/SamplePathTessellators.cpp
+++ b/samplecode/SamplePathTessellators.cpp
@@ -15,7 +15,6 @@
#include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/GrSurfaceDrawContext.h"
#include "src/gpu/tessellate/GrPathCurveTessellator.h"
-#include "src/gpu/tessellate/GrPathIndirectTessellator.h"
#include "src/gpu/tessellate/GrPathWedgeTessellator.h"
#include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
@@ -70,16 +69,20 @@
constexpr static SkPMColor4f kCyan = {0,1,1,1};
auto alloc = flushState->allocator();
switch (fMode) {
+ using DrawInnerFan = GrPathTessellator::DrawInnerFan;
+ using ShaderType = GrPathCurveTessellator::ShaderType;
case Mode::kCurveMiddleOut:
- fTessellator = GrPathIndirectTessellator::Make(
- alloc, fPath, fMatrix, kCyan, GrPathTessellator::DrawInnerFan::kYes);
+ fTessellator = GrPathCurveTessellator::Make(alloc, fMatrix, kCyan,
+ DrawInnerFan::kYes,
+ ShaderType::kFixedCountMiddleOut);
break;
case Mode::kWedgeTessellate:
fTessellator = GrPathWedgeTessellator::Make(alloc, fMatrix, kCyan);
break;
case Mode::kCurveTessellate:
fTessellator = GrPathCurveTessellator::Make(alloc, fMatrix, kCyan,
- GrPathTessellator::DrawInnerFan::kYes);
+ DrawInnerFan::kYes,
+ ShaderType::kHardwareTessellation);
break;
}
fTessellator->prepare(flushState, this->bounds(), fPath);
diff --git a/src/gpu/tessellate/GrPathCurveTessellator.cpp b/src/gpu/tessellate/GrPathCurveTessellator.cpp
index d4b3304..706f9a0 100644
--- a/src/gpu/tessellate/GrPathCurveTessellator.cpp
+++ b/src/gpu/tessellate/GrPathCurveTessellator.cpp
@@ -21,16 +21,11 @@
// supported by the hardware.
class CurveWriter {
public:
- CurveWriter(const SkRect& cullBounds, const SkMatrix& viewMatrix,
- const GrShaderCaps& shaderCaps)
+ CurveWriter(const SkRect& cullBounds, const SkMatrix& viewMatrix, int maxSegments)
: fCullTest(cullBounds, viewMatrix)
- , fVectorXform(viewMatrix) {
- // GrCurveTessellateShader tessellates T=0..(1/2) on the first side of the 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.
- float maxSegments = shaderCaps.maxTessellationSegments() * 2;
- fMaxSegments_pow2 = maxSegments * maxSegments;
- fMaxSegments_pow4 = fMaxSegments_pow2 * fMaxSegments_pow2;
+ , fVectorXform(viewMatrix)
+ , fMaxSegments_pow2(maxSegments * maxSegments)
+ , fMaxSegments_pow4(fMaxSegments_pow2 * fMaxSegments_pow2) {
}
SK_ALWAYS_INLINE void writeQuadratic(GrVertexChunkBuilder* chunker, const SkPoint p[3]) {
@@ -43,6 +38,7 @@
if (GrVertexWriter vertexWriter = chunker->appendVertex()) {
GrPathUtils::writeQuadAsCubic(p, &vertexWriter);
}
+ fNumFixedSegments_pow4 = std::max(numSegments_pow4, fNumFixedSegments_pow4);
}
}
@@ -56,6 +52,8 @@
if (GrVertexWriter vertexWriter = chunker->appendVertex()) {
GrTessellationShader::WriteConicPatch(p, w, &vertexWriter);
}
+ fNumFixedSegments_pow4 = std::max(numSegments_pow2 * numSegments_pow2,
+ fNumFixedSegments_pow4);
}
}
@@ -69,9 +67,12 @@
if (GrVertexWriter vertexWriter = chunker->appendVertex()) {
vertexWriter.writeArray(p, 4);
}
+ fNumFixedSegments_pow4 = std::max(numSegments_pow4, fNumFixedSegments_pow4);
}
}
+ int numFixedSegments_pow4() const { return fNumFixedSegments_pow4; }
+
private:
void chopAndWriteQuadratic(GrVertexChunkBuilder* chunker, const SkPoint p[3]) {
SkPoint chops[5];
@@ -123,31 +124,58 @@
GrCullTest fCullTest;
GrVectorXform fVectorXform;
- float fMaxSegments_pow2;
- float fMaxSegments_pow4;
+ const float fMaxSegments_pow2;
+ const float fMaxSegments_pow4;
+
+ // If using fixed count, this is the number of segments we need to emit per instance. Always
+ // emit at least 2 segments so we can support triangles.
+ float fNumFixedSegments_pow4 = 2*2*2*2;
};
} // namespace
-GrPathTessellator* GrPathCurveTessellator::Make(SkArenaAlloc* arena,
- const SkMatrix& viewMatrix,
- const SkPMColor4f& color,
- DrawInnerFan drawInnerFan) {
- auto shader = GrPathTessellationShader::MakeHardwareCurveShader(arena, viewMatrix, color);
- return arena->make<GrPathCurveTessellator>(shader, drawInnerFan);
+GrPathTessellator* GrPathCurveTessellator::Make(SkArenaAlloc* arena, const SkMatrix& viewMatrix,
+ const SkPMColor4f& color, DrawInnerFan drawInnerFan,
+ int numPathVerbs, const GrCaps& caps) {
+ if (caps.shaderCaps()->tessellationSupport() &&
+ numPathVerbs >= caps.minPathVerbsForHwTessellation()) {
+ return Make(arena, viewMatrix, color, drawInnerFan, ShaderType::kHardwareTessellation);
+ } else {
+ return Make(arena, viewMatrix, color, drawInnerFan, ShaderType::kFixedCountMiddleOut);
+ }
+}
+
+GrPathTessellator* GrPathCurveTessellator::Make(SkArenaAlloc* arena, const SkMatrix& viewMatrix,
+ const SkPMColor4f& color, DrawInnerFan drawInnerFan,
+ ShaderType shaderType) {
+ GrPathTessellationShader* shader;
+ switch (shaderType) {
+ case ShaderType::kFixedCountMiddleOut:
+ shader = GrPathTessellationShader::MakeMiddleOutInstancedShader(arena, viewMatrix,
+ color);
+ break;
+ case ShaderType::kHardwareTessellation:
+ shader = GrPathTessellationShader::MakeHardwareCurveShader(arena, viewMatrix, color);
+ break;
+ }
+ return arena->make([=](void* objStart) {
+ return new(objStart) GrPathCurveTessellator(shader, drawInnerFan);
+ });
}
void GrPathCurveTessellator::prepare(GrMeshDrawOp::Target* target, const SkRect& cullBounds,
- const SkPath& path,
- const BreadcrumbTriangleList* breadcrumbTriangleList) {
- SkASSERT(target->caps().shaderCaps()->tessellationSupport());
+ const SkPath& path,
+ const BreadcrumbTriangleList* breadcrumbTriangleList) {
SkASSERT(fVertexChunkArray.empty());
// Determine how many triangles to allocate.
int maxTriangles = 0;
if (fDrawInnerFan) {
- maxTriangles += GrPathTessellator::MaxTrianglesInInnerFan(path);
+ // An n-sided polygon is fanned by n-2 triangles.
+ int maxEdgesInFan = GrPathTessellator::MaxSegmentsInPath(path);
+ int maxTrianglesInFan = std::max(maxEdgesInFan - 2, 0);
+ maxTriangles += maxTrianglesInFan;
}
if (breadcrumbTriangleList) {
maxTriangles += breadcrumbTriangleList->count();
@@ -174,14 +202,43 @@
numRemainingTriangles -= numWritten;
}
if (breadcrumbTriangleList) {
- int numWritten = GrPathTessellator::WriteBreadcrumbTriangles(&vertexWriter,
- breadcrumbTriangleList);
+ int numWritten = 0;
+ SkDEBUGCODE(int count = 0;)
+ for (const auto* tri = breadcrumbTriangleList->head(); tri; tri = tri->fNext) {
+ SkDEBUGCODE(++count;)
+ auto p0 = grvx::float2::Load(tri->fPts);
+ auto p1 = grvx::float2::Load(tri->fPts + 1);
+ auto p2 = grvx::float2::Load(tri->fPts + 2);
+ if (skvx::any((p0 == p1) & (p1 == p2))) {
+ // Cull completely horizontal or vertical triangles. GrTriangulator can't always
+ // get these breadcrumb edges right when they run parallel to the sweep
+ // direction because their winding is undefined by its current definition.
+ // FIXME(skia:12060): This seemed safe, but if there is a view matrix it will
+ // introduce T-junctions.
+ continue;
+ }
+ vertexWriter.writeArray(tri->fPts, 3);
+ // Mark this instance as a triangle by setting it to a conic with w=Inf.
+ vertexWriter.fill(GrVertexWriter::kIEEE_32_infinity, 2);
+ ++numWritten;
+ }
+ SkASSERT(count == breadcrumbTriangleList->count());
numRemainingTriangles -= numWritten;
}
chunker.popVertices(numRemainingTriangles);
}
- CurveWriter curveWriter(cullBounds, fShader->viewMatrix(), *target->caps().shaderCaps());
+ 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 = target->caps().shaderCaps()->maxTessellationSegments() * 2;
+ } else {
+ maxSegments = kMaxFixedCountSegments;
+ }
+
+ CurveWriter curveWriter(cullBounds, fShader->viewMatrix(), maxSegments);
for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
switch (verb) {
case SkPathVerb::kQuad:
@@ -197,11 +254,27 @@
break;
}
}
+
+ if (!fShader->willUseTessellationShaders()) {
+ // log2(n) == log16(n^4).
+ int fixedResolveLevel = GrWangsFormula::nextlog16(curveWriter.numFixedSegments_pow4());
+ fFixedVertexCount =
+ GrPathTessellationShader::NumTrianglesAtResolveLevel(fixedResolveLevel) * 3;
+ }
}
+
void GrPathCurveTessellator::draw(GrOpFlushState* flushState) const {
- for (const GrVertexChunk& chunk : fVertexChunkArray) {
- flushState->bindBuffers(nullptr, nullptr, chunk.fBuffer);
- flushState->draw(chunk.fCount * 4, chunk.fBase * 4);
+ 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(nullptr, chunk.fBuffer, nullptr);
+ flushState->drawInstanced(chunk.fCount, chunk.fBase, fFixedVertexCount, 0);
+ }
}
}
diff --git a/src/gpu/tessellate/GrPathCurveTessellator.h b/src/gpu/tessellate/GrPathCurveTessellator.h
index c0785db..10e2d57 100644
--- a/src/gpu/tessellate/GrPathCurveTessellator.h
+++ b/src/gpu/tessellate/GrPathCurveTessellator.h
@@ -16,8 +16,18 @@
// or a conic. Quadratics are converted to cubics and triangles are converted to conics with w=Inf.
class GrPathCurveTessellator : public GrPathTessellator {
public:
- static GrPathTessellator* Make(SkArenaAlloc*, const SkMatrix&, const SkPMColor4f&,
- DrawInnerFan);
+ // Creates a curve tessellator with the shader type best suited for the given path description.
+ static GrPathTessellator* Make(SkArenaAlloc*, const SkMatrix& viewMatrix, const SkPMColor4f&,
+ DrawInnerFan, int numPathVerbs, const GrCaps&);
+
+ enum class ShaderType {
+ kFixedCountMiddleOut,
+ kHardwareTessellation
+ };
+
+ // Creates a curve tessellator with a specific shader type.
+ static GrPathTessellator* Make(SkArenaAlloc*, const SkMatrix& viewMatrix, const SkPMColor4f&,
+ DrawInnerFan, ShaderType);
void prepare(GrMeshDrawOp::Target*, const SkRect& cullBounds, const SkPath&,
const BreadcrumbTriangleList*) override;
@@ -32,7 +42,8 @@
const bool fDrawInnerFan;
GrVertexChunkArray fVertexChunkArray;
- friend class SkArenaAlloc; // For constructor.
+ // If using fixed count, this is the number of vertices we need to emit per instance.
+ int fFixedVertexCount;
};
#endif
diff --git a/src/gpu/tessellate/GrPathIndirectTessellator.cpp b/src/gpu/tessellate/GrPathIndirectTessellator.cpp
deleted file mode 100644
index 80aa3ff..0000000
--- a/src/gpu/tessellate/GrPathIndirectTessellator.cpp
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * Copyright 2021 Google LLC.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "src/gpu/tessellate/GrPathIndirectTessellator.h"
-
-#include "src/gpu/GrEagerVertexAllocator.h"
-#include "src/gpu/geometry/GrPathUtils.h"
-#include "src/gpu/geometry/GrWangsFormula.h"
-#include "src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h"
-#include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
-
-constexpr static float kPrecision = GrTessellationPathRenderer::kLinearizationPrecision;
-
-GrPathTessellator* GrPathIndirectTessellator::Make(SkArenaAlloc* arena, const SkPath& path,
- const SkMatrix& viewMatrix,
- const SkPMColor4f& color,
- DrawInnerFan drawInnerFan) {
- auto shader = GrPathTessellationShader::MakeMiddleOutInstancedShader(arena, viewMatrix, color);
- return arena->make<GrPathIndirectTessellator>(shader, path, drawInnerFan);
-}
-
-GrPathIndirectTessellator::GrPathIndirectTessellator(GrPathTessellationShader* shader,
- const SkPath& path, DrawInnerFan drawInnerFan)
- : GrPathTessellator(shader)
- , fDrawInnerFan(drawInnerFan != DrawInnerFan::kNo) {
- // Count the number of instances at each resolveLevel.
- GrVectorXform xform(fShader->viewMatrix());
- for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
- int level;
- switch (verb) {
- case SkPathVerb::kQuad:
- level = GrWangsFormula::quadratic_log2(kPrecision, pts, xform);
- break;
- case SkPathVerb::kConic:
- level = GrWangsFormula::conic_log2(kPrecision, pts, *w, xform);
- break;
- case SkPathVerb::kCubic:
- level = GrWangsFormula::cubic_log2(kPrecision, pts, xform);
- break;
- default:
- continue;
- }
- SkASSERT(level >= 0);
- // Instances with 2^0=1 segments are empty (zero area). We ignore them completely.
- if (level > 0) {
- level = std::min(level, kMaxResolveLevel);
- ++fResolveLevelCounts[level];
- ++fOuterCurveInstanceCount;
- }
- }
-}
-
-// How many vertices do we need to draw in order to triangulate a curve with 2^resolveLevel line
-// segments?
-constexpr static int num_vertices_at_resolve_level(int resolveLevel) {
- // resolveLevel=0 -> 0 line segments -> 0 triangles -> 0 vertices
- // resolveLevel=1 -> 2 line segments -> 1 triangle -> 3 vertices
- // resolveLevel=2 -> 4 line segments -> 3 triangles -> 9 vertices
- // resolveLevel=3 -> 8 line segments -> 7 triangles -> 21 vertices
- // ...
- return ((1 << resolveLevel) - 1) * 3;
-}
-
-void GrPathIndirectTessellator::prepare(GrMeshDrawOp::Target* target, const SkRect& /*cullBounds*/,
- const SkPath& path,
- const BreadcrumbTriangleList* breadcrumbTriangleList) {
- SkASSERT(fTotalInstanceCount == 0);
- SkASSERT(fIndirectDrawCount == 0);
- SkASSERT(target->caps().drawInstancedSupport());
-
- int instanceLockCount = fOuterCurveInstanceCount;
- if (fDrawInnerFan) {
- instanceLockCount += GrPathTessellator::MaxTrianglesInInnerFan(path);
- }
- if (breadcrumbTriangleList) {
- instanceLockCount += breadcrumbTriangleList->count();
- }
- if (instanceLockCount == 0) {
- return;
- }
-
- // Allocate a buffer to store the instance data.
- GrEagerDynamicVertexAllocator vertexAlloc(target, &fInstanceBuffer, &fBaseInstance);
- GrVertexWriter instanceWriter = static_cast<SkPoint*>(vertexAlloc.lock(sizeof(SkPoint) * 4,
- instanceLockCount));
- if (!instanceWriter) {
- return;
- }
-
- // Write out any triangles at the beginning of the cubic data. Since this shader draws curves,
- // output the triangles as conics with w=infinity (which is equivalent to a triangle).
- int numTrianglesAtBeginningOfData = 0;
- if (fDrawInnerFan) {
- numTrianglesAtBeginningOfData = GrMiddleOutPolygonTriangulator::WritePathInnerFan(
- &instanceWriter,
- GrMiddleOutPolygonTriangulator::OutputType::kConicsWithInfiniteWeight, path);
- }
- if (breadcrumbTriangleList) {
- numTrianglesAtBeginningOfData += GrPathTessellator::WriteBreadcrumbTriangles(
- &instanceWriter, breadcrumbTriangleList);
- }
-
- // Allocate space for the GrDrawIndexedIndirectCommand structs. Allocate enough for each
- // possible resolve level (kMaxResolveLevel; resolveLevel=0 never has any instances), plus one
- // more for the optional inner fan triangles.
- int indirectLockCnt = kMaxResolveLevel + 1;
- GrDrawIndirectWriter indirectWriter = target->makeDrawIndirectSpace(indirectLockCnt,
- &fIndirectDrawBuffer,
- &fIndirectDrawOffset);
- if (!indirectWriter) {
- SkASSERT(!fIndirectDrawBuffer);
- vertexAlloc.unlock(0);
- return;
- }
-
- // Fill out the GrDrawIndexedIndirectCommand structs and determine the starting instance data
- // location at each resolve level.
- GrVertexWriter instanceLocations[kMaxResolveLevel + 1];
- int currentBaseInstance = fBaseInstance;
- SkASSERT(fResolveLevelCounts[0] == 0);
- for (int resolveLevel=1, numExtraInstances=numTrianglesAtBeginningOfData;
- resolveLevel <= kMaxResolveLevel;
- ++resolveLevel, numExtraInstances=0) {
- int instanceCountAtCurrLevel = fResolveLevelCounts[resolveLevel];
- if (!(instanceCountAtCurrLevel + numExtraInstances)) {
- SkDEBUGCODE(instanceLocations[resolveLevel] = nullptr;)
- continue;
- }
- instanceLocations[resolveLevel] = instanceWriter.makeOffset(0);
- SkASSERT(fIndirectDrawCount < indirectLockCnt);
- // The vertex shader determines the T value at which to draw each vertex. Since the
- // triangles are arranged in "middle-out" order, we can conveniently control the
- // resolveLevel by changing only the vertexCount.
- indirectWriter.write(instanceCountAtCurrLevel + numExtraInstances, currentBaseInstance,
- num_vertices_at_resolve_level(resolveLevel), 0);
- ++fIndirectDrawCount;
- currentBaseInstance += instanceCountAtCurrLevel + numExtraInstances;
- instanceWriter = instanceWriter.makeOffset(instanceCountAtCurrLevel * 4 * sizeof(SkPoint));
- }
-
- target->putBackIndirectDraws(indirectLockCnt - fIndirectDrawCount);
-
-#ifdef SK_DEBUG
- SkASSERT(currentBaseInstance ==
- fBaseInstance + numTrianglesAtBeginningOfData + fOuterCurveInstanceCount);
-
- GrVertexWriter endLocations[kMaxResolveLevel + 1];
- int lastResolveLevel = 0;
- for (int resolveLevel = 1; resolveLevel <= kMaxResolveLevel; ++resolveLevel) {
- if (!instanceLocations[resolveLevel]) {
- endLocations[resolveLevel] = nullptr;
- continue;
- }
- endLocations[lastResolveLevel] = instanceLocations[resolveLevel].makeOffset(0);
- lastResolveLevel = resolveLevel;
- }
- endLocations[lastResolveLevel] = instanceWriter.makeOffset(0);
-#endif
-
- fTotalInstanceCount = numTrianglesAtBeginningOfData;
-
- // Write out the cubic instances.
- if (fOuterCurveInstanceCount) {
- GrVectorXform xform(fShader->viewMatrix());
- for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
- int level;
- switch (verb) {
- default:
- continue;
- case SkPathVerb::kQuad:
- level = GrWangsFormula::quadratic_log2(kPrecision, pts, xform);
- break;
- case SkPathVerb::kConic:
- level = GrWangsFormula::conic_log2(kPrecision, pts, *w, xform);
- break;
- case SkPathVerb::kCubic:
- level = GrWangsFormula::cubic_log2(kPrecision, pts, xform);
- break;
- }
- if (level == 0) {
- continue;
- }
- level = std::min(level, kMaxResolveLevel);
- switch (verb) {
- case SkPathVerb::kQuad:
- GrPathUtils::writeQuadAsCubic(pts, &instanceLocations[level]);
- break;
- case SkPathVerb::kCubic:
- instanceLocations[level].writeArray(pts, 4);
- break;
- case SkPathVerb::kConic:
- GrTessellationShader::WriteConicPatch(pts, *w, &instanceLocations[level]);
- break;
- default:
- SkUNREACHABLE;
- }
- ++fTotalInstanceCount;
- }
- }
-
-#ifdef SK_DEBUG
- for (int i = 1; i <= kMaxResolveLevel; ++i) {
- SkASSERT(instanceLocations[i] == endLocations[i]);
- }
- SkASSERT(fTotalInstanceCount == numTrianglesAtBeginningOfData + fOuterCurveInstanceCount);
-#endif
-
- vertexAlloc.unlock(fTotalInstanceCount);
-}
-
-void GrPathIndirectTessellator::draw(GrOpFlushState* flushState) const {
- if (fIndirectDrawCount) {
- flushState->bindBuffers(nullptr, fInstanceBuffer, nullptr);
- flushState->drawIndirect(fIndirectDrawBuffer.get(), fIndirectDrawOffset,
- fIndirectDrawCount);
- }
-}
-
-void GrPathIndirectTessellator::drawHullInstances(GrOpFlushState* flushState) const {
- if (fTotalInstanceCount) {
- flushState->bindBuffers(nullptr, fInstanceBuffer, nullptr);
- flushState->drawInstanced(fTotalInstanceCount, fBaseInstance, 4, 0);
- }
-}
diff --git a/src/gpu/tessellate/GrPathIndirectTessellator.h b/src/gpu/tessellate/GrPathIndirectTessellator.h
deleted file mode 100644
index c871737..0000000
--- a/src/gpu/tessellate/GrPathIndirectTessellator.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2021 Google LLC.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef GrPathIndirectTessellator_DEFINED
-#define GrPathIndirectTessellator_DEFINED
-
-#include "src/gpu/tessellate/GrPathTessellator.h"
-#include "src/gpu/tessellate/GrTessellationPathRenderer.h"
-
-// Prepares patches of the path's outer curves and, optionally, inner fan triangles using indirect
-// draw commands. Quadratics are converted to cubics and triangles are converted to conics with
-// w=Inf. An outer curve is an independent, 4-point closed contour that represents either a cubic or
-// a conic.
-class GrPathIndirectTessellator : public GrPathTessellator {
-public:
- static GrPathTessellator* Make(SkArenaAlloc*, const SkPath&, const SkMatrix&,
- const SkPMColor4f&, DrawInnerFan);
-
- void prepare(GrMeshDrawOp::Target*, const SkRect& cullBounds, const SkPath&,
- const BreadcrumbTriangleList*) override;
- void draw(GrOpFlushState*) const override;
- void drawHullInstances(GrOpFlushState*) const override;
-
-private:
- constexpr static int kMaxResolveLevel = GrTessellationPathRenderer::kMaxResolveLevel;
-
- GrPathIndirectTessellator(GrPathTessellationShader*, const SkPath&, DrawInnerFan);
-
- const bool fDrawInnerFan;
- int fResolveLevelCounts[kMaxResolveLevel + 1] = {0};
- int fOuterCurveInstanceCount = 0;
-
- sk_sp<const GrBuffer> fInstanceBuffer;
- int fBaseInstance = 0;
- int fTotalInstanceCount = 0;
-
- sk_sp<const GrBuffer> fIndirectDrawBuffer;
- size_t fIndirectDrawOffset = 0;
- int fIndirectDrawCount = 0;
-
- friend class SkArenaAlloc; // For constructor.
-};
-
-#endif
diff --git a/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp b/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp
index c340ae9..37b4c75 100644
--- a/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp
+++ b/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp
@@ -14,7 +14,6 @@
#include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
#include "src/gpu/tessellate/GrPathCurveTessellator.h"
-#include "src/gpu/tessellate/GrPathIndirectTessellator.h"
#include "src/gpu/tessellate/GrTessellationPathRenderer.h"
#include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
@@ -189,16 +188,10 @@
// Pass 1: Tessellate the outer curves into the stencil buffer.
if (!isLinear) {
- if (args.fCaps->shaderCaps()->tessellationSupport() &&
- fPath.countVerbs() >= args.fCaps->minPathVerbsForHwTessellation()) {
- fTessellator = GrPathCurveTessellator::Make(args.fArena, fViewMatrix,
- SK_PMColor4fTRANSPARENT,
- GrPathTessellator::DrawInnerFan::kNo);
- } else {
- fTessellator = GrPathIndirectTessellator::Make(args.fArena, fPath, fViewMatrix,
- SK_PMColor4fTRANSPARENT,
- GrPathTessellator::DrawInnerFan::kNo);
- }
+ fTessellator = GrPathCurveTessellator::Make(args.fArena, fViewMatrix,
+ SK_PMColor4fTRANSPARENT,
+ GrPathTessellator::DrawInnerFan::kNo,
+ fPath.countVerbs(), *args.fCaps);
const GrUserStencilSettings* stencilPathSettings =
GrPathTessellationShader::StencilPathSettings(fPath.getFillType());
fStencilCurvesProgram = GrTessellationShader::MakeProgram(args, fTessellator->shader(),
diff --git a/src/gpu/tessellate/GrPathStencilFillOp.cpp b/src/gpu/tessellate/GrPathStencilFillOp.cpp
index 7fb97f6..06b1c05 100644
--- a/src/gpu/tessellate/GrPathStencilFillOp.cpp
+++ b/src/gpu/tessellate/GrPathStencilFillOp.cpp
@@ -14,7 +14,6 @@
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
#include "src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h"
#include "src/gpu/tessellate/GrPathCurveTessellator.h"
-#include "src/gpu/tessellate/GrPathIndirectTessellator.h"
#include "src/gpu/tessellate/GrPathWedgeTessellator.h"
#include "src/gpu/tessellate/GrTessellationPathRenderer.h"
#include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
@@ -115,13 +114,13 @@
}
if (!args.fCaps->shaderCaps()->tessellationSupport() ||
fPath.countVerbs() < args.fCaps->minPathVerbsForHwTessellation()) {
- fTessellator = GrPathIndirectTessellator::Make(args.fArena, fPath, fViewMatrix,
- SK_PMColor4fTRANSPARENT,
- drawFanWithTessellator);
+ fTessellator = GrPathCurveTessellator::Make(
+ args.fArena, fViewMatrix, SK_PMColor4fTRANSPARENT, drawFanWithTessellator,
+ GrPathCurveTessellator::ShaderType::kFixedCountMiddleOut);
} else if (drawFanWithTessellator == GrPathTessellator::DrawInnerFan::kNo) {
- fTessellator = GrPathCurveTessellator::Make(args.fArena, fViewMatrix,
- SK_PMColor4fTRANSPARENT,
- GrPathTessellator::DrawInnerFan::kNo);
+ fTessellator = GrPathCurveTessellator::Make(
+ args.fArena, fViewMatrix, SK_PMColor4fTRANSPARENT, drawFanWithTessellator,
+ GrPathCurveTessellator::ShaderType::kHardwareTessellation);
} else {
fTessellator = GrPathWedgeTessellator::Make(args.fArena, fViewMatrix,
SK_PMColor4fTRANSPARENT);
diff --git a/src/gpu/tessellate/GrPathTessellator.h b/src/gpu/tessellate/GrPathTessellator.h
index c03701c..b8bb392 100644
--- a/src/gpu/tessellate/GrPathTessellator.h
+++ b/src/gpu/tessellate/GrPathTessellator.h
@@ -23,6 +23,10 @@
public:
using BreadcrumbTriangleList = GrInnerFanTriangulator::BreadcrumbTriangleList;
+ // For fixed count tessellators, this is the largest number of segments we can stuff into a
+ // single instance before we need to chop.
+ constexpr static int kMaxFixedCountSegments = 32;
+
// For subclasses that use this enum, if DrawInnerFan is kNo, the class only emits the path's
// outer curves. In that case the caller is responsible to handle the path's inner fan.
enum class DrawInnerFan : bool {
@@ -30,10 +34,6 @@
kYes
};
- // Creates the tessellator best suited to draw the given path.
- static GrPathTessellator* Make(SkArenaAlloc*, const SkPath&, const SkMatrix&,
- const SkPMColor4f&, DrawInnerFan, const GrCaps&);
-
const GrPathTessellationShader* shader() const { return fShader; }
// Called before draw(). Prepares GPU buffers containing the geometry to tessellate. If the
@@ -53,9 +53,6 @@
virtual ~GrPathTessellator() {}
-protected:
- GrPathTessellator(GrPathTessellationShader* shader) : fShader(shader) {}
-
// Returns an upper bound on the number of segments (lineTo, quadTo, conicTo, cubicTo) in a
// path, also accounting for any implicit lineTos from closing contours.
static int MaxSegmentsInPath(const SkPath& path) {
@@ -65,40 +62,8 @@
return path.countVerbs();
}
- // Returns an upper bound on the number of triangles it would require to fan a path's inner
- // polygon, in the case where no additional vertices are introduced.
- static int MaxTrianglesInInnerFan(const SkPath& path) {
- int maxEdgesInFan = MaxSegmentsInPath(path);
- return std::max(maxEdgesInFan - 2, 0); // An n-sided polygon is fanned by n-2 triangles.
- }
-
- // Writes out the non-degenerate triangles from 'breadcrumbTriangleList' as 4-point conic
- // patches with w=Infinity.
- static int WriteBreadcrumbTriangles(GrVertexWriter* writer,
- const BreadcrumbTriangleList* breadcrumbTriangleList) {
- int numWritten = 0;
- SkDEBUGCODE(int count = 0;)
- for (const auto* tri = breadcrumbTriangleList->head(); tri; tri = tri->fNext) {
- SkDEBUGCODE(++count;)
- auto p0 = grvx::float2::Load(tri->fPts);
- auto p1 = grvx::float2::Load(tri->fPts + 1);
- auto p2 = grvx::float2::Load(tri->fPts + 2);
- if (skvx::any((p0 == p1) & (p1 == p2))) {
- // Cull completely horizontal or vertical triangles. GrTriangulator can't always get
- // these breadcrumb edges right when they run parallel to the sweep direction
- // because their winding is undefined by its current definition.
- // FIXME(skia:12060): This seemed safe, but if there is a view matrix it will
- // introduce T-junctions.
- continue;
- }
- writer->writeArray(tri->fPts, 3);
- // Mark this instance as a triangle by setting it to a conic with w=Inf.
- writer->fill(GrVertexWriter::kIEEE_32_infinity, 2);
- ++numWritten;
- }
- SkASSERT(count == breadcrumbTriangleList->count());
- return numWritten;
- }
+protected:
+ GrPathTessellator(GrPathTessellationShader* shader) : fShader(shader) {}
GrPathTessellationShader* fShader;
};
diff --git a/src/gpu/tessellate/shaders/GrPathTessellationShader.h b/src/gpu/tessellate/shaders/GrPathTessellationShader.h
index d4ac447..4a20aa9 100644
--- a/src/gpu/tessellate/shaders/GrPathTessellationShader.h
+++ b/src/gpu/tessellate/shaders/GrPathTessellationShader.h
@@ -20,6 +20,16 @@
const SkMatrix& viewMatrix,
const SkPMColor4f&);
+ // How many triangles are in a curve with 2^resolveLevel line segments?
+ constexpr static int NumTrianglesAtResolveLevel(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;
+ }
+
// 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:
//
@@ -28,9 +38,10 @@
// 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 caller may compute each cubic's resolveLevel on the CPU (i.e., the log2 number of line
- // segments it will be divided into; see GrWangsFormula::cubic_log2/quadratic_log2/conic_log2),
- // and then sort the instance buffer by resolveLevel for efficient batching of indirect draws.
+ // 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).
static GrPathTessellationShader* MakeMiddleOutInstancedShader(SkArenaAlloc*,
const SkMatrix& viewMatrix,
const SkPMColor4f&);
diff --git a/src/gpu/tessellate/shaders/GrPathTessellationShader_MiddleOut.cpp b/src/gpu/tessellate/shaders/GrPathTessellationShader_MiddleOut.cpp
index ee1d162..5dc5c63 100644
--- a/src/gpu/tessellate/shaders/GrPathTessellationShader_MiddleOut.cpp
+++ b/src/gpu/tessellate/shaders/GrPathTessellationShader_MiddleOut.cpp
@@ -7,6 +7,7 @@
#include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
+#include "src/gpu/geometry/GrWangsFormula.h"
#include "src/gpu/glsl/GrGLSLProgramBuilder.h"
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
@@ -20,9 +21,10 @@
// 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 caller may compute each cubic's resolveLevel on the CPU (i.e., the log2 number of line
-// segments it will be divided into; see GrWangsFormula::cubic_log2/quadratic_log2/conic_log2), and
-// then sort the instance buffer by resolveLevel for efficient batching of indirect draws.
+// 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 SkMatrix& viewMatrix, const SkPMColor4f& color)
@@ -43,13 +45,20 @@
GrGLSLGeometryProcessor* MiddleOutShader::createGLSLInstance(const GrShaderCaps&) const {
class Impl : public GrPathTessellationShader::Impl {
void emitVertexCode(GrGLSLVertexBuilder* v, GrGPArgs* gpArgs) override {
+ v->defineConstant("PRECISION", GrTessellationPathRenderer::kLinearizationPrecision);
+ v->insertFunction(GrWangsFormula::as_sksl().c_str());
if (v->getProgramBuilder()->shaderCaps()->bitManipulationSupport()) {
// Determines the T value at which to place the given vertex in a "middle-out"
// topology.
v->insertFunction(R"(
- float find_middle_out_T() {
+ float find_middle_out_T(float maxResolveLevel) {
int totalTriangleIdx = sk_VertexID/3 + 1;
int resolveLevel = findMSB(totalTriangleIdx) + 1;
+ if (resolveLevel > int(maxResolveLevel)) {
+ // This vertex is at a higher resolve level than we need. Emit a degenerate
+ // triangle at T=1.
+ return 1;
+ }
int firstTriangleAtDepth = (1 << (resolveLevel - 1));
int triangleIdxWithinDepth = totalTriangleIdx - firstTriangleAtDepth;
int vertexIdxWithinDepth = triangleIdxWithinDepth * 2 + sk_VertexID % 3;
@@ -59,9 +68,14 @@
// Determines the T value at which to place the given vertex in a "middle-out"
// topology.
v->insertFunction(R"(
- float find_middle_out_T() {
+ float find_middle_out_T(float maxResolveLevel) {
float totalTriangleIdx = float(sk_VertexID/3) + 1;
float resolveLevel = floor(log2(totalTriangleIdx)) + 1;
+ if (resolveLevel > maxResolveLevel) {
+ // This vertex is at a higher resolve level than we need. Emit a degenerate
+ // triangle at T=1.
+ return 1;
+ }
float firstTriangleAtDepth = exp2(resolveLevel - 1);
float triangleIdxWithinDepth = totalTriangleIdx - firstTriangleAtDepth;
float vertexIdxWithinDepth = triangleIdxWithinDepth*2 + float(sk_VertexID % 3);
@@ -78,13 +92,19 @@
} else {
float w = -1; // w < 0 tells us to treat the instance as an integral cubic.
float4x2 P = float4x2(inputPoints_0_1, inputPoints_2_3);
+ float maxResolveLevel;
if (isinf(P[3].y)) {
// The patch is a conic.
w = P[3].x;
+ maxResolveLevel =
+ wangs_formula_conic_log2(PRECISION, AFFINE_MATRIX * float3x2(P), w);
P[3] = P[2]; // Duplicate the endpoint.
P[1] *= w; // Unproject p1.
+ } else {
+ // The patch is an integral cubic.
+ maxResolveLevel = wangs_formula_cubic_log2(PRECISION, P, AFFINE_MATRIX);
}
- float T = find_middle_out_T();
+ float T = find_middle_out_T(maxResolveLevel);
if (0 < T && T < 1) {
// Evaluate at T. Use De Casteljau's for its accuracy and stability.
float2 ab = mix(P[0], P[1], T);