Track min required segments in PatchWriter

Also handles automatically chopping curves to reach the maximum segments
allowed for the PatchWriter.

Change-Id: Iba817e817dd270d170d305c1256cf9aec7b803d2
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/502056
Reviewed-by: Christopher Dalton <csmartdalton@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
This commit is contained in:
Michael Ludwig 2022-02-04 13:06:12 -05:00 committed by SkCQ
parent 43539c22a2
commit 220702dac0
12 changed files with 166 additions and 204 deletions

View File

@ -134,7 +134,10 @@ private:
if (needsInnerFan) {
patchPreallocCount += fPath.countVerbs() - 1;
}
PatchWriter patchWriter(flushState, fTessellator, patchPreallocCount);
PatchWriter patchWriter(flushState,
fTessellator,
tessShader->maxTessellationSegments(*caps.shaderCaps()),
patchPreallocCount);
if (needsInnerFan) {
// Write out inner fan triangles.
@ -149,10 +152,7 @@ private:
}
// Write out the curves.
fTessellator->writePatches(patchWriter,
tessShader->maxTessellationSegments(*caps.shaderCaps()),
shaderMatrix,
{pathMatrix, fPath, kCyan});
fTessellator->writePatches(patchWriter, shaderMatrix, {pathMatrix, fPath, kCyan});
if (!tessShader->willUseTessellationShaders()) {
fTessellator->prepareFixedCountBuffers(flushState);

View File

@ -423,7 +423,9 @@ void PathInnerTriangulateOp::onPrepare(GrOpFlushState* flushState) {
fTessellator->patchPreallocCount(fPath.countVerbs());
SkASSERT(patchPreallocCount); // Otherwise fTessellator should be null.
PatchWriter patchWriter(flushState, fTessellator, patchPreallocCount);
auto tessShader = &fStencilCurvesProgram->geomProc().cast<GrPathTessellationShader>();
int maxSegments = tessShader->maxTessellationSegments(*caps.shaderCaps());
PatchWriter patchWriter(flushState, fTessellator, maxSegments, patchPreallocCount);
// Write out breadcrumb triangles. This must be called after polysToTriangles() in order for
// fFanBreadcrumbs to be complete.
@ -446,9 +448,7 @@ void PathInnerTriangulateOp::onPrepare(GrOpFlushState* flushState) {
SkASSERT(breadcrumbCount == fFanBreadcrumbs.count());
// Write out the curves.
auto tessShader = &fStencilCurvesProgram->geomProc().cast<GrPathTessellationShader>();
fTessellator->writePatches(patchWriter,
tessShader->maxTessellationSegments(*caps.shaderCaps()),
tessShader->viewMatrix(),
{SkMatrix::I(), fPath, SK_PMColor4fTRANSPARENT});

View File

@ -28,20 +28,24 @@ void write_triangle_stack(PatchWriter* writer,
#if SK_GPU_V1
PatchWriter::PatchWriter(GrMeshDrawTarget* target,
PathTessellator* tessellator,
int maxTessellationSegments,
int initialPatchAllocCount)
: PatchWriter(target,
&tessellator->fVertexChunkArray,
tessellator->fAttribs,
maxTessellationSegments,
sizeof(SkPoint) * 4 + PatchAttribsStride(tessellator->fAttribs),
initialPatchAllocCount) {
}
PatchWriter::PatchWriter(GrMeshDrawTarget* target,
StrokeTessellator* tessellator,
int maxTessellationSegments,
int initialPatchAllocCount)
: PatchWriter(target,
&tessellator->fVertexChunkArray,
tessellator->fAttribs,
maxTessellationSegments,
sizeof(SkPoint) * 4 + PatchAttribsStride(tessellator->fAttribs),
initialPatchAllocCount) {
}
@ -60,11 +64,11 @@ void PatchWriter::chopAndWriteQuads(float2 p0, float2 p1, float2 p2, int numPatc
// p1 & p2 of the cubic representation of the middle quad.
float4 middle = mix(ab, bc, mix(T, T.zwxy(), 2/3.f));
this->writeQuadratic(p0, ab.lo, abc.lo); // Write the 1st quad.
this->writeQuadPatch(p0, ab.lo, abc.lo); // Write the 1st quad.
if (needsInnerTriangles) {
this->writeTriangle(p0, abc.lo, abc.hi);
}
this->writeCubic(abc.lo, middle, abc.hi); // Write the 2nd quad already converted to a cubic
this->writeCubicPatch(abc.lo, middle, abc.hi); // Write the 2nd quad (as a cubic already)
if (needsInnerTriangles) {
write_triangle_stack(this, innerTriangulator.pushVertex(to_skpoint(abc.hi)));
}
@ -76,14 +80,14 @@ void PatchWriter::chopAndWriteQuads(float2 p0, float2 p1, float2 p2, int numPatc
float2 bc = (p1 + p2) * .5f;
float2 abc = (ab + bc) * .5f;
this->writeQuadratic(p0, ab, abc); // Write the 1st quad.
this->writeQuadPatch(p0, ab, abc); // Write the 1st quad.
if (needsInnerTriangles) {
this->writeTriangle(p0, abc, p2);
}
this->writeQuadratic(abc, bc, p2); // Write the 2nd quad.
this->writeQuadPatch(abc, bc, p2); // Write the 2nd quad.
} else {
SkASSERT(numPatches == 1);
this->writeQuadratic(p0, p1, p2); // Write the single remaining quad.
this->writeQuadPatch(p0, p1, p2); // Write the single remaining quad.
}
if (needsInnerTriangles) {
write_triangle_stack(this, innerTriangulator.pushVertex(to_skpoint(p2)));
@ -108,10 +112,10 @@ void PatchWriter::chopAndWriteConics(float2 p0, float2 p1, float2 p2, float w, i
// Project and write the 1st conic.
float2 midpoint = abc.xy() / abc.w();
this->writeConic(h0.xy() / h0.w(),
ab.xy() / ab.w(),
midpoint,
ab.w() / sqrtf(h0.w() * abc.w()));
this->writeConicPatch(h0.xy() / h0.w(),
ab.xy() / ab.w(),
midpoint,
ab.w() / sqrtf(h0.w() * abc.w()));
if (needsInnerTriangles) {
write_triangle_stack(this, innerTriangulator.pushVertex(to_skpoint(midpoint)));
}
@ -119,10 +123,10 @@ void PatchWriter::chopAndWriteConics(float2 p0, float2 p1, float2 p2, float w, i
}
// Project and write the remaining conic.
SkASSERT(numPatches == 1);
this->writeConic(h0.xy() / h0.w(),
h1.xy() / h1.w(),
h2.xy(), // h2.w == 1
h1.w() / sqrtf(h0.w()));
this->writeConicPatch(h0.xy() / h0.w(),
h1.xy() / h1.w(),
h2.xy(), // h2.w == 1
h1.w() / sqrtf(h0.w()));
if (needsInnerTriangles) {
write_triangle_stack(this, innerTriangulator.pushVertex(to_skpoint(h2.xy())));
write_triangle_stack(this, innerTriangulator.close());
@ -144,11 +148,11 @@ void PatchWriter::chopAndWriteCubics(float2 p0, float2 p1, float2 p2, float2 p3,
float4 abcd = mix(abc, bcd, T);
float4 middle = mix(abc, bcd, T.zwxy()); // p1 & p2 of the middle cubic.
this->writeCubic(p0, ab.lo, abc.lo, abcd.lo); // Write the 1st cubic.
this->writeCubicPatch(p0, ab.lo, abc.lo, abcd.lo); // Write the 1st cubic.
if (needsInnerTriangles) {
this->writeTriangle(p0, abcd.lo, abcd.hi);
}
this->writeCubic(abcd.lo, middle, abcd.hi); // Write the 2nd cubic.
this->writeCubicPatch(abcd.lo, middle, abcd.hi); // Write the 2nd cubic.
if (needsInnerTriangles) {
write_triangle_stack(this, innerTriangulator.pushVertex(to_skpoint(abcd.hi)));
}
@ -163,14 +167,14 @@ void PatchWriter::chopAndWriteCubics(float2 p0, float2 p1, float2 p2, float2 p3,
float2 bcd = (bc + cd) * .5f;
float2 abcd = (abc + bcd) * .5f;
this->writeCubic(p0, ab, abc, abcd); // Write the 1st cubic.
this->writeCubicPatch(p0, ab, abc, abcd); // Write the 1st cubic.
if (needsInnerTriangles) {
this->writeTriangle(p0, abcd, p3);
}
this->writeCubic(abcd, bcd, cd, p3); // Write the 2nd cubic.
this->writeCubicPatch(abcd, bcd, cd, p3); // Write the 2nd cubic.
} else {
SkASSERT(numPatches == 1);
this->writeCubic(p0, p1, p2, p3); // Write the single remaining cubic.
this->writeCubicPatch(p0, p1, p2, p3); // Write the single remaining cubic.
}
if (needsInnerTriangles) {
write_triangle_stack(this, innerTriangulator.pushVertex(to_skpoint(p3)));

View File

@ -11,6 +11,7 @@
#include "include/private/SkColorData.h"
#include "src/gpu/GrVertexChunkArray.h"
#include "src/gpu/tessellate/Tessellation.h"
#include "src/gpu/tessellate/WangsFormula.h"
#define AI SK_ALWAYS_INLINE
@ -27,16 +28,27 @@ public:
PatchWriter(GrMeshDrawTarget* target,
GrVertexChunkArray* vertexChunkArray,
PatchAttribs attribs,
int maxTessellationSegments,
size_t patchStride,
int initialAllocCount)
: fAttribs(attribs)
, fChunker(target, vertexChunkArray, patchStride, initialAllocCount) {}
, fMaxSegments_pow2(pow2(maxTessellationSegments))
, fMaxSegments_pow4(pow2(fMaxSegments_pow2))
, fChunker(target, vertexChunkArray, patchStride, initialAllocCount) {
// For fans or strokes, the minimum required segment count is 1 (making either a triangle
// with the fan point, or a stroked line). Otherwise, we need 2 segments to represent
// triangles purely from the tessellated vertices.
fCurrMinSegments_pow4 = (attribs & PatchAttribs::kFanPoint ||
attribs & PatchAttribs::kJoinControlPoint) ? 1.f : 16.f; // 2^4
}
#if SK_GPU_V1
// Create PatchWriters that write directly to the GrVertexChunkArrays stored on the provided
// tessellators.
PatchWriter(GrMeshDrawTarget*, PathTessellator*, int initialPatchAllocCount);
PatchWriter(GrMeshDrawTarget*, StrokeTessellator*, int initialPatchAllocCount);
PatchWriter(GrMeshDrawTarget*, PathTessellator*,
int maxTessellationSegments, int initialPatchAllocCount);
PatchWriter(GrMeshDrawTarget*, StrokeTessellator*,
int maxTessellationSegments, int initialPatchAllocCount);
#endif
~PatchWriter() {
@ -46,6 +58,15 @@ public:
PatchAttribs attribs() const { return fAttribs; }
// Fast log2 of minimum required # of segments per tracked Wang's formula calculations.
int requiredResolveLevel() const {
return wangs_formula::nextlog16(fCurrMinSegments_pow4); // log16(n^4) == log2(n)
}
// Fast minimum required # of segments from tracked Wang's formula calculations.
int requiredFixedSegments() const {
return SkScalarCeilToInt(wangs_formula::root4(fCurrMinSegments_pow4));
}
// Updates the stroke's join control point that will be written out with each patch. This is
// automatically adjusted when appending various geometries (e.g. Conic/Cubic), but sometimes
// must be set explicitly.
@ -117,52 +138,62 @@ public:
*/
// Write a cubic curve with its four control points.
AI void writeCubic(float2 p0, float2 p1, float2 p2, float2 p3) {
// TODO: Have cubic store or automatically compute wang's formula so this can automatically
// call into chopAndWriteCubics.
this->writePatch(p0, p1, p2, p3, kCubicCurveType);
AI void writeCubic(float2 p0, float2 p1, float2 p2, float2 p3, float n4) {
if (this->updateRequiredSegments(n4)) {
this->writeCubicPatch(p0, p1, p2, p3);
} else {
int numPatches = SkScalarCeilToInt(wangs_formula::root4(
std::min(n4, pow4(kMaxTessellationSegmentsPerCurve)) / fMaxSegments_pow4));
this->chopAndWriteCubics(p0, p1, p2, p3, numPatches);
}
}
AI void writeCubic(float4 p0p1, float4 p2p3) {
this->writeCubic(p0p1.lo, p0p1.hi, p2p3.lo, p2p3.hi);
}
AI void writeCubic(float2 p0, float4 p1p2, float2 p3) {
this->writeCubic(p0, p1p2.lo, p1p2.hi, p3);
}
AI void writeCubic(const SkPoint pts[4]) {
this->writeCubic(float4::Load(pts), float4::Load(pts + 2));
AI void writeCubic(const SkPoint pts[4], float n4) {
float4 p0p1 = float4::Load(pts);
float4 p2p3 = float4::Load(pts + 2);
this->writeCubic(p0p1.lo, p0p1.hi, p2p3.lo, p2p3.hi, n4);
}
// Write a conic curve with three control points and 'w', with the last coord of the last
// control point signaling a conic by being set to infinity.
AI void writeConic(float2 p0, float2 p1, float2 p2, float w) {
// TODO: Have Conic store or automatically compute Wang's formula so this can automatically
// call into chopAndWriteConics.
this->writePatch(p0, p1, p2, {w, SK_FloatInfinity}, kConicCurveType);
AI void writeConic(float2 p0, float2 p1, float2 p2, float w, float n2) {
if (this->updateRequiredSegments(n2*n2)) {
this->writeConicPatch(p0, p1, p2, w);
} else {
int numPatches = SkScalarCeilToInt(sqrtf(
std::min(n2, pow2(kMaxTessellationSegmentsPerCurve)) / fMaxSegments_pow2));
this->chopAndWriteConics(p0, p1, p2, w, numPatches);
}
}
AI void writeConic(const SkPoint pts[3], float w) {
AI void writeConic(const SkPoint pts[3], float w, float n2) {
this->writeConic(skvx::bit_pun<float2>(pts[0]),
skvx::bit_pun<float2>(pts[1]),
skvx::bit_pun<float2>(pts[2]),
w);
w, n2);
}
// Write a quadratic curve that automatically converts its three control points into an
// equivalent cubic.
AI void writeQuadratic(float2 p0, float2 p1, float2 p2) {
// TODO: Have Quadratic store or automatically compute Wang's formula so this can
// automatically call into chopAndWriteQuadratics *before* it is converted to an equivalent
// cubic if needed.
this->writeCubic(p0, mix(float4(p0, p2), p1.xyxy(), 2/3.f), p2);
AI void writeQuadratic(float2 p0, float2 p1, float2 p2, float n4) {
if (this->updateRequiredSegments(n4)) {
this->writeQuadPatch(p0, p1, p2);
} else {
int numPatches = SkScalarCeilToInt(wangs_formula::root4(
std::min(n4, pow4(kMaxTessellationSegmentsPerCurve)) / fMaxSegments_pow4));
this->chopAndWriteQuads(p0, p1, p2, numPatches);
}
}
AI void writeQuadratic(const SkPoint pts[3]) {
AI void writeQuadratic(const SkPoint pts[3], float n4) {
this->writeQuadratic(skvx::bit_pun<float2>(pts[0]),
skvx::bit_pun<float2>(pts[1]),
skvx::bit_pun<float2>(pts[2]));
skvx::bit_pun<float2>(pts[2]),
n4);
}
// Write a line that is automatically converted into an equivalent cubic.
AI void writeLine(float4 p0p1) {
this->writeCubic(p0p1.lo, (p0p1.zwxy() - p0p1) * (1/3.f) + p0p1, p0p1.hi);
// No chopping needed, and should have been reset to 1 segment if using writeLine
SkASSERT(fCurrMinSegments_pow4 >= 1.f);
this->writeCubicPatch(p0p1.lo, (p0p1.zwxy() - p0p1) * (1/3.f) + p0p1, p0p1.hi);
}
AI void writeLine(float2 p0, float2 p1) { this->writeLine({p0, p1}); }
AI void writeLine(SkPoint p0, SkPoint p1) {
@ -172,6 +203,8 @@ public:
// Write a triangle by setting it to a conic with w=Inf, and using a distinct
// explicit curve type for when inf isn't supported in shaders.
AI void writeTriangle(float2 p0, float2 p1, float2 p2) {
// No chopping needed, and should have been reset to 2 segments if using writeTriangle.
SkASSERT(fCurrMinSegments_pow4 >= (2*2*2*2));
this->writePatch(p0, p1, p2, {SK_FloatInfinity, SK_FloatInfinity},
kTriangularConicCurveType);
}
@ -193,31 +226,13 @@ public:
}
}
// Chops the given quadratic into 'numPatches' equal segments (in the parametric sense) and
// writes them to the GPU buffer.
//
// Fills space between chops with triangles if PathPatchAttrib::kFanPoint is not enabled.
void chopAndWriteQuads(float2 p0, float2 p1, float2 p2, int numPatches);
// Chops the given conic into 'numPatches' equal segments (in the parametric sense) and
// writes them to the GPU buffer.
//
// Fills space between chops with triangles if PathPatchAttrib::kFanPoint is not enabled.
void chopAndWriteConics(float2 p0, float2 p1, float2 p2, float w, int numPatches);
// Chops the given cubic into 'numPatches' equal segments (in the parametric sense) and
// writes them to the GPU buffer.
//
// Fills space between chops with triangles if PathPatchAttrib::kFanPoint is not enabled.
void chopAndWriteCubics(float2 p0, float2 p1, float2 p2, float2 p3, int numPatches);
private:
template <typename T>
static VertexWriter::Conditional<T> If(bool c, const T& v) { return VertexWriter::If(c,v); }
void emitPatchAttribs(VertexWriter vertexWriter,
SkPoint joinControlPoint,
float explicitCurveType) {
AI void emitPatchAttribs(VertexWriter vertexWriter,
SkPoint joinControlPoint,
float explicitCurveType) {
vertexWriter << If((fAttribs & PatchAttribs::kJoinControlPoint), joinControlPoint)
<< If((fAttribs & PatchAttribs::kFanPoint), fFanPointAttrib)
<< If((fAttribs & PatchAttribs::kStrokeParams), fStrokeParamsAttrib)
@ -225,8 +240,7 @@ private:
<< If((fAttribs & PatchAttribs::kExplicitCurveType), explicitCurveType);
}
SK_ALWAYS_INLINE
void writePatch(float2 p0, float2 p1, float2 p2, float2 p3, float explicitCurveType) {
AI void writePatch(float2 p0, float2 p1, float2 p2, float2 p3, float explicitCurveType) {
const bool defer = (fAttribs & PatchAttribs::kJoinControlPoint) &&
!fHasJoinControlPoint;
@ -259,8 +273,44 @@ private:
}
}
}
// Helpers that normalize curves to a generic patch, but does no other work.
AI void writeCubicPatch(float2 p0, float2 p1, float2 p2, float2 p3) {
this->writePatch(p0, p1, p2, p3, kCubicCurveType);
}
AI void writeCubicPatch(float2 p0, float4 p1p2, float2 p3) {
this->writeCubicPatch(p0, p1p2.lo, p1p2.hi, p3);
}
AI void writeQuadPatch(float2 p0, float2 p1, float2 p2) {
this->writeCubicPatch(p0, mix(float4(p0, p2), p1.xyxy(), 2/3.f), p2);
}
AI void writeConicPatch(float2 p0, float2 p1, float2 p2, float w) {
this->writePatch(p0, p1, p2, {w, SK_FloatInfinity}, kConicCurveType);
}
// Helpers that chop the curve type into 'numPatches' parametrically uniform curves. It is
// assumed that 'numPatches' is calculated such that the resulting curves require the maximum
// number of segments to draw appropriately (since the original presumably needed even more).
void chopAndWriteQuads(float2 p0, float2 p1, float2 p2, int numPatches);
void chopAndWriteConics(float2 p0, float2 p1, float2 p2, float w, int numPatches);
void chopAndWriteCubics(float2 p0, float2 p1, float2 p2, float2 p3, int numPatches);
// Returns true if curve can be written w/o needing to chop
bool updateRequiredSegments(float n4) {
if (n4 <= fMaxSegments_pow4) {
fCurrMinSegments_pow4 = std::max(n4, fCurrMinSegments_pow4);
return true;
} else {
fCurrMinSegments_pow4 = fMaxSegments_pow4;
return false;
}
}
const PatchAttribs fAttribs;
const float fMaxSegments_pow2;
const float fMaxSegments_pow4;
float fCurrMinSegments_pow4;
GrVertexChunkBuilder fChunker;
SkPoint fJoinControlPointAttrib;
@ -268,8 +318,6 @@ private:
StrokeParams fStrokeParamsAttrib;
VertexColor fColorAttrib;
bool fHasJoinControlPoint = false;
static constexpr size_t kMaxStride =
4 * sizeof(SkPoint) + // control points
sizeof(SkPoint) + // join control point or fan attrib point (not used at same time)
@ -281,6 +329,8 @@ private:
// Contents are valid (sans join control point) if fHasDeferredPatch is true.
char fDeferredPatchStorage[kMaxStride];
bool fHasDeferredPatch = false;
bool fHasJoinControlPoint = false;
};
} // namespace skgpu

View File

@ -29,16 +29,8 @@ int PathCurveTessellator::patchPreallocCount(int totalCombinedPathVerbCnt) const
}
void PathCurveTessellator::writePatches(PatchWriter& patchWriter,
int maxTessellationSegments,
const SkMatrix& shaderMatrix,
const PathDrawList& pathDrawList) {
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
// emit at least 2 segments so we can support triangles.
float numFixedSegments_pow4 = 2*2*2*2;
for (auto [pathMatrix, path, color] : pathDrawList) {
AffineMatrix m(pathMatrix);
wangs_formula::VectorXform totalXform(SkMatrix::Concat(shaderMatrix, pathMatrix));
@ -56,19 +48,8 @@ void PathCurveTessellator::writePatches(PatchWriter& patchWriter,
if (n4 <= 1) {
break; // This quad only needs 1 segment, which is empty.
}
if (n4 <= maxSegments_pow4) {
// This quad already fits in "maxTessellationSegments".
patchWriter.writeQuadratic(p0, p1, p2);
} else {
// The path should have been pre-chopped if needed, so all curves fit in
// kMaxTessellationSegmentsPerCurve.
n4 = std::min(n4, pow4(kMaxTessellationSegmentsPerCurve));
// Chop until each quad tessellation requires "maxSegments" or fewer.
int numPatches =
SkScalarCeilToInt(wangs_formula::root4(n4/maxSegments_pow4));
patchWriter.chopAndWriteQuads(p0, p1, p2, numPatches);
}
numFixedSegments_pow4 = std::max(n4, numFixedSegments_pow4);
patchWriter.writeQuadratic(p0, p1, p2, n4);
break;
}
@ -82,18 +63,8 @@ void PathCurveTessellator::writePatches(PatchWriter& patchWriter,
if (n2 <= 1) {
break; // This conic only needs 1 segment, which is empty.
}
if (n2 <= maxSegments_pow2) {
// This conic already fits in "maxTessellationSegments".
patchWriter.writeConic(p0, p1, p2, *w);
} else {
// The path should have been pre-chopped if needed, so all curves fit in
// kMaxTessellationSegmentsPerCurve.
n2 = std::min(n2, pow2(kMaxTessellationSegmentsPerCurve));
// Chop until each conic tessellation requires "maxSegments" or fewer.
int numPatches = SkScalarCeilToInt(sqrtf(n2/maxSegments_pow2));
patchWriter.chopAndWriteConics(p0, p1, p2, *w, numPatches);
}
numFixedSegments_pow4 = std::max(n2*n2, numFixedSegments_pow4);
patchWriter.writeConic(p0, p1, p2, *w, n2);
break;
}
@ -106,19 +77,8 @@ void PathCurveTessellator::writePatches(PatchWriter& patchWriter,
if (n4 <= 1) {
break; // This cubic only needs 1 segment, which is empty.
}
if (n4 <= maxSegments_pow4) {
// This cubic already fits in "maxTessellationSegments".
patchWriter.writeCubic(p0, p1, p2, p3);
} else {
// The path should have been pre-chopped if needed, so all curves fit in
// kMaxTessellationSegmentsPerCurve.
n4 = std::min(n4, pow4(kMaxTessellationSegmentsPerCurve));
// Chop until each cubic tessellation requires "maxSegments" or fewer.
int numPatches =
SkScalarCeilToInt(wangs_formula::root4(n4/maxSegments_pow4));
patchWriter.chopAndWriteCubics(p0, p1, p2, p3, numPatches);
}
numFixedSegments_pow4 = std::max(n4, numFixedSegments_pow4);
patchWriter.writeCubic(p0, p1, p2, p3, n4);
break;
}
@ -127,10 +87,9 @@ void PathCurveTessellator::writePatches(PatchWriter& patchWriter,
}
}
// log16(n^4) == log2(n).
// We already chopped curves to make sure none needed a higher resolveLevel than
// kMaxFixedResolveLevel.
fFixedResolveLevel = SkTPin(wangs_formula::nextlog16(numFixedSegments_pow4),
fFixedResolveLevel = SkTPin(patchWriter.requiredResolveLevel(),
fFixedResolveLevel,
int(kMaxFixedResolveLevel));
}

View File

@ -31,7 +31,6 @@ public:
int patchPreallocCount(int totalCombinedPathVerbCnt) const final;
void writePatches(PatchWriter& patchWriter,
int maxTessellationSegments,
const SkMatrix& shaderMatrix,
const PathDrawList& pathDrawList) final;

View File

@ -73,7 +73,6 @@ public:
// 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 writePatches(PatchWriter&,
int maxTessellationSegments,
const SkMatrix& shaderMatrix,
const PathDrawList&) = 0;
@ -89,8 +88,8 @@ public:
int totalCombinedPathVerbCnt,
bool willUseTessellationShaders) {
if (int patchPreallocCount = this->patchPreallocCount(totalCombinedPathVerbCnt)) {
PatchWriter patchWriter(target, this, patchPreallocCount);
this->writePatches(patchWriter, maxTessellationSegments, shaderMatrix, pathDrawList);
PatchWriter patchWriter(target, this, maxTessellationSegments, patchPreallocCount);
this->writePatches(patchWriter, shaderMatrix, pathDrawList);
}
if (!willUseTessellationShaders) {
this->prepareFixedCountBuffers(target);

View File

@ -131,16 +131,8 @@ int PathWedgeTessellator::patchPreallocCount(int totalCombinedPathVerbCnt) const
}
void PathWedgeTessellator::writePatches(PatchWriter& patchWriter,
int maxTessellationSegments,
const SkMatrix& shaderMatrix,
const PathDrawList& pathDrawList) {
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
// emit at least 1 segment.
float numFixedSegments_pow4 = 1;
for (auto [pathMatrix, path, color] : pathDrawList) {
AffineMatrix m(pathMatrix);
wangs_formula::VectorXform totalXform(SkMatrix::Concat(shaderMatrix, pathMatrix));
@ -173,19 +165,8 @@ void PathWedgeTessellator::writePatches(PatchWriter& patchWriter,
float n4 = wangs_formula::quadratic_pow4(kTessellationPrecision,
pts,
totalXform);
if (n4 <= maxSegments_pow4) {
// This quad already fits in "maxTessellationSegments".
patchWriter.writeQuadratic(p0, p1, p2);
} else {
// The path should have been pre-chopped if needed, so all curves fit in
// kMaxTessellationSegmentsPerCurve.
n4 = std::min(n4, pow4(kMaxTessellationSegmentsPerCurve));
// Chop until each quad tessellation requires "maxSegments" or fewer.
int numPatches =
SkScalarCeilToInt(wangs_formula::root4(n4/maxSegments_pow4));
patchWriter.chopAndWriteQuads(p0, p1, p2, numPatches);
}
numFixedSegments_pow4 = std::max(n4, numFixedSegments_pow4);
patchWriter.writeQuadratic(p0, p1, p2, n4);
lastPoint = pts[2];
break;
}
@ -197,18 +178,8 @@ void PathWedgeTessellator::writePatches(PatchWriter& patchWriter,
pts,
*w,
totalXform);
if (n2 <= maxSegments_pow2) {
// This conic already fits in "maxTessellationSegments".
patchWriter.writeConic(p0, p1, p2, *w);
} else {
// The path should have been pre-chopped if needed, so all curves fit in
// kMaxTessellationSegmentsPerCurve.
n2 = std::min(n2, pow2(kMaxTessellationSegmentsPerCurve));
// Chop until each conic tessellation requires "maxSegments" or fewer.
int numPatches = SkScalarCeilToInt(sqrtf(n2/maxSegments_pow2));
patchWriter.chopAndWriteConics(p0, p1, p2, *w, numPatches);
}
numFixedSegments_pow4 = std::max(n2*n2, numFixedSegments_pow4);
patchWriter.writeConic(p0, p1, p2, *w, n2);
lastPoint = pts[2];
break;
}
@ -219,19 +190,8 @@ void PathWedgeTessellator::writePatches(PatchWriter& patchWriter,
float n4 = wangs_formula::cubic_pow4(kTessellationPrecision,
pts,
totalXform);
if (n4 <= maxSegments_pow4) {
// This cubic already fits in "maxTessellationSegments".
patchWriter.writeCubic(p0, p1, p2, p3);
} else {
// The path should have been pre-chopped if needed, so all curves fit in
// kMaxTessellationSegmentsPerCurve.
n4 = std::min(n4, pow4(kMaxTessellationSegmentsPerCurve));
// Chop until each cubic tessellation requires "maxSegments" or fewer.
int numPatches =
SkScalarCeilToInt(wangs_formula::root4(n4/maxSegments_pow4));
patchWriter.chopAndWriteCubics(p0, p1, p2, p3, numPatches);
}
numFixedSegments_pow4 = std::max(n4, numFixedSegments_pow4);
patchWriter.writeCubic(p0, p1, p2, p3, n4);
lastPoint = pts[3];
break;
}
@ -248,10 +208,9 @@ void PathWedgeTessellator::writePatches(PatchWriter& patchWriter,
}
}
// log16(n^4) == log2(n).
// We already chopped curves to make sure none needed a higher resolveLevel than
// kMaxFixedResolveLevel.
fFixedResolveLevel = SkTPin(wangs_formula::nextlog16(numFixedSegments_pow4),
fFixedResolveLevel = SkTPin(patchWriter.requiredResolveLevel(),
fFixedResolveLevel,
int(kMaxFixedResolveLevel));
}

View File

@ -34,7 +34,6 @@ public:
int patchPreallocCount(int totalCombinedPathVerbCnt) const final;
void writePatches(PatchWriter&,
int maxTessellationSegments,
const SkMatrix& shaderMatrix,
const PathDrawList&) final;

View File

@ -23,7 +23,7 @@ namespace skgpu {
namespace {
constexpr static float kMaxParametricSegments_pow4 =
StrokeFixedCountTessellator::kMaxParametricSegments_pow4;
pow4(StrokeFixedCountTessellator::kMaxParametricSegments);
// Writes out strokes to the given instance chunk array, chopping if necessary so that all instances
// require 32 parametric segments or less. (We don't consider radial segments here. The tessellator
@ -38,10 +38,6 @@ public:
float parametricPrecision() const { return fParametricPrecision; }
// maxParametricSegments^4, or the number of parametric segments, raised to the 4th power,
// that are required by the single instance we've written that requires the most segments.
float maxParametricSegments_pow4() const { return fMaxParametricSegments_pow4; }
SK_ALWAYS_INLINE void lineTo(SkPoint start, SkPoint end) {
fPatchWriter.writeLine(start, end);
}
@ -53,9 +49,7 @@ public:
return;
}
fPatchWriter.writeQuadratic(p);
fMaxParametricSegments_pow4 = std::max(numParametricSegments_pow4,
fMaxParametricSegments_pow4);
fPatchWriter.writeQuadratic(p, numParametricSegments_pow4);
}
SK_ALWAYS_INLINE void conicTo(const SkPoint p[3], float w) {
@ -65,9 +59,7 @@ public:
this->chopConicTo({p, w});
return;
}
fPatchWriter.writeConic(p, w);
fMaxParametricSegments_pow4 = std::max(numParametricSegments_pow4,
fMaxParametricSegments_pow4);
fPatchWriter.writeConic(p, w, n2);
}
SK_ALWAYS_INLINE void cubicConvex180To(const SkPoint p[4]) {
@ -76,9 +68,7 @@ public:
this->chopCubicConvex180To(p);
return;
}
fPatchWriter.writeCubic(p);
fMaxParametricSegments_pow4 = std::max(numParametricSegments_pow4,
fMaxParametricSegments_pow4);
fPatchWriter.writeCubic(p, numParametricSegments_pow4);
}
// Called when we encounter the verb "kMoveWithinContour". Moves invalidate the previous control
@ -123,7 +113,6 @@ private:
PatchWriter& fPatchWriter;
const float fParametricPrecision;
float fMaxParametricSegments_pow4 = 1;
};
// Returns the worst-case number of edges we will need in order to draw a join of the given type.
@ -276,9 +265,8 @@ int StrokeFixedCountTessellator::writePatches(PatchWriter& patchWriter,
int maxRadialSegmentsInStroke =
std::max(SkScalarCeilToInt(maxRadialSegmentsPerRadian * SK_ScalarPI), 1);
int maxParametricSegmentsInStroke = SkScalarCeilToInt(sqrtf(sqrtf(
instanceWriter.maxParametricSegments_pow4())));
SkASSERT(maxParametricSegmentsInStroke >= 1); // maxParametricSegments_pow4 is always >= 1.
int maxParametricSegmentsInStroke = patchWriter.requiredFixedSegments();
SkASSERT(maxParametricSegmentsInStroke >= 1);
// Now calculate the maximum number of edges we will need in the stroke portion of the instance.
// The first and last edges in a stroke are shared by both the parametric and radial sets of
@ -322,7 +310,11 @@ int StrokeFixedCountTessellator::prepare(GrMeshDrawTarget* target,
std::array<float,2> matrixMinMaxScales,
PathStrokeList* pathStrokeList,
int totalCombinedStrokeVerbCnt) {
PatchWriter patchWriter(target, this, this->patchPreallocCount(totalCombinedStrokeVerbCnt));
// NOTE: For now InstanceWriter manually chops curves that exceed kMaxParametricSegments_pow4,
// so passing in kMaxParametricSegments to PatchWriter avoids its auto-chopping while still
// correctly accumulating the min required segment count.
PatchWriter patchWriter(target, this, kMaxParametricSegments,
this->patchPreallocCount(totalCombinedStrokeVerbCnt));
fFixedEdgeCount = this->writePatches(patchWriter,
shaderMatrix,

View File

@ -20,7 +20,7 @@ namespace skgpu {
// instance are emitted as degenerate triangles.
class StrokeFixedCountTessellator final : public StrokeTessellator {
public:
constexpr static float kMaxParametricSegments_pow4 = 32*32*32*32; // 32^4
constexpr static int kMaxParametricSegments = 32;
constexpr static int8_t kMaxParametricSegments_log2 = 5; // log2(32)
StrokeFixedCountTessellator(PatchAttribs attribs) : StrokeTessellator(attribs) {}

View File

@ -231,7 +231,7 @@ public:
// tessellation does not require explicit curve types or automatic CPU chopping and
// deferring, PatchWriter::writeCubic() patch type is sufficient, although 'p' may actually
// contain data causing it to be interpreted as something other than a cubic in the shader.
fPatchWriter.writeCubic(p);
fPatchWriter.writeCubic(p, 0.f);
fPatchWriter.updateJoinControlPointAttrib(endControlPoint);
}
@ -589,7 +589,7 @@ private:
}
asPatch[3] = nextControlPoint;
fPatchWriter.writeCubic(asPatch); // not really a cubic, see note in writePatchTo()
fPatchWriter.writeCubic(asPatch, 0.f); // not really a cubic, see note in writePatchTo()
}
fPatchWriter.updateJoinControlPointAttrib(nextControlPoint);
@ -860,7 +860,8 @@ int StrokeHardwareTessellator::prepare(GrMeshDrawTarget* target,
std::array<float,2> matrixMinMaxScales,
PathStrokeList* pathStrokeList,
int totalCombinedStrokeVerbCnt) {
PatchWriter patchWriter(target, this, this->patchPreallocCount(totalCombinedStrokeVerbCnt));
PatchWriter patchWriter(target, this, 0.f, /* unused max segment tracking */
this->patchPreallocCount(totalCombinedStrokeVerbCnt));
return this->writePatches(patchWriter, shaderMatrix, matrixMinMaxScales, pathStrokeList);
}