Move last join control point storage into PatchWriter

Change-Id: I916c23778e04911ea122720b8e48850caab4df64
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/501437
Reviewed-by: John Stiles <johnstiles@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
This commit is contained in:
Michael Ludwig 2022-02-01 10:47:40 -05:00 committed by SkCQ
parent ff94e373a3
commit 1f1270f8ac
7 changed files with 96 additions and 78 deletions

View File

@ -33,7 +33,7 @@ PatchWriter::PatchWriter(GrMeshDrawTarget* target,
: PatchWriter(target,
&tessellator->fVertexChunkArray,
tessellator->fAttribs,
sizeof(SkPoint) * 5 + PatchAttribsStride(tessellator->fAttribs),
sizeof(SkPoint) * 4 + PatchAttribsStride(tessellator->fAttribs),
initialPatchAllocCount) {
}
#endif

View File

@ -41,23 +41,46 @@ public:
PatchAttribs attribs() const { return fAttribs; }
// 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.
//
// PatchAttribs::kJoinControlPoint must be enabled.
void updateJoinControlPointAttrib(SkPoint lastControlPoint) {
SkASSERT(fAttribs & PatchAttribs::kJoinControlPoint);
fJoinControlPointAttrib = lastControlPoint;
fHasJoinControlPoint = true;
}
// Completes a closed contour of a stroke by rewriting a deferred patch with the now-available
// join control point information defined by the last verb of the contour (now already written).
//
// PatchAttribs::kJoinControlPoint must be enabled.
void writeDeferredStrokePatch() {
SkASSERT(fAttribs & PatchAttribs::kJoinControlPoint);
// TODO: This will also handle writing a deferred patch when that is moved into PatchWriter
fHasJoinControlPoint = false;
}
// TODO: This is only used while migrating from InstanceWriter
const SkPoint& joinControlPoint() const { return fJoinControlPointAttrib; }
bool hasJoinControlPoint() const { return fHasJoinControlPoint; }
// Updates the fan point that will be written out with each patch (i.e., the point that wedges
// fan around).
// PathPatchAttrib::kFanPoint must be enabled.
// PatchAttribs::kFanPoint must be enabled.
void updateFanPointAttrib(SkPoint fanPoint) {
SkASSERT(fAttribs & PatchAttribs::kFanPoint);
fFanPointAttrib = fanPoint;
}
// Updates the stroke params that are written out with each patch.
// PathPatchAttrib::kStrokeParams must be enabled.
// PatchAttribs::kStrokeParams must be enabled.
void updateStrokeParamsAttrib(StrokeParams strokeParams) {
SkASSERT(fAttribs & PatchAttribs::kStrokeParams);
fStrokeParamsAttrib = strokeParams;
}
// Updates the color that will be written out with each patch.
// PathPatchAttrib::kColor must be enabled.
// PatchAttribs::kColor must be enabled.
void updateColorAttrib(const SkPMColor4f& color) {
SkASSERT(fAttribs & PatchAttribs::kColor);
fColorAttrib.set(color, fAttribs & PatchAttribs::kWideColorIfEnabled);
@ -151,18 +174,27 @@ private:
static VertexWriter::Conditional<T> If(bool c, const T& v) { return VertexWriter::If(c,v); }
void emitPatchAttribs(VertexWriter vertexWriter, float explicitCurveType) {
vertexWriter << If((fAttribs & PatchAttribs::kFanPoint), fFanPointAttrib)
// TODO: For now, the join control point must be explicitly provided by caller *before*
// they write any patch, and are responsible for deferring data on their own. This assert
// will relax when PatchWriter automates the deferring.
SkASSERT(!(fAttribs & PatchAttribs::kJoinControlPoint) ||
fJoinControlPointAttrib.isFinite());
vertexWriter << If((fAttribs & PatchAttribs::kJoinControlPoint), fJoinControlPointAttrib)
<< If((fAttribs & PatchAttribs::kFanPoint), fFanPointAttrib)
<< If((fAttribs & PatchAttribs::kStrokeParams), fStrokeParamsAttrib)
<< If((fAttribs & PatchAttribs::kColor), fColorAttrib)
<< If((fAttribs & PatchAttribs::kExplicitCurveType), explicitCurveType);
}
const PatchAttribs fAttribs;
GrVertexChunkBuilder fChunker;
SkPoint fJoinControlPointAttrib;
SkPoint fFanPointAttrib;
StrokeParams fStrokeParamsAttrib;
VertexColor fColorAttrib;
GrVertexChunkBuilder fChunker;
bool fHasJoinControlPoint = false;
// For when fChunker fails to allocate a patch in GPU memory.
SkAutoTMalloc<char> fFallbackPatchStorage;

View File

@ -33,6 +33,8 @@ public:
InstanceWriter(PatchWriter& patchWriter, float matrixMaxScale)
: fPatchWriter(patchWriter)
, fParametricPrecision(StrokeTolerances::CalcParametricPrecision(matrixMaxScale)) {
SkASSERT(fPatchWriter.attribs() & PatchAttribs::kJoinControlPoint);
SkASSERT(!fPatchWriter.hasJoinControlPoint());
}
float parametricPrecision() const { return fParametricPrecision; }
@ -90,8 +92,7 @@ public:
// Called when we encounter the verb "kMoveWithinContour". Moves invalidate the previous control
// point. The stroke iterator tells us the new value to use for the previous control point.
void setLastControlPoint(SkPoint newLastControlPoint) {
fLastControlPoint = newLastControlPoint;
fHasLastControlPoint = true;
fPatchWriter.updateJoinControlPointAttrib(newLastControlPoint);
}
// Draws a circle whose diameter is equal to the stroke width. We emit circles at cusp points
@ -99,19 +100,24 @@ public:
void writeCircle(SkPoint location) {
// The shader interprets an empty stroke + empty join as a special case that denotes a
// circle, or 180-degree point stroke.
PatchWriter::CubicPatch(fPatchWriter) << VertexWriter::Repeat<5>(location);
// TODO: This is a little awkward right now, but will be simplified as more of
// InstanceWriter is pulled into PatchWriter.
SkPoint joinPoint = fPatchWriter.joinControlPoint();
fPatchWriter.updateJoinControlPointAttrib(location);
PatchWriter::CubicPatch(fPatchWriter) << VertexWriter::Repeat<4>(location);
fPatchWriter.updateJoinControlPointAttrib(joinPoint);
}
void finishContour() {
if (fHasDeferredFirstStroke) {
// We deferred the first stroke because we didn't know the previous control point to use
// for its join. We write it out now.
SkASSERT(fHasLastControlPoint);
SkASSERT(fPatchWriter.hasJoinControlPoint());
this->writeStroke(fDeferredFirstStroke, SkPoint(),
fDeferredCurveTypeIfUnsupportedInfinity);
fHasDeferredFirstStroke = false;
}
fHasLastControlPoint = false;
fPatchWriter.writeDeferredStrokePatch();
}
private:
@ -140,25 +146,17 @@ private:
SK_ALWAYS_INLINE void writeStroke(const SkPoint p[4], SkPoint endControlPoint,
float curveTypeIfUnsupportedInfinity) {
if (fHasLastControlPoint) {
if (fPatchWriter.hasJoinControlPoint()) {
PatchWriter::Patch(fPatchWriter, curveTypeIfUnsupportedInfinity)
<< VertexWriter::Array(p, 4) << fLastControlPoint;
<< VertexWriter::Array(p, 4);
} else {
// We don't know the previous control point yet to use for the join. Defer writing out
// this stroke until the end.
memcpy(fDeferredFirstStroke, p, sizeof(fDeferredFirstStroke));
fDeferredCurveTypeIfUnsupportedInfinity = curveTypeIfUnsupportedInfinity;
fHasDeferredFirstStroke = true;
fHasLastControlPoint = true;
}
fLastControlPoint = endControlPoint;
}
void discardStroke(const SkPoint p[], int numPts) {
// Set fLastControlPoint to the next stroke's p0 (which will be equal to the final point of
// this stroke). This has the effect of disabling the next stroke's join.
fLastControlPoint = p[numPts - 1];
fHasLastControlPoint = true;
fPatchWriter.updateJoinControlPointAttrib(endControlPoint);
}
PatchWriter& fPatchWriter;
@ -168,9 +166,7 @@ private:
// We can't write out the first stroke until we know the previous control point for its join.
SkPoint fDeferredFirstStroke[4];
float fDeferredCurveTypeIfUnsupportedInfinity;
SkPoint fLastControlPoint; // Used to configure the joins in the instance data.
bool fHasDeferredFirstStroke = false;
bool fHasLastControlPoint = false;
};
// Returns the worst-case number of edges we will need in order to draw a join of the given type.

View File

@ -60,6 +60,8 @@ public:
// each chop has the potential to introduce an extra segment.
, fMaxTessellationSegments(std::max(maxTessellationSegments - 2, 1))
, fParametricPrecision(StrokeTolerances::CalcParametricPrecision(matrixMaxScale)) {
SkASSERT(fPatchWriter.attribs() & PatchAttribs::kJoinControlPoint);
SkASSERT(!fPatchWriter.hasJoinControlPoint());
}
// This is the precision value, adjusted for the view matrix, to use with Wang's formulas when
@ -132,7 +134,9 @@ public:
void moveTo(SkPoint pt) {
fCurrContourStartPoint = pt;
fHasLastControlPoint = false;
// Either the first contour, or the prior strokes should have had verbs explicitly writing
// caps, which resets the join control point.
SkASSERT(!fPatchWriter.hasJoinControlPoint());
}
// Writes out the given line, possibly chopping its previous join until the segments fit in
@ -197,28 +201,29 @@ public:
SkPoint endControlPoint) {
SkASSERT(fStrokeJoinType != JoinType::kBowtie);
if (!fHasLastControlPoint) {
if (!fPatchWriter.hasJoinControlPoint()) {
// The first stroke doesn't have a previous join (yet). If the current contour ends up
// closing itself, we will add that join as its own patch. TODO: Consider deferring the
// first stroke until we know whether the contour will close. This will allow us to use
// the closing join as the first patch's previous join.
fHasLastControlPoint = true;
fCurrContourFirstControlPoint = (p[1] != p[0]) ? p[1] : p[2];
fLastControlPoint = p[0]; // Disables the join section of this patch.
// Disables the join section of this patch.
fPatchWriter.updateJoinControlPointAttrib(p[0]);
} else if (!prevJoinFitsInPatch) {
// There aren't enough guaranteed segments to fold the previous join into this patch.
// Emit the join in its own separate patch.
this->internalJoinTo(fStrokeJoinType, p[0], (p[1] != p[0]) ? p[1] : p[2]);
fLastControlPoint = p[0]; // Disables the join section of this patch.
// Disables the join section of this patch.
fPatchWriter.updateJoinControlPointAttrib(p[0]);
}
HwPatch(fPatchWriter) << fLastControlPoint << VertexWriter::Array(p, 4);
fLastControlPoint = endControlPoint;
HwPatch(fPatchWriter) << VertexWriter::Array(p, 4);
fPatchWriter.updateJoinControlPointAttrib(endControlPoint);
}
void writeClose(SkPoint contourEndpoint, const SkMatrix& viewMatrix,
const SkStrokeRec& stroke) {
if (!fHasLastControlPoint) {
if (!fPatchWriter.hasJoinControlPoint()) {
// Draw caps instead of closing if the subpath is zero length:
//
// "Any zero length subpath ... shall be stroked if the 'stroke-linecap' property has
@ -234,12 +239,11 @@ public:
// contourEndpoint == fCurrContourStartPoint.)
this->writeLineTo(contourEndpoint, fCurrContourStartPoint);
this->internalJoinTo(fStrokeJoinType, fCurrContourStartPoint, fCurrContourFirstControlPoint);
fHasLastControlPoint = false;
fPatchWriter.writeDeferredStrokePatch();
}
void writeCaps(SkPoint contourEndpoint, const SkMatrix& viewMatrix, const SkStrokeRec& stroke) {
if (!fHasLastControlPoint) {
if (!fPatchWriter.hasJoinControlPoint()) {
// We don't have any control points to orient the caps. In this case, square and round
// caps are specified to be drawn as an axis-aligned square or circle respectively.
// Assign default control points that achieve this.
@ -269,9 +273,8 @@ public:
outset = {d, -c};
}
fCurrContourFirstControlPoint = fCurrContourStartPoint - outset;
fLastControlPoint = fCurrContourStartPoint + outset;
fHasLastControlPoint = true;
contourEndpoint = fCurrContourStartPoint;
fPatchWriter.updateJoinControlPointAttrib(fCurrContourStartPoint + outset);
}
switch (stroke.getCap()) {
@ -282,7 +285,8 @@ public:
// If our join type isn't round we can alternatively use a bowtie.
JoinType roundCapJoinType = (stroke.getJoin() == SkPaint::kRound_Join)
? JoinType::kRound : JoinType::kBowtie;
this->internalJoinTo(roundCapJoinType, contourEndpoint, fLastControlPoint);
this->internalJoinTo(roundCapJoinType, contourEndpoint,
fPatchWriter.joinControlPoint());
this->internalMoveTo(fCurrContourStartPoint, fCurrContourFirstControlPoint);
this->internalJoinTo(roundCapJoinType, fCurrContourStartPoint,
fCurrContourFirstControlPoint);
@ -291,7 +295,7 @@ public:
case SkPaint::kSquare_Cap: {
// A square cap is the same as appending lineTos.
auto strokeJoinType = JoinType(stroke.getJoin());
SkVector lastTangent = contourEndpoint - fLastControlPoint;
SkVector lastTangent = contourEndpoint - fPatchWriter.joinControlPoint();
if (!stroke.isHairlineStyle()) {
// Extend the cap by 1/2 stroke width.
lastTangent *= (.5f * stroke.getWidth()) / lastTangent.length();
@ -317,7 +321,7 @@ public:
}
}
fHasLastControlPoint = false;
fPatchWriter.writeDeferredStrokePatch();
}
private:
@ -329,8 +333,8 @@ private:
void internalMoveTo(SkPoint pt, SkPoint lastControlPoint) {
fCurrContourStartPoint = pt;
fCurrContourFirstControlPoint = fLastControlPoint = lastControlPoint;
fHasLastControlPoint = true;
fCurrContourFirstControlPoint = lastControlPoint;
fPatchWriter.updateJoinControlPointAttrib(lastControlPoint);
}
// Recursively chops the given conic and its previous join until the segments fit in
@ -470,14 +474,14 @@ private:
void internalPatchTo(JoinType prevJoinType, bool prevJoinFitsInPatch, const SkPoint p[4],
SkPoint endPt) {
if (prevJoinType == JoinType::kBowtie) {
SkASSERT(fHasLastControlPoint);
SkASSERT(fPatchWriter.hasJoinControlPoint());
// Bowtie joins are only used on internal chops, and internal chops almost always have
// continuous tangent angles (i.e., the ending tangent of the first chop and the
// beginning tangent of the second both point in the same direction). The tangents will
// only ever not point in the same direction if we chopped at a cusp point, so that's
// the only time we actually need a bowtie.
SkPoint nextControlPoint = (p[1] == p[0]) ? p[2] : p[1];
SkVector a = p[0] - fLastControlPoint;
SkVector a = p[0] - fPatchWriter.joinControlPoint();
SkVector b = nextControlPoint - p[0];
float ab_cosTheta = a.dot(b);
float ab_pow2 = a.dot(a) * b.dot(b);
@ -500,7 +504,6 @@ private:
if (!SkScalarNearlyEqual(ab_pow2, ab_cosTheta * fabsf(ab_cosTheta),
ab_pow2 * SK_ScalarNearlyZero)) {
this->internalJoinTo(JoinType::kBowtie, p[0], nextControlPoint);
fLastControlPoint = p[0]; // Disables the join section of this patch.
prevJoinFitsInPatch = true;
}
}
@ -511,14 +514,14 @@ private:
// Recursively chops the given join until the segments fit in tessellation patches.
void internalJoinTo(JoinType joinType, SkPoint junctionPoint, SkPoint nextControlPoint,
int maxDepth = -1) {
if (!fHasLastControlPoint) {
if (!fPatchWriter.hasJoinControlPoint()) {
// The first stroke doesn't have a previous join.
return;
}
if (!fSoloRoundJoinAlwaysFitsInPatch && maxDepth != 0 &&
(joinType == JoinType::kRound || joinType == JoinType::kBowtie)) {
SkVector tan0 = junctionPoint - fLastControlPoint;
SkVector tan0 = junctionPoint - fPatchWriter.joinControlPoint();
SkVector tan1 = nextControlPoint - junctionPoint;
float rotation = SkMeasureAngleBetweenVectors(tan0, tan1);
float numRadialSegments = rotation * fNumRadialSegmentsPerRadian;
@ -548,7 +551,7 @@ private:
} while (c0 - junctionPoint != -(c1 - junctionPoint) && --maxAttempts);
// First join half.
this->internalJoinTo(joinType, junctionPoint, c0, maxDepth - 1);
fLastControlPoint = c1;
fPatchWriter.updateJoinControlPointAttrib(c1);
// Second join half.
this->internalJoinTo(joinType, junctionPoint, nextControlPoint, maxDepth - 1);
return;
@ -556,11 +559,11 @@ private:
}
// We should never write out joins before the first curve.
SkASSERT(fHasLastControlPoint);
SkASSERT(fPatchWriter.hasJoinControlPoint());
{
HwPatch patch(fPatchWriter);
patch << fLastControlPoint << junctionPoint;
patch << junctionPoint;
if (joinType == JoinType::kBowtie) {
// {prevControlPoint, [p0, p0, p0, p3]} is a reserved patch pattern that means this
// patch is a bowtie. The bowtie is anchored on p0 and its tangent angles go from
@ -575,20 +578,7 @@ private:
patch << (nextControlPoint);
}
fLastControlPoint = nextControlPoint;
}
void discardStroke(const SkPoint p[], int numPoints) {
if (!fHasLastControlPoint) {
// This disables the first join, if any. (The first join gets added as a standalone
// patch during close(), but setting fCurrContourFirstControlPoint to p[0] causes us to
// skip that join if we attempt to add it later.)
fCurrContourFirstControlPoint = p[0];
fHasLastControlPoint = true;
}
// Set fLastControlPoint to the next stroke's p0 (which will be equal to the final point of
// this stroke). This has the effect of disabling the next stroke's join.
fLastControlPoint = p[numPoints - 1];
fPatchWriter.updateJoinControlPointAttrib(nextControlPoint);
}
PatchWriter& fPatchWriter;
@ -622,10 +612,8 @@ private:
// Variables related to the specific contour that we are currently iterating during
// prepareBuffers().
bool fHasLastControlPoint = false;
SkPoint fCurrContourStartPoint;
SkPoint fCurrContourFirstControlPoint;
SkPoint fLastControlPoint;
};
SK_ALWAYS_INLINE bool cubic_has_cusp(const SkPoint p[4]) {

View File

@ -36,7 +36,7 @@ public:
PathStrokeList* fNext = nullptr;
};
StrokeTessellator(PatchAttribs attribs) : fAttribs(attribs) {}
StrokeTessellator(PatchAttribs attribs) : fAttribs(attribs | PatchAttribs::kJoinControlPoint) {}
// Gives an approximate initial buffer size for this class to write patches into. Ideally the
// whole stroke will fit into this initial buffer, but if it requires a lot of chopping, the

View File

@ -72,13 +72,14 @@ SK_MAYBE_UNUSED constexpr static float kTessellationPrecision = 4;
enum class PatchAttribs {
// Attribs.
kNone = 0,
kFanPoint = 1 << 0, // [float2] Used by wedges. This is the center point the wedges fan around.
kStrokeParams = 1 << 1, // [float2] Used when strokes have different widths or join types.
kColor = 1 << 2, // [ubyte4 or float4] Used when patches have different colors.
kExplicitCurveType = 1 << 3, // [float] Used when GPU can't infer curve type based on infinity.
kJoinControlPoint = 1 << 0, // [float2] Used by strokes. This defines tangent direction.
kFanPoint = 1 << 1, // [float2] Used by wedges. This is the center point the wedges fan around.
kStrokeParams = 1 << 2, // [float2] Used when strokes have different widths or join types.
kColor = 1 << 3, // [ubyte4 or float4] Used when patches have different colors.
kExplicitCurveType = 1 << 4, // [float] Used when GPU can't infer curve type based on infinity.
// Extra flags.
kWideColorIfEnabled = 1 << 4, // If kColor is set, specifies it to be float4 wide color.
kWideColorIfEnabled = 1 << 5, // If kColor is set, specifies it to be float4 wide color.
};
GR_MAKE_BITFIELD_CLASS_OPS(PatchAttribs)
@ -125,7 +126,8 @@ constexpr static float kTriangularConicCurveType SK_MAYBE_UNUSED = 2; // Conic
// Returns the packed size in bytes of the attribs portion of tessellation patches (or instances) in
// GPU buffers.
constexpr size_t PatchAttribsStride(PatchAttribs attribs) {
return (attribs & PatchAttribs::kFanPoint ? sizeof(float) * 2 : 0) +
return (attribs & PatchAttribs::kJoinControlPoint ? sizeof(float) * 2 : 0) +
(attribs & PatchAttribs::kFanPoint ? sizeof(float) * 2 : 0) +
(attribs & PatchAttribs::kStrokeParams ? sizeof(float) * 2 : 0) +
(attribs & PatchAttribs::kColor
? (attribs & PatchAttribs::kWideColorIfEnabled ? sizeof(float)

View File

@ -26,7 +26,7 @@ GrStrokeTessellationShader::GrStrokeTessellationShader(const GrShaderCaps& shade
: GrPrimitiveType::kTriangleStrip,
(mode == Mode::kHardwareTessellation) ? 1 : 0, viewMatrix, color)
, fMode(mode)
, fPatchAttribs(attribs)
, fPatchAttribs(attribs | PatchAttribs::kJoinControlPoint)
, fStroke(stroke)
, fMaxParametricSegments_log2(maxParametricSegments_log2) {
// We should use explicit curve type when, and only when, there isn't infinity support.
@ -37,8 +37,6 @@ GrStrokeTessellationShader::GrStrokeTessellationShader(const GrShaderCaps& shade
SkASSERT(!(attribs & PatchAttribs::kExplicitCurveType));
}
if (fMode == Mode::kHardwareTessellation) {
// A join calculates its starting angle using prevCtrlPtAttr.
fAttribs.emplace_back("prevCtrlPtAttr", kFloat2_GrVertexAttribType, SkSLType::kFloat2);
// pts 0..3 define the stroke as a cubic bezier. If p3.y is infinity, then it's a conic
// with w=p3.x.
//
@ -52,6 +50,8 @@ GrStrokeTessellationShader::GrStrokeTessellationShader(const GrShaderCaps& shade
// (p3 - p0).
fAttribs.emplace_back("pts01Attr", kFloat4_GrVertexAttribType, SkSLType::kFloat4);
fAttribs.emplace_back("pts23Attr", kFloat4_GrVertexAttribType, SkSLType::kFloat4);
// A join calculates its starting angle using prevCtrlPtAttr.
fAttribs.emplace_back("prevCtrlPtAttr", kFloat2_GrVertexAttribType, SkSLType::kFloat2);
} else {
// pts 0..3 define the stroke as a cubic bezier. If p3.y is infinity, then it's a conic
// with w=p3.x.
@ -93,10 +93,10 @@ GrStrokeTessellationShader::GrStrokeTessellationShader(const GrShaderCaps& shade
}
if (fMode == Mode::kHardwareTessellation) {
this->setVertexAttributesWithImplicitOffsets(fAttribs.data(), fAttribs.count());
SkASSERT(this->vertexStride() == sizeof(SkPoint) * 5 + PatchAttribsStride(fPatchAttribs));
SkASSERT(this->vertexStride() == sizeof(SkPoint) * 4 + PatchAttribsStride(fPatchAttribs));
} else {
this->setInstanceAttributesWithImplicitOffsets(fAttribs.data(), fAttribs.count());
SkASSERT(this->instanceStride() == sizeof(SkPoint) * 5 + PatchAttribsStride(fPatchAttribs));
SkASSERT(this->instanceStride() == sizeof(SkPoint) * 4 + PatchAttribsStride(fPatchAttribs));
if (!shaderCaps.vertexIDSupport()) {
constexpr static Attribute kVertexAttrib("edgeID", kFloat_GrVertexAttribType,
SkSLType::kFloat);