Write GPU buffers directly from GrStrokeGeometry
Removes the intermediate stroke representation that GrStrokeGeometry
used to generate. Uses GrOpFlushState::makeVertexStateAtLeast instead
and writes patches directly to a vertex buffer as we iterate the path.
If the vertex buffer runs out of room we simply allocate a new one and
draw the stroke in chunks.
Bug: skia:10419
Bug: skia:10460
Change-Id: Ic743158366e43d4d3f5a4ff97b039d48c9c9c65b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/305380
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
diff --git a/samplecode/SampleCCPRGeometry.cpp b/samplecode/SampleCCPRGeometry.cpp
index 0aa8100..758c076 100644
--- a/samplecode/SampleCCPRGeometry.cpp
+++ b/samplecode/SampleCCPRGeometry.cpp
@@ -26,7 +26,6 @@
#include "src/gpu/GrResourceProvider.h"
#include "src/gpu/ccpr/GrCCCoverageProcessor.h"
#include "src/gpu/ccpr/GrCCFillGeometry.h"
-#include "src/gpu/ccpr/GrCCStroker.h"
#include "src/gpu/ccpr/GrGSCoverageProcessor.h"
#include "src/gpu/ccpr/GrVSCoverageProcessor.h"
#include "src/gpu/geometry/GrPathUtils.h"
@@ -66,7 +65,7 @@
void updateGpuData();
- PrimitiveType fPrimitiveType = PrimitiveType::kTriangles;
+ PrimitiveType fPrimitiveType = PrimitiveType::kCubics;
SkCubicType fCubicType;
SkMatrix fCubicKLM;
@@ -75,7 +74,9 @@
float fConicWeight = .5;
float fStrokeWidth = 40;
- bool fDoStroke = false;
+ SkPaint::Join fStrokeJoin = SkPaint::kMiter_Join;
+ SkPaint::Cap fStrokeCap = SkPaint::kButt_Cap;
+ bool fDoStroke = true;
SkTArray<TriPointInstance> fTriPointInstances;
SkTArray<QuadPointInstance> fQuadPointInstances;
@@ -176,14 +177,18 @@
void CCPRGeometryView::onDrawContent(SkCanvas* canvas) {
canvas->clear(SK_ColorBLACK);
- if (!fDoStroke) {
- SkPaint outlinePaint;
- outlinePaint.setColor(0x80ffffff);
- outlinePaint.setStyle(SkPaint::kStroke_Style);
+ SkPaint outlinePaint;
+ outlinePaint.setColor(0xff808080);
+ outlinePaint.setStyle(SkPaint::kStroke_Style);
+ if (fDoStroke) {
+ outlinePaint.setStrokeWidth(fStrokeWidth);
+ } else {
outlinePaint.setStrokeWidth(0);
- outlinePaint.setAntiAlias(true);
- canvas->drawPath(fPath, outlinePaint);
}
+ outlinePaint.setStrokeJoin(fStrokeJoin);
+ outlinePaint.setStrokeCap(fStrokeCap);
+ outlinePaint.setAntiAlias(true);
+ canvas->drawPath(fPath, outlinePaint);
#if 0
SkPaint gridPaint;
@@ -200,7 +205,18 @@
#endif
SkString caption;
- if (GrRenderTargetContext* rtc = canvas->internal_private_accessTopLayerRenderTargetContext()) {
+ caption.appendf("PrimitiveType_%s",
+ GrCCCoverageProcessor::PrimitiveTypeName(fPrimitiveType));
+ if (PrimitiveType::kCubics == fPrimitiveType) {
+ caption.appendf(" (%s)", SkCubicTypeName(fCubicType));
+ } else if (PrimitiveType::kConics == fPrimitiveType) {
+ caption.appendf(" (w=%f)", fConicWeight);
+ }
+
+ if (fDoStroke) {
+ caption.appendf(" (stroke_width=%f)", fStrokeWidth);
+ } else if (GrRenderTargetContext* rtc =
+ canvas->internal_private_accessTopLayerRenderTargetContext()) {
// Render coverage count.
auto ctx = canvas->recordingContext();
SkASSERT(ctx);
@@ -222,18 +238,6 @@
paint.setPorterDuffXPFactory(SkBlendMode::kSrcOver);
rtc->drawRect(nullptr, std::move(paint), GrAA::kNo, SkMatrix::I(),
SkRect::MakeIWH(this->width(), this->height()));
-
- // Add label.
- caption.appendf("PrimitiveType_%s",
- GrCCCoverageProcessor::PrimitiveTypeName(fPrimitiveType));
- if (PrimitiveType::kCubics == fPrimitiveType) {
- caption.appendf(" (%s)", SkCubicTypeName(fCubicType));
- } else if (PrimitiveType::kConics == fPrimitiveType) {
- caption.appendf(" (w=%f)", fConicWeight);
- }
- if (fDoStroke) {
- caption.appendf(" (stroke_width=%f)", fStrokeWidth);
- }
} else {
caption = "Use GPU backend to visualize geometry.";
}
@@ -345,8 +349,8 @@
void CCPRGeometryView::DrawCoverageCountOp::onExecute(GrOpFlushState* state,
const SkRect& chainBounds) {
GrResourceProvider* rp = state->resourceProvider();
- auto direct = state->gpu()->getContext();
#ifdef SK_GL
+ auto direct = state->gpu()->getContext();
GrGLGpu* glGpu = GrBackendApi::kOpenGL == direct->backend()
? static_cast<GrGLGpu*>(state->gpu())
: nullptr;
@@ -370,52 +374,30 @@
GrOpsRenderPass* renderPass = state->opsRenderPass();
- if (!fView->fDoStroke) {
- for (int i = 0; i < proc->numSubpasses(); ++i) {
- proc->reset(fView->fPrimitiveType, i, rp);
- proc->bindPipeline(state, pipeline, this->bounds());
+ for (int i = 0; i < proc->numSubpasses(); ++i) {
+ proc->reset(fView->fPrimitiveType, i, rp);
+ proc->bindPipeline(state, pipeline, this->bounds());
- if (PrimitiveType::kCubics == fView->fPrimitiveType ||
- PrimitiveType::kConics == fView->fPrimitiveType) {
- sk_sp<GrGpuBuffer> instBuff(rp->createBuffer(
- fView->fQuadPointInstances.count() * sizeof(QuadPointInstance),
- GrGpuBufferType::kVertex, kDynamic_GrAccessPattern,
- fView->fQuadPointInstances.begin()));
- if (!fView->fQuadPointInstances.empty() && instBuff) {
- proc->bindBuffers(renderPass, std::move(instBuff));
- proc->drawInstances(renderPass, fView->fQuadPointInstances.count(), 0);
- }
- } else {
- sk_sp<GrGpuBuffer> instBuff(rp->createBuffer(
- fView->fTriPointInstances.count() * sizeof(TriPointInstance),
- GrGpuBufferType::kVertex, kDynamic_GrAccessPattern,
- fView->fTriPointInstances.begin()));
- if (!fView->fTriPointInstances.empty() && instBuff) {
- proc->bindBuffers(renderPass, std::move(instBuff));
- proc->drawInstances(renderPass, fView->fTriPointInstances.count(), 0);
- }
+ if (PrimitiveType::kCubics == fView->fPrimitiveType ||
+ PrimitiveType::kConics == fView->fPrimitiveType) {
+ sk_sp<GrGpuBuffer> instBuff(rp->createBuffer(
+ fView->fQuadPointInstances.count() * sizeof(QuadPointInstance),
+ GrGpuBufferType::kVertex, kDynamic_GrAccessPattern,
+ fView->fQuadPointInstances.begin()));
+ if (!fView->fQuadPointInstances.empty() && instBuff) {
+ proc->bindBuffers(renderPass, std::move(instBuff));
+ proc->drawInstances(renderPass, fView->fQuadPointInstances.count(), 0);
+ }
+ } else {
+ sk_sp<GrGpuBuffer> instBuff(rp->createBuffer(
+ fView->fTriPointInstances.count() * sizeof(TriPointInstance),
+ GrGpuBufferType::kVertex, kDynamic_GrAccessPattern,
+ fView->fTriPointInstances.begin()));
+ if (!fView->fTriPointInstances.empty() && instBuff) {
+ proc->bindBuffers(renderPass, std::move(instBuff));
+ proc->drawInstances(renderPass, fView->fTriPointInstances.count(), 0);
}
}
- } else if (PrimitiveType::kConics != fView->fPrimitiveType) { // No conic stroke support yet.
- GrCCStroker stroker(0,0,0);
-
- SkPaint p;
- p.setStyle(SkPaint::kStroke_Style);
- p.setStrokeWidth(fView->fStrokeWidth);
- p.setStrokeJoin(SkPaint::kMiter_Join);
- p.setStrokeMiter(4);
- // p.setStrokeCap(SkPaint::kRound_Cap);
- stroker.parseDeviceSpaceStroke(fView->fPath, SkPathPriv::PointData(fView->fPath),
- SkStrokeRec(p), p.getStrokeWidth(), GrScissorTest::kDisabled,
- SkIRect::MakeWH(fView->width(), fView->height()), {0, 0});
- GrCCStroker::BatchID batchID = stroker.closeCurrentBatch();
-
- GrOnFlushResourceProvider onFlushRP(direct->priv().drawingManager());
- stroker.prepareToDraw(&onFlushRP);
-
- SkIRect ibounds;
- this->bounds().roundOut(&ibounds);
- stroker.drawStrokes(state, proc.get(), batchID, ibounds);
}
#ifdef SK_GL
@@ -511,6 +493,17 @@
if (unichar == 'S') {
fDoStroke = !fDoStroke;
this->updateAndInval();
+ return true;
+ }
+ if (unichar == 'J') {
+ fStrokeJoin = (SkPaint::Join)((fStrokeJoin + 1) % 3);
+ this->updateAndInval();
+ return true;
+ }
+ if (unichar == 'C') {
+ fStrokeCap = (SkPaint::Cap)((fStrokeCap + 1) % 3);
+ this->updateAndInval();
+ return true;
}
return false;
}
diff --git a/src/gpu/tessellate/GrStrokeGeometry.cpp b/src/gpu/tessellate/GrStrokeGeometry.cpp
index 13674ed..196ae9d 100644
--- a/src/gpu/tessellate/GrStrokeGeometry.cpp
+++ b/src/gpu/tessellate/GrStrokeGeometry.cpp
@@ -11,22 +11,24 @@
#include "include/private/SkNx.h"
#include "src/core/SkGeometry.h"
#include "src/core/SkMathPriv.h"
+#include "src/core/SkPathPriv.h"
// This is the maximum distance in pixels that we can stray from the edge of a stroke when
// converting it to flat line segments.
static constexpr float kMaxErrorFromLinearization = 1/8.f;
+constexpr static float kInternalRoundJoinType = GrTessellateStrokeShader::kInternalRoundJoinType;
+
+static Sk2f lerp(const Sk2f& a, const Sk2f& b, float T) {
+ SkASSERT(1 != T); // The below does not guarantee lerp(a, b, 1) === b.
+ return (b - a) * T + a;
+}
+
static inline float length(const Sk2f& n) {
Sk2f nn = n*n;
return SkScalarSqrt(nn[0] + nn[1]);
}
-static inline Sk2f normalize(const Sk2f& v) {
- Sk2f vv = v*v;
- vv += SkNx_shuffle<1,0>(vv);
- return v * vv.rsqrt();
-}
-
static inline void transpose(const Sk2f& a, const Sk2f& b, Sk2f* X, Sk2f* Y) {
float transpose[4];
a.store(transpose);
@@ -34,13 +36,6 @@
Sk2f::Load2(transpose, X, Y);
}
-static inline void normalize2(const Sk2f& v0, const Sk2f& v1, SkPoint out[2]) {
- Sk2f X, Y;
- transpose(v0, v1, &X, &Y);
- Sk2f invlength = (X*X + Y*Y).rsqrt();
- Sk2f::Store2(out, Y * invlength, -X * invlength);
-}
-
static inline float calc_curvature_costheta(const Sk2f& leftTan, const Sk2f& rightTan) {
Sk2f X, Y;
transpose(leftTan, rightTan, &X, &Y);
@@ -49,80 +44,194 @@
return (dotprod[0] + dotprod[1]) * invlength[0] * invlength[1];
}
-static GrStrokeGeometry::Verb join_verb_from_join(SkPaint::Join join) {
- using Verb = GrStrokeGeometry::Verb;
- switch (join) {
- case SkPaint::kBevel_Join:
- return Verb::kBevelJoin;
- case SkPaint::kMiter_Join:
- return Verb::kMiterJoin;
- case SkPaint::kRound_Join:
- return Verb::kRoundJoin;
- }
- SK_ABORT("Invalid SkPaint::Join.");
+void GrStrokeGeometry::allocVertexChunk(int minVertexAllocCount) {
+ VertexChunk* chunk = &fVertexChunkArray->push_back();
+ fCurrChunkVertexData = (SkPoint*)fTarget->makeVertexSpaceAtLeast(
+ sizeof(SkPoint), minVertexAllocCount, minVertexAllocCount, &chunk->fVertexBuffer,
+ &chunk->fBaseVertex, &fCurrChunkVertexCapacity);
+ fCurrChunkMinVertexAllocCount = minVertexAllocCount;
}
-void GrStrokeGeometry::beginPath(const SkStrokeRec& stroke, float strokeDevWidth,
- InstanceTallies* tallies) {
- SkASSERT(!fInsideContour);
+SkPoint* GrStrokeGeometry::reservePatch() {
+ constexpr static int kNumVerticesPerPatch = GrTessellateStrokeShader::kNumVerticesPerPatch;
+ if (fVertexChunkArray->back().fVertexCount + kNumVerticesPerPatch > fCurrChunkVertexCapacity) {
+ // No need to put back vertices; the buffer is full.
+ this->allocVertexChunk(fCurrChunkMinVertexAllocCount * 2);
+ }
+ if (!fCurrChunkVertexData) {
+ SkDebugf("WARNING: Failed to allocate vertex buffer for tessellated stroke.");
+ return nullptr;
+ }
+ SkASSERT(fVertexChunkArray->back().fVertexCount + kNumVerticesPerPatch <=
+ fCurrChunkVertexCapacity);
+ SkPoint* patch = fCurrChunkVertexData + fVertexChunkArray->back().fVertexCount;
+ fVertexChunkArray->back().fVertexCount += kNumVerticesPerPatch;
+ return patch;
+}
+
+void GrStrokeGeometry::writeCubicSegment(float leftJoinType, const SkPoint pts[4],
+ float overrideNumSegments) {
+ SkPoint c1 = (pts[1] == pts[0]) ? pts[2] : pts[1];
+ SkPoint c2 = (pts[2] == pts[3]) ? pts[1] : pts[2];
+
+ if (fHasPreviousSegment) {
+ this->writeJoin(leftJoinType, pts[0], fLastControlPoint, c1);
+ } else {
+ fCurrContourFirstControlPoint = c1;
+ fHasPreviousSegment = true;
+ }
+
+ if (SkPoint* patch = this->reservePatch()) {
+ memcpy(patch, pts, sizeof(SkPoint) * 4);
+ patch[4].set(-overrideNumSegments, fCurrStrokeRadius);
+ }
+
+ fLastControlPoint = c2;
+ fCurrentPoint = pts[3];
+}
+
+void GrStrokeGeometry::writeJoin(float joinType, const SkPoint& anchorPoint,
+ const SkPoint& prevControlPoint, const SkPoint& nextControlPoint) {
+ if (SkPoint* joinPatch = this->reservePatch()) {
+ joinPatch[0] = anchorPoint;
+ joinPatch[1] = prevControlPoint;
+ joinPatch[2] = nextControlPoint;
+ joinPatch[3] = anchorPoint;
+ joinPatch[4].set(joinType, fCurrStrokeRadius);
+ }
+}
+
+void GrStrokeGeometry::writeSquareCap(const SkPoint& endPoint, const SkPoint& controlPoint) {
+ SkVector v = (endPoint - controlPoint);
+ v.normalize();
+ SkPoint capPoint = endPoint + v*fCurrStrokeRadius;
+ // Construct a line that incorporates controlPoint so we get a water tight edge with the rest of
+ // the stroke. The cubic will technically step outside the cap, but we will force it to only
+ // have one segment, giving edges only at the endpoints.
+ if (SkPoint* capPatch = this->reservePatch()) {
+ capPatch[0] = endPoint;
+ capPatch[1] = controlPoint;
+ // Straddle the midpoint of the cap because the tessellated geometry emits a center point at
+ // T=.5, and we need to ensure that point stays inside the cap.
+ capPatch[2] = endPoint + capPoint - controlPoint;
+ capPatch[3] = capPoint;
+ capPatch[4].set(-1, fCurrStrokeRadius);
+ }
+}
+
+void GrStrokeGeometry::writeCaps() {
+ if (!fHasPreviousSegment) {
+ // 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.
+ fCurrContourFirstControlPoint = fCurrContourStartPoint - SkPoint{1,0};
+ fLastControlPoint = fCurrContourStartPoint + SkPoint{1,0};
+ fCurrentPoint = fCurrContourStartPoint;
+ }
+
+ switch (fCurrStrokeCapType) {
+ case SkPaint::kButt_Cap:
+ break;
+ case SkPaint::kRound_Cap:
+ // A round cap is the same thing as a 180-degree round join.
+ this->writeJoin(3, fCurrContourStartPoint, fCurrContourFirstControlPoint,
+ fCurrContourFirstControlPoint);
+ this->writeJoin(3, fCurrentPoint, fLastControlPoint, fLastControlPoint);
+ break;
+ case SkPaint::kSquare_Cap:
+ this->writeSquareCap(fCurrContourStartPoint, fCurrContourFirstControlPoint);
+ this->writeSquareCap(fCurrentPoint, fLastControlPoint);
+ break;
+ }
+}
+
+void GrStrokeGeometry::addPath(const SkPath& path, const SkStrokeRec& stroke) {
+ this->beginPath(stroke, stroke.getWidth());
+ SkPathVerb previousVerb = SkPathVerb::kClose;
+ for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
+ switch (verb) {
+ case SkPathVerb::kMove:
+ // "A subpath ... consisting of a single moveto shall not be stroked."
+ // https://www.w3.org/TR/SVG11/painting.html#StrokeProperties
+ if (previousVerb != SkPathVerb::kMove && previousVerb != SkPathVerb::kClose) {
+ this->writeCaps();
+ }
+ this->moveTo(pts[0]);
+ break;
+ case SkPathVerb::kClose:
+ this->close();
+ break;
+ case SkPathVerb::kLine:
+ SkASSERT(previousVerb != SkPathVerb::kClose);
+ this->lineTo(pts[0], pts[1]);
+ break;
+ case SkPathVerb::kQuad:
+ SkASSERT(previousVerb != SkPathVerb::kClose);
+ this->quadraticTo(pts);
+ break;
+ case SkPathVerb::kCubic:
+ SkASSERT(previousVerb != SkPathVerb::kClose);
+ this->cubicTo(pts);
+ break;
+ case SkPathVerb::kConic:
+ SkASSERT(previousVerb != SkPathVerb::kClose);
+ SkUNREACHABLE;
+ }
+ previousVerb = verb;
+ }
+ if (previousVerb != SkPathVerb::kMove && previousVerb != SkPathVerb::kClose) {
+ this->writeCaps();
+ }
+}
+
+static float join_type_from_join(SkPaint::Join join) {
+ switch (join) {
+ case SkPaint::kBevel_Join:
+ return GrTessellateStrokeShader::kBevelJoinType;
+ case SkPaint::kMiter_Join:
+ return GrTessellateStrokeShader::kMiterJoinType;
+ case SkPaint::kRound_Join:
+ return GrTessellateStrokeShader::kRoundJoinType;
+ }
+ SkUNREACHABLE;
+}
+
+void GrStrokeGeometry::beginPath(const SkStrokeRec& stroke, float strokeDevWidth) {
// Client should have already converted the stroke to device space (i.e. width=1 for hairline).
SkASSERT(strokeDevWidth > 0);
fCurrStrokeRadius = strokeDevWidth/2;
- fCurrStrokeJoinVerb = join_verb_from_join(stroke.getJoin());
+ fCurrStrokeJoinType = join_type_from_join(stroke.getJoin());
fCurrStrokeCapType = stroke.getCap();
- fCurrStrokeTallies = tallies;
-
- if (Verb::kMiterJoin == fCurrStrokeJoinVerb) {
- // We implement miters by placing a triangle-shaped cap on top of a bevel join. Convert the
- // "miter limit" to how tall that triangle cap can be.
- float m = stroke.getMiter();
- fMiterMaxCapHeightOverWidth = .5f * SkScalarSqrt(m*m - 1);
- }
// Find the angle of curvature where the arc height above a simple line from point A to point B
// is equal to kMaxErrorFromLinearization.
float r = std::max(1 - kMaxErrorFromLinearization / fCurrStrokeRadius, 0.f);
fMaxCurvatureCosTheta = 2*r*r - 1;
- fCurrContourFirstPtIdx = -1;
- fCurrContourFirstNormalIdx = -1;
-
- fVerbs.push_back(Verb::kBeginPath);
+ fHasPreviousSegment = false;
}
-void GrStrokeGeometry::moveTo(SkPoint pt) {
- SkASSERT(!fInsideContour);
- fCurrContourFirstPtIdx = fPoints.count();
- fCurrContourFirstNormalIdx = fNormals.count();
- fPoints.push_back(pt);
- SkDEBUGCODE(fInsideContour = true);
+void GrStrokeGeometry::moveTo(const SkPoint& pt) {
+ fHasPreviousSegment = false;
+ fCurrContourStartPoint = pt;
}
-void GrStrokeGeometry::lineTo(SkPoint pt) {
- SkASSERT(fInsideContour);
- this->lineTo(fCurrStrokeJoinVerb, pt);
+void GrStrokeGeometry::lineTo(const SkPoint& p0, const SkPoint& p1) {
+ this->lineTo(fCurrStrokeJoinType, p0, p1);
}
-void GrStrokeGeometry::lineTo(Verb leftJoinVerb, SkPoint pt) {
- Sk2f tan = Sk2f::Load(&pt) - Sk2f::Load(&fPoints.back());
- if ((tan == 0).allTrue()) {
+void GrStrokeGeometry::lineTo(float leftJoinType, const SkPoint& pt0, const SkPoint& pt1) {
+ Sk2f p0 = Sk2f::Load(&pt0);
+ Sk2f p1 = Sk2f::Load(&pt1);
+ if ((p0 == p1).allTrue()) {
return;
}
-
- tan = normalize(tan);
- SkVector n = SkVector::Make(tan[1], -tan[0]);
-
- this->recordLeftJoinIfNotEmpty(leftJoinVerb, n);
- fNormals.push_back(n);
-
- this->recordStroke(Verb::kLinearStroke, 0);
- fPoints.push_back(pt);
+ this->writeCubicSegment(leftJoinType, p0, lerp(p0, p1, 1/3.f), lerp(p0, p1, 2/3.f), p1, 1);
}
void GrStrokeGeometry::quadraticTo(const SkPoint P[3]) {
- SkASSERT(fInsideContour);
- this->quadraticTo(fCurrStrokeJoinVerb, P, SkFindQuadMaxCurvature(P));
+ this->quadraticTo(fCurrStrokeJoinType, P, SkFindQuadMaxCurvature(P));
}
// Wang's formula for quadratics (1985) gives us the number of evenly spaced (in the parametric
@@ -134,7 +243,7 @@
return SkScalarCeilToInt(f);
}
-void GrStrokeGeometry::quadraticTo(Verb leftJoinVerb, const SkPoint P[3], float maxCurvatureT) {
+void GrStrokeGeometry::quadraticTo(float leftJoinType, const SkPoint P[3], float maxCurvatureT) {
Sk2f p0 = Sk2f::Load(P);
Sk2f p1 = Sk2f::Load(P+1);
Sk2f p2 = Sk2f::Load(P+2);
@@ -146,16 +255,13 @@
// an issue.
if ((tan0.abs() < SK_ScalarNearlyZero).allTrue() || // p0 ~= p1
(tan1.abs() < SK_ScalarNearlyZero).allTrue()) { // p1 ~= p2
- this->lineTo(leftJoinVerb, P[2]);
+ this->lineTo(leftJoinType, P[0], P[2]);
return;
}
- SkPoint normals[2];
- normalize2(tan0, tan1, normals);
-
// Decide how many flat line segments to chop the curve into.
int numSegments = wangs_formula_quadratic(p0, p1, p2);
- numSegments = SkTPin(numSegments, 1, 1 << kMaxNumLinearSegmentsLog2);
+ numSegments = std::max(numSegments, 1);
// At + B gives a vector tangent to the quadratic.
Sk2f A = p0 - p1*2 + p2;
@@ -193,22 +299,22 @@
if (leftT > 0) {
SkChopQuadAt(currQuadratic, ptsBuffer, leftT);
- this->quadraticTo(leftJoinVerb, ptsBuffer, /*maxCurvatureT=*/1);
+ this->quadraticTo(leftJoinType, ptsBuffer, /*maxCurvatureT=*/1);
if (rightT < 1) {
rightT = (rightT - leftT) / (1 - leftT);
}
currQuadratic = ptsBuffer + 2;
} else {
- this->rotateTo(leftJoinVerb, normals[0], currQuadratic[1]);
+ this->rotateTo(leftJoinType, currQuadratic[0], currQuadratic[1]);
}
if (rightT < 1) {
SkChopQuadAt(currQuadratic, ptsBuffer, rightT);
- this->lineTo(Verb::kInternalRoundJoin, ptsBuffer[2]);
- this->quadraticTo(Verb::kInternalRoundJoin, ptsBuffer + 2, /*maxCurvatureT=*/0);
+ this->lineTo(kInternalRoundJoinType, ptsBuffer[0], ptsBuffer[2]);
+ this->quadraticTo(kInternalRoundJoinType, ptsBuffer + 2, /*maxCurvatureT=*/0);
} else {
- this->lineTo(Verb::kInternalRoundJoin, currQuadratic[2]);
- this->rotateTo(Verb::kInternalRoundJoin, normals[1],
+ this->lineTo(kInternalRoundJoinType, currQuadratic[0], currQuadratic[2]);
+ this->rotateTo(kInternalRoundJoinType, currQuadratic[2],
currQuadratic[2]*2 - currQuadratic[1]);
}
return;
@@ -216,24 +322,18 @@
if (numSegments > fMaxTessellationSegments) {
SkPoint ptsBuffer[5];
SkChopQuadAt(P, ptsBuffer, 0.5f);
- this->quadraticTo(leftJoinVerb, ptsBuffer, 0);
- this->quadraticTo(Verb::kInternalRoundJoin, ptsBuffer + 3, 0);
+ this->quadraticTo(leftJoinType, ptsBuffer, 0);
+ this->quadraticTo(kInternalRoundJoinType, ptsBuffer + 3, 0);
return;
}
- this->recordLeftJoinIfNotEmpty(leftJoinVerb, normals[0]);
- fNormals.push_back_n(2, normals);
-
- this->recordStroke(Verb::kQuadraticStroke, SkNextLog2(numSegments));
- p1.store(&fPoints.push_back());
- p2.store(&fPoints.push_back());
+ this->writeCubicSegment(leftJoinType, p0, lerp(p0, p1, 2/3.f), lerp(p1, p2, 1/3.f), p2);
}
void GrStrokeGeometry::cubicTo(const SkPoint P[4]) {
- SkASSERT(fInsideContour);
float roots[3];
int numRoots = SkFindCubicMaxCurvature(P, roots);
- this->cubicTo(fCurrStrokeJoinVerb, P,
+ this->cubicTo(fCurrStrokeJoinType, P,
numRoots > 0 ? roots[numRoots/2] : 0,
numRoots > 1 ? roots[0] : kLeftMaxCurvatureNone,
numRoots > 2 ? roots[2] : kRightMaxCurvatureNone);
@@ -250,8 +350,8 @@
return SkScalarCeilToInt(f);
}
-void GrStrokeGeometry::cubicTo(Verb leftJoinVerb, const SkPoint P[4], float maxCurvatureT,
- float leftMaxCurvatureT, float rightMaxCurvatureT) {
+void GrStrokeGeometry::cubicTo(float leftJoinType, const SkPoint P[4], float maxCurvatureT,
+ float leftMaxCurvatureT, float rightMaxCurvatureT) {
Sk2f p0 = Sk2f::Load(P);
Sk2f p1 = Sk2f::Load(P+1);
Sk2f p2 = Sk2f::Load(P+2);
@@ -265,7 +365,7 @@
p1 = p0;
tan0 = p2 - p0;
if ((tan0.abs() < SK_ScalarNearlyZero).allTrue()) { // p0 ~= p1 ~= p2
- this->lineTo(leftJoinVerb, P[3]);
+ this->lineTo(leftJoinType, P[0], P[3]);
return;
}
}
@@ -274,17 +374,14 @@
tan1 = p3 - p1;
if ((tan1.abs() < SK_ScalarNearlyZero).allTrue() || // p1 ~= p2 ~= p3
(p0 == p1).allTrue()) { // p0 ~= p1 AND p2 ~= p3
- this->lineTo(leftJoinVerb, P[3]);
+ this->lineTo(leftJoinType, P[0], P[3]);
return;
}
}
- SkPoint normals[2];
- normalize2(tan0, tan1, normals);
-
// Decide how many flat line segments to chop the curve into.
int numSegments = wangs_formula_cubic(p0, p1, p2, p3);
- numSegments = SkTPin(numSegments, 1, 1 << kMaxNumLinearSegmentsLog2);
+ numSegments = std::max(numSegments, 1);
// At^2 + Bt + C gives a vector tangent to the cubic. (More specifically, it's the derivative
// minus an irrelevant scale by 3, since all we care about is the direction.)
@@ -328,7 +425,7 @@
if (leftT > 0) {
SkChopCubicAt(currCubic, ptsBuffer, leftT);
- this->cubicTo(leftJoinVerb, ptsBuffer, /*maxCurvatureT=*/1,
+ this->cubicTo(leftJoinType, ptsBuffer, /*maxCurvatureT=*/1,
(kLeftMaxCurvatureNone != leftMaxCurvatureT)
? leftMaxCurvatureT/leftT : kLeftMaxCurvatureNone,
kRightMaxCurvatureNone);
@@ -341,144 +438,73 @@
currCubic = ptsBuffer + 3;
} else {
SkPoint c1 = (ptsBuffer[1] == ptsBuffer[0]) ? ptsBuffer[2] : ptsBuffer[1];
- this->rotateTo(leftJoinVerb, normals[0], c1);
+ this->rotateTo(leftJoinType, ptsBuffer[0], c1);
}
if (rightT < 1) {
SkChopCubicAt(currCubic, ptsBuffer, rightT);
- this->lineTo(Verb::kInternalRoundJoin, ptsBuffer[3]);
+ this->lineTo(kInternalRoundJoinType, ptsBuffer[0], ptsBuffer[3]);
currCubic = ptsBuffer + 3;
- this->cubicTo(Verb::kInternalRoundJoin, currCubic, /*maxCurvatureT=*/0,
+ this->cubicTo(kInternalRoundJoinType, currCubic, /*maxCurvatureT=*/0,
kLeftMaxCurvatureNone, kRightMaxCurvatureNone);
} else {
- this->lineTo(Verb::kInternalRoundJoin, currCubic[3]);
+ this->lineTo(kInternalRoundJoinType, currCubic[0], currCubic[3]);
SkPoint c2 = (currCubic[2] == currCubic[3]) ? currCubic[1] : currCubic[2];
- this->rotateTo(Verb::kInternalRoundJoin, normals[1], currCubic[3]*2 - c2);
+ this->rotateTo(kInternalRoundJoinType, currCubic[3], currCubic[3]*2 - c2);
}
return;
}
// Recurse and check the other two points of max curvature, if any.
if (kRightMaxCurvatureNone != rightMaxCurvatureT) {
- this->cubicTo(leftJoinVerb, P, rightMaxCurvatureT, leftMaxCurvatureT,
+ this->cubicTo(leftJoinType, P, rightMaxCurvatureT, leftMaxCurvatureT,
kRightMaxCurvatureNone);
return;
}
if (kLeftMaxCurvatureNone != leftMaxCurvatureT) {
SkASSERT(kRightMaxCurvatureNone == rightMaxCurvatureT);
- this->cubicTo(leftJoinVerb, P, leftMaxCurvatureT, kLeftMaxCurvatureNone,
+ this->cubicTo(leftJoinType, P, leftMaxCurvatureT, kLeftMaxCurvatureNone,
kRightMaxCurvatureNone);
return;
}
if (numSegments > fMaxTessellationSegments) {
SkPoint ptsBuffer[7];
SkChopCubicAt(P, ptsBuffer, 0.5f);
- this->cubicTo(leftJoinVerb, ptsBuffer, 0, kLeftMaxCurvatureNone, kRightMaxCurvatureNone);
- this->cubicTo(Verb::kInternalRoundJoin, ptsBuffer + 3, 0, kLeftMaxCurvatureNone,
+ this->cubicTo(leftJoinType, ptsBuffer, 0, kLeftMaxCurvatureNone, kRightMaxCurvatureNone);
+ this->cubicTo(kInternalRoundJoinType, ptsBuffer + 3, 0, kLeftMaxCurvatureNone,
kRightMaxCurvatureNone);
return;
}
- this->recordLeftJoinIfNotEmpty(leftJoinVerb, normals[0]);
- fNormals.push_back_n(2, normals);
-
- this->recordStroke(Verb::kCubicStroke, SkNextLog2(numSegments));
- p1.store(&fPoints.push_back());
- p2.store(&fPoints.push_back());
- p3.store(&fPoints.push_back());
+ this->writeCubicSegment(leftJoinType, p0, p1, p2, p3);
}
-void GrStrokeGeometry::recordStroke(Verb verb, int numSegmentsLog2) {
- SkASSERT(Verb::kLinearStroke != verb || 0 == numSegmentsLog2);
- fVerbs.push_back(verb);
- if (Verb::kLinearStroke != verb) {
- fParams.push_back().fNumLinearSegmentsLog2 = numSegmentsLog2;
- }
- ++fCurrStrokeTallies->fStrokes[numSegmentsLog2];
+void GrStrokeGeometry::rotateTo(float leftJoinType, const SkPoint& anchorPoint,
+ const SkPoint& controlPoint) {
+ // Effectively rotate the current normal by drawing a zero length, 1-segment cubic.
+ // writeCubicSegment automatically adds the necessary join and the zero length cubic serves as
+ // a glue that guarantees a water tight rasterized edge between the new join and the segment
+ // that comes after the rotate.
+ SkPoint pts[4] = {anchorPoint, controlPoint, anchorPoint*2 - controlPoint, anchorPoint};
+ this->writeCubicSegment(leftJoinType, pts, 1);
}
-void GrStrokeGeometry::rotateTo(Verb leftJoinVerb, SkVector normal, SkPoint controlPoint) {
- SkASSERT(fPoints.count() > fCurrContourFirstPtIdx);
- this->recordLeftJoinIfNotEmpty(leftJoinVerb, normal);
- fVerbs.push_back(Verb::kRotate);
- fPoints.push_back(controlPoint);
- fPoints.push_back(fPoints[fPoints.count() - 2]);
- fNormals.push_back(normal);
-}
-
-void GrStrokeGeometry::recordLeftJoinIfNotEmpty(Verb joinVerb, SkVector nextNormal) {
- if (fNormals.count() <= fCurrContourFirstNormalIdx) {
- // The contour is empty. Nothing to join with.
- SkASSERT(fNormals.count() == fCurrContourFirstNormalIdx);
- return;
- }
- fVerbs.push_back(joinVerb);
-}
-
-void GrStrokeGeometry::closeContour() {
- SkASSERT(fInsideContour);
- SkASSERT(fPoints.count() > fCurrContourFirstPtIdx);
- if (fPoints.back() != fPoints[fCurrContourFirstPtIdx]) {
- // Draw a line back to the beginning.
- this->lineTo(fCurrStrokeJoinVerb, fPoints[fCurrContourFirstPtIdx]);
- }
- fVerbs.push_back(fCurrStrokeJoinVerb);
- fVerbs.push_back(Verb::kEndContour);
- SkDEBUGCODE(fInsideContour = false);
-}
-
-void GrStrokeGeometry::capContourAndExit() {
- SkASSERT(fInsideContour);
- if (fCurrContourFirstNormalIdx >= fNormals.count()) {
- // This contour is empty. Add a normal in the direction that caps orient on empty geometry.
- SkASSERT(fNormals.count() == fCurrContourFirstNormalIdx);
- fNormals.push_back({1, 0});
- }
-
- this->recordCapsIfAny();
- fVerbs.push_back(Verb::kEndContour);
-
- SkDEBUGCODE(fInsideContour = false);
-}
-
-void GrStrokeGeometry::recordCapsIfAny() {
- SkASSERT(fInsideContour);
- SkASSERT(fCurrContourFirstNormalIdx < fNormals.count());
-
- if (SkPaint::kButt_Cap == fCurrStrokeCapType) {
+void GrStrokeGeometry::close() {
+ if (!fHasPreviousSegment) {
+ // Draw caps instead of closing if the subpath is zero length:
+ //
+ // "Any zero length subpath ... shall be stroked if the 'stroke-linecap' property has a
+ // value of round or square producing respectively a circle or a square."
+ //
+ // (https://www.w3.org/TR/SVG11/painting.html#StrokeProperties)
+ //
+ this->writeCaps();
return;
}
- Verb capVerb;
- if (SkPaint::kSquare_Cap == fCurrStrokeCapType) {
- if (fCurrStrokeRadius * SK_ScalarRoot2Over2 < kMaxErrorFromLinearization) {
- return;
- }
- capVerb = Verb::kSquareCap;
- fCurrStrokeTallies->fStrokes[0] += 2;
- } else {
- SkASSERT(SkPaint::kRound_Cap == fCurrStrokeCapType);
- if (fCurrStrokeRadius < kMaxErrorFromLinearization) {
- return;
- }
- capVerb = Verb::kRoundCap;
- fCurrStrokeTallies->fTriangles += 2;
- fCurrStrokeTallies->fConics += 4;
- }
-
- fVerbs.push_back(capVerb);
- fVerbs.push_back(Verb::kEndContour);
-
- fVerbs.push_back(capVerb);
-
- // Reserve the space first, since push_back() takes the point by reference and might
- // invalidate the reference if the array grows.
- fPoints.reserve(fPoints.count() + 1);
- fPoints.push_back(fPoints[fCurrContourFirstPtIdx]);
-
- // Reserve the space first, since push_back() takes the normal by reference and might
- // invalidate the reference if the array grows. (Although in this case we should be fine
- // since there is a negate operator.)
- fNormals.reserve(fNormals.count() + 1);
- fNormals.push_back(-fNormals[fCurrContourFirstNormalIdx]);
+ // Draw a line back to the beginning. (This will be discarded if
+ // fCurrentPoint == fCurrContourStartPoint.)
+ this->lineTo(fCurrStrokeJoinType, fCurrentPoint, fCurrContourStartPoint);
+ this->writeJoin(fCurrStrokeJoinType, fCurrContourStartPoint, fLastControlPoint,
+ fCurrContourFirstControlPoint);
}
diff --git a/src/gpu/tessellate/GrStrokeGeometry.h b/src/gpu/tessellate/GrStrokeGeometry.h
index 2c4a757..2bc6340 100644
--- a/src/gpu/tessellate/GrStrokeGeometry.h
+++ b/src/gpu/tessellate/GrStrokeGeometry.h
@@ -11,120 +11,108 @@
#include "include/core/SkPaint.h"
#include "include/core/SkPoint.h"
#include "include/private/SkTArray.h"
+#include "src/gpu/ops/GrMeshDrawOp.h"
+#include "src/gpu/tessellate/GrTessellateStrokeShader.h"
class SkStrokeRec;
-/**
- * This class converts post-transform stroked paths into a set of independent strokes, joins, and
- * caps that map directly to GPU instances.
- */
+// This is an RAII class that expands strokes into tessellation patches for consumption by
+// GrTessellateStrokeShader. The provided GrMeshDrawOp::Target must not be used externally for the
+// entire lifetime of this class. e.g.:
+//
+// void onPrepare(GrOpFlushState* target) {
+// GrStrokeGeometry g(target, &fMyVertexChunks, count); // Locks target.
+// for (...) {
+// g.addPath(path, stroke);
+// }
+// }
+// ... target can now be used normally again.
+// ... fMyVertexChunks now contains chunks that can be drawn during onExecute.
class GrStrokeGeometry {
public:
- static constexpr int kMaxNumLinearSegmentsLog2 = 15;
-
- GrStrokeGeometry(int maxTessellationSegments, int numSkPoints = 0, int numSkVerbs = 0)
- : fMaxTessellationSegments(maxTessellationSegments)
- , fVerbs(numSkVerbs * 5/2) // Reserve for a 2.5x expansion in verbs. (Joins get their
- // own separate verb in our representation.)
- , fParams(numSkVerbs * 3) // Somewhere around 1-2 params per verb.
- , fPoints(numSkPoints * 5/4) // Reserve for a 1.25x expansion in points and normals.
- , fNormals(numSkPoints * 5/4) {}
-
- // A string of verbs and their corresponding, params, points, and normals are a compact
- // representation of what will eventually be independent instances in GPU buffers.
- enum class Verb : uint8_t {
- kBeginPath, // Instructs the iterator to advance its stroke width, atlas offset, etc.
-
- // Independent strokes of a single line or curve, with (antialiased) butt caps on the ends.
- kLinearStroke,
- kQuadraticStroke,
- kCubicStroke,
-
- // Updates the last tangent without moving the current position on the stroke.
- kRotate,
-
- // Joins are a triangles that connect the outer corners of two adjoining strokes. Miters
- // have an additional triangle cap on top of the bevel, and round joins have an arc on top.
- kBevelJoin,
- kMiterJoin,
- kRoundJoin,
-
- // We use internal joins when we have to internally break up a stroke because its curvature
- // is too strong for a triangle strip. They are coverage-counted, self-intersecting
- // quadrilaterals that tie the four corners of two adjoining strokes together a like a
- // shoelace. (Coverage is negative on the inside half.) We place an arc on both ends of an
- // internal round join.
- kInternalBevelJoin,
- kInternalRoundJoin,
-
- kSquareCap,
- kRoundCap,
-
- kEndContour // Instructs the iterator to advance its internal point and normal ptrs.
- };
- static bool IsInternalJoinVerb(Verb verb);
-
- // Some verbs require additional parameters(s).
- union Parameter {
- // For cubic and quadratic strokes: How many flat line segments to chop the curve into?
- int fNumLinearSegmentsLog2;
- // For miter and round joins: How tall should the triangle cap be on top of the join?
- // (This triangle is the conic control points for a round join.)
- float fMiterCapHeightOverWidth;
- float fConicWeight; // Round joins only.
+ // We generate vertex buffers in chunks. Normally there will only be one chunk, but in rare
+ // cases the first can run out of space if too many cubics needed to be subdivided.
+ struct VertexChunk {
+ sk_sp<const GrBuffer> fVertexBuffer;
+ int fVertexCount = 0;
+ int fBaseVertex;
};
- const SkTArray<Verb, true>& verbs() const { SkASSERT(!fInsideContour); return fVerbs; }
- const SkTArray<Parameter, true>& params() const { SkASSERT(!fInsideContour); return fParams; }
- const SkTArray<SkPoint, true>& points() const { SkASSERT(!fInsideContour); return fPoints; }
- const SkTArray<SkVector, true>& normals() const { SkASSERT(!fInsideContour); return fNormals; }
+ // Stores raw pointers to the provided target and vertexChunkArray, which this class will use
+ // and push to as addPath is called. The caller is responsible to bind and draw each chunk that
+ // gets pushed to the array. (See GrTessellateStrokeShader.)
+ GrStrokeGeometry(GrMeshDrawOp::Target* target, SkTArray<VertexChunk>* vertexChunkArray,
+ int totalCombinedVerbCnt)
+ : fMaxTessellationSegments(target->caps().shaderCaps()->maxTessellationSegments())
+ , fTarget(target)
+ , fVertexChunkArray(vertexChunkArray) {
+ this->allocVertexChunk(
+ (totalCombinedVerbCnt * 3) * GrTessellateStrokeShader::kNumVerticesPerPatch);
+ }
- // These track the numbers of instances required to draw all the recorded strokes.
- struct InstanceTallies {
- int fStrokes[kMaxNumLinearSegmentsLog2 + 1];
- int fTriangles;
- int fConics;
+ // "Releases" the target to be used externally again by putting back any unused pre-allocated
+ // vertices.
+ ~GrStrokeGeometry() {
+ fTarget->putBackVertices(fCurrChunkVertexCapacity - fVertexChunkArray->back().fVertexCount,
+ sizeof(SkPoint));
+ }
- InstanceTallies operator+(const InstanceTallies&) const;
- };
-
- void beginPath(const SkStrokeRec&, float strokeDevWidth, InstanceTallies*);
- void moveTo(SkPoint);
- void lineTo(SkPoint);
- void quadraticTo(const SkPoint[3]);
- void cubicTo(const SkPoint[4]);
- void closeContour(); // Connect back to the first point in the contour and exit.
- void capContourAndExit(); // Add endcaps (if any) and exit the contour.
+ void addPath(const SkPath&, const SkStrokeRec&);
private:
- void lineTo(Verb leftJoinVerb, SkPoint);
- void quadraticTo(Verb leftJoinVerb, const SkPoint[3], float maxCurvatureT);
+ void allocVertexChunk(int minVertexAllocCount);
+ SkPoint* reservePatch();
+
+ // Join types are written as floats in P4.x. See GrTessellateStrokeShader for definitions.
+ void writeCubicSegment(float leftJoinType, const SkPoint pts[4], float overrideNumSegments = 0);
+ void writeCubicSegment(float leftJoinType, const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
+ const Sk2f& p3, float overrideNumSegments = 0) {
+ SkPoint pts[4];
+ p0.store(&pts[0]);
+ p1.store(&pts[1]);
+ p2.store(&pts[2]);
+ p3.store(&pts[3]);
+ this->writeCubicSegment(leftJoinType, pts, overrideNumSegments);
+ }
+ void writeJoin(float joinType, const SkPoint& anchorPoint, const SkPoint& prevControlPoint,
+ const SkPoint& nextControlPoint);
+ void writeSquareCap(const SkPoint& endPoint, const SkPoint& controlPoint);
+ void writeCaps();
+
+ void beginPath(const SkStrokeRec&, float strokeDevWidth);
+ void moveTo(const SkPoint&);
+ void lineTo(const SkPoint& p0, const SkPoint& p1);
+ void quadraticTo(const SkPoint[3]);
+ void cubicTo(const SkPoint[4]);
+ void close();
+
+ void lineTo(float leftJoinType, const SkPoint& p0, const SkPoint& p1);
+ void quadraticTo(float leftJoinType, const SkPoint[3], float maxCurvatureT);
static constexpr float kLeftMaxCurvatureNone = 1;
static constexpr float kRightMaxCurvatureNone = 0;
- void cubicTo(Verb leftJoinVerb, const SkPoint[4], float maxCurvatureT, float leftMaxCurvatureT,
+ void cubicTo(float leftJoinType, const SkPoint[4], float maxCurvatureT, float leftMaxCurvatureT,
float rightMaxCurvatureT);
- // Pushes a new normal to fNormals and records a join, without changing the current position.
- void rotateTo(Verb leftJoinVerb, SkVector normal, SkPoint controlPoint);
+ // TEMPORARY: Rotates the current control point without changing the current position.
+ // This is used when we convert a curve to a lineTo, and that behavior will soon go away.
+ void rotateTo(float leftJoinType, const SkPoint& anchorPoint, const SkPoint& controlPoint);
- // Records a stroke in fElememts.
- void recordStroke(Verb, int numSegmentsLog2);
+ const int fMaxTessellationSegments;
- // Records a join in fElememts with the previous stroke, if the cuurent contour is not empty.
- void recordLeftJoinIfNotEmpty(Verb joinType, SkVector nextNormal);
+ // These are raw pointers whose lifetimes are controlled outside this class.
+ GrMeshDrawOp::Target* const fTarget;
+ SkTArray<VertexChunk>* const fVertexChunkArray;
- void recordCapsIfAny();
+ // Variables related to the vertex chunk that we are currently filling.
+ int fCurrChunkVertexCapacity;
+ int fCurrChunkMinVertexAllocCount;
+ SkPoint* fCurrChunkVertexData;
+ // Variables related to the path that we are currently iterating.
float fCurrStrokeRadius;
- Verb fCurrStrokeJoinVerb;
+ float fCurrStrokeJoinType; // See GrTessellateStrokeShader for join type definitions .
SkPaint::Cap fCurrStrokeCapType;
- InstanceTallies* fCurrStrokeTallies = nullptr;
-
- // We implement miters by placing a triangle-shaped cap on top of a bevel join. This field tells
- // us what the miter limit is, restated in terms of how tall that triangle cap can be.
- float fMiterMaxCapHeightOverWidth;
-
// Any curvature on the original curve gets magnified on the outer edge of the stroke,
// proportional to how thick the stroke radius is. This field tells us the maximum curvature we
// can tolerate using the current stroke radius, before linearization artifacts begin to appear
@@ -134,47 +122,12 @@
// section with strong curvature into lineTo's with round joins in between.)
float fMaxCurvatureCosTheta;
- int fCurrContourFirstPtIdx;
- int fCurrContourFirstNormalIdx;
-
- SkDEBUGCODE(bool fInsideContour = false);
-
- const int fMaxTessellationSegments;
- SkSTArray<128, Verb, true> fVerbs;
- SkSTArray<128, Parameter, true> fParams;
- SkSTArray<128, SkPoint, true> fPoints;
- SkSTArray<128, SkVector, true> fNormals;
+ // Variables related to the specific contour that we are currently iterating.
+ bool fHasPreviousSegment = false;
+ SkPoint fCurrContourStartPoint;
+ SkPoint fCurrContourFirstControlPoint;
+ SkPoint fLastControlPoint;
+ SkPoint fCurrentPoint;
};
-inline GrStrokeGeometry::InstanceTallies GrStrokeGeometry::InstanceTallies::operator+(
- const InstanceTallies& t) const {
- InstanceTallies ret;
- for (int i = 0; i <= kMaxNumLinearSegmentsLog2; ++i) {
- ret.fStrokes[i] = fStrokes[i] + t.fStrokes[i];
- }
- ret.fTriangles = fTriangles + t.fTriangles;
- ret.fConics = fConics + t.fConics;
- return ret;
-}
-
-inline bool GrStrokeGeometry::IsInternalJoinVerb(Verb verb) {
- switch (verb) {
- case Verb::kInternalBevelJoin:
- case Verb::kInternalRoundJoin:
- return true;
- case Verb::kBeginPath:
- case Verb::kLinearStroke:
- case Verb::kQuadraticStroke:
- case Verb::kCubicStroke:
- case Verb::kRotate:
- case Verb::kBevelJoin:
- case Verb::kMiterJoin:
- case Verb::kRoundJoin:
- case Verb::kSquareCap:
- case Verb::kRoundCap:
- case Verb::kEndContour:
- return false;
- }
- SK_ABORT("Invalid GrStrokeGeometry::Verb.");
-}
#endif
diff --git a/src/gpu/tessellate/GrTessellateStrokeOp.cpp b/src/gpu/tessellate/GrTessellateStrokeOp.cpp
index f8b274f..854d4ab 100644
--- a/src/gpu/tessellate/GrTessellateStrokeOp.cpp
+++ b/src/gpu/tessellate/GrTessellateStrokeOp.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Google LLC.
+ * Copyright 2020 Google LLC.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
@@ -44,8 +44,7 @@
GrAAType aaType)
: GrDrawOp(ClassID())
, fPathStrokes(transform_path(viewMatrix, path), transform_stroke(viewMatrix, stroke))
- , fNumVerbs(path.countVerbs())
- , fNumPoints(path.countPoints())
+ , fTotalCombinedVerbCnt(path.countVerbs())
, fColor(get_paint_constant_blended_color(paint))
, fAAType(aaType)
, fProcessors(std::move(paint)) {
@@ -100,8 +99,7 @@
SkASSERT(fMiterLimitOrZero == 0 || fMiterLimitOrZero == op->fMiterLimitOrZero);
fMiterLimitOrZero = op->fMiterLimitOrZero;
}
- fNumVerbs += op->fNumVerbs;
- fNumPoints += op->fNumPoints;
+ fTotalCombinedVerbCnt += op->fTotalCombinedVerbCnt;
return CombineResult::kMerged;
}
@@ -110,226 +108,14 @@
GrAppliedClip*, const GrXferProcessor::DstProxyView&) {
}
-static SkPoint lerp(const SkPoint& a, const SkPoint& b, float T) {
- SkASSERT(1 != T); // The below does not guarantee lerp(a, b, 1) === b.
- return (b - a) * T + a;
-}
-
-static void write_line(SkPoint* patch, const SkPoint& p0, const SkPoint& p1) {
- patch[0] = p0;
- patch[1] = lerp(p0, p1, 1/3.f);
- patch[2] = lerp(p0, p1, 2/3.f);
- patch[3] = p1;
-}
-
-static void write_quadratic(SkPoint* patch, const SkPoint pts[]) {
- patch[0] = pts[0];
- patch[1] = lerp(pts[0], pts[1], 2/3.f);
- patch[2] = lerp(pts[1], pts[2], 1/3.f);
- patch[3] = pts[2];
-}
-
-static void write_loop(SkPoint* patch, const SkPoint& intersectionPoint,
- const SkPoint lastControlPt, const SkPoint& nextControlPt) {
- patch[0] = intersectionPoint;
- patch[1] = lastControlPt;
- patch[2] = nextControlPt;
- patch[3] = intersectionPoint;
-}
-
-static void write_square_cap(SkPoint* patch, const SkPoint& endPoint,
- const SkPoint controlPoint, float strokeRadius) {
- SkVector v = (endPoint - controlPoint);
- v.normalize();
- SkPoint capPoint = endPoint + v*strokeRadius;
- // Construct a line that incorporates controlPoint so we get a water tight edge with the rest of
- // the stroke. The cubic will technically step outside the cap, but we will force it to only
- // have one segment, giving edges only at the endpoints.
- patch[0] = endPoint;
- patch[1] = controlPoint;
- // Straddle the midpoint of the cap because the tessellated geometry emits a center point at
- // T=.5, and we need to ensure that point stays inside the cap.
- patch[2] = endPoint + capPoint - controlPoint;
- patch[3] = capPoint;
-}
-
void GrTessellateStrokeOp::onPrepare(GrOpFlushState* flushState) {
- // Rebuild the stroke using GrStrokeGeometry.
- GrStrokeGeometry strokeGeometry(flushState->caps().shaderCaps()->maxTessellationSegments(),
- fNumPoints, fNumVerbs);
+ GrStrokeGeometry strokeGeometry(flushState, &fVertexChunks, fTotalCombinedVerbCnt);
for (auto& [path, stroke] : fPathStrokes) {
- float strokeRadius = stroke.getWidth() * .5f;
- GrStrokeGeometry::InstanceTallies tallies = GrStrokeGeometry::InstanceTallies();
- strokeGeometry.beginPath(stroke, strokeRadius * 2, &tallies);
- SkPathVerb previousVerb = SkPathVerb::kClose;
- for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
- switch (verb) {
- case SkPathVerb::kMove:
- if (previousVerb != SkPathVerb::kClose) {
- strokeGeometry.capContourAndExit();
- }
- strokeGeometry.moveTo(pts[0]);
- break;
- case SkPathVerb::kClose:
- strokeGeometry.closeContour();
- break;
- case SkPathVerb::kLine:
- strokeGeometry.lineTo(pts[1]);
- break;
- case SkPathVerb::kQuad:
- strokeGeometry.quadraticTo(pts);
- break;
- case SkPathVerb::kCubic:
- strokeGeometry.cubicTo(pts);
- break;
- case SkPathVerb::kConic:
- SkUNREACHABLE;
- }
- previousVerb = verb;
- }
- if (previousVerb != SkPathVerb::kClose) {
- strokeGeometry.capContourAndExit();
- }
+ strokeGeometry.addPath(path, stroke);
}
-
- auto vertexData = static_cast<SkPoint*>(flushState->makeVertexSpace(
- sizeof(SkPoint), strokeGeometry.verbs().count() * 2 * 5, &fVertexBuffer, &fBaseVertex));
- if (!vertexData) {
- return;
- }
-
- using Verb = GrStrokeGeometry::Verb;
-
- // Dump GrStrokeGeometry into tessellation patches.
- //
- // This loop is only a temporary adapter for GrStrokeGeometry so we can bootstrap the
- // tessellation shaders. Once the shaders are landed and tested, we will overhaul
- // GrStrokeGeometry and remove this loop.
- int i = 0;
- const SkTArray<SkPoint, true>& pathPts = strokeGeometry.points();
- auto pendingJoin = Verb::kEndContour;
- SkPoint firstJoinControlPoint = {0, 0};
- SkPoint lastJoinControlPoint = {0, 0};
- bool hasFirstControlPoint = false;
- float currStrokeRadius = 0;
- auto pathStrokesIter = fPathStrokes.begin();
- for (auto verb : strokeGeometry.verbs()) {
- SkPoint patch[4];
- float overrideNumSegments = 0;
- switch (verb) {
- case Verb::kBeginPath:
- SkASSERT(pathStrokesIter != fPathStrokes.end());
- pendingJoin = Verb::kEndContour;
- firstJoinControlPoint = {0, 0};
- lastJoinControlPoint = {0, 0};
- hasFirstControlPoint = false;
- currStrokeRadius = (*pathStrokesIter).fStroke.getWidth() * .5f;
- ++pathStrokesIter;
- continue;
- case Verb::kRoundJoin:
- case Verb::kInternalRoundJoin:
- case Verb::kMiterJoin:
- case Verb::kBevelJoin:
- case Verb::kInternalBevelJoin:
- pendingJoin = verb;
- continue;
- case Verb::kLinearStroke:
- write_line(patch, pathPts[i], pathPts[i+1]);
- ++i;
- break;
- case Verb::kQuadraticStroke:
- write_quadratic(patch, &pathPts[i]);
- i += 2;
- break;
- case Verb::kCubicStroke:
- memcpy(patch, &pathPts[i], sizeof(SkPoint) * 4);
- i += 3;
- break;
- case Verb::kRotate:
- write_loop(patch, pathPts[i], pathPts[i+1], pathPts[i]*2 - pathPts[i+1]);
- i += 2;
- break;
- case Verb::kSquareCap: {
- SkASSERT(pendingJoin == Verb::kEndContour);
- write_square_cap(patch, pathPts[i], lastJoinControlPoint, currStrokeRadius);
- // This cubic steps outside the cap, but if we force it to only have one segment, we
- // will just get the rectangular cap.
- overrideNumSegments = 1;
- break;
- }
- case Verb::kRoundCap:
- // A round cap is the same thing as a 180-degree round join.
- SkASSERT(pendingJoin == Verb::kEndContour);
- pendingJoin = Verb::kRoundJoin;
- write_loop(patch, pathPts[i], lastJoinControlPoint, lastJoinControlPoint);
- break;
- case Verb::kEndContour:
- // Final join
- write_loop(patch, pathPts[i], firstJoinControlPoint, lastJoinControlPoint);
- ++i;
- break;
- }
-
- SkPoint c1 = (patch[1] == patch[0]) ? patch[2] : patch[1];
- SkPoint c2 = (patch[2] == patch[3]) ? patch[1] : patch[2];
-
- if (pendingJoin != Verb::kEndContour) {
- vertexData[0] = patch[0];
- vertexData[1] = lastJoinControlPoint;
- vertexData[2] = c1;
- vertexData[3] = patch[0];
- switch (pendingJoin) {
- case Verb::kBevelJoin:
- vertexData[4].set(1, currStrokeRadius);
- break;
- case Verb::kMiterJoin:
- vertexData[4].set(2, currStrokeRadius);
- break;
- case Verb::kRoundJoin:
- vertexData[4].set(3, currStrokeRadius);
- break;
- case Verb::kInternalRoundJoin:
- case Verb::kInternalBevelJoin:
- default:
- vertexData[4].set(4, currStrokeRadius);
- break;
- }
- vertexData += 5;
- fVertexCount += 5;
- pendingJoin = Verb::kEndContour;
- }
-
- if (verb != Verb::kRoundCap) {
- if (!hasFirstControlPoint) {
- firstJoinControlPoint = c1;
- hasFirstControlPoint = true;
- }
- lastJoinControlPoint = c2;
- }
-
- if (verb == Verb::kEndContour) {
- // Temporary hack for this adapter in case the next contour is a round cap.
- lastJoinControlPoint = firstJoinControlPoint;
- hasFirstControlPoint = false;
- } else if (verb != Verb::kRotate && verb != Verb::kRoundCap) {
- memcpy(vertexData, patch, sizeof(SkPoint) * 4);
- vertexData[4].set(-overrideNumSegments, currStrokeRadius);
- vertexData += 5;
- fVertexCount += 5;
- }
- }
- SkASSERT(pathStrokesIter == fPathStrokes.end());
-
- SkASSERT(fVertexCount <= strokeGeometry.verbs().count() * 2 * 5);
- flushState->putBackVertices(strokeGeometry.verbs().count() * 2 * 5 - fVertexCount,
- sizeof(SkPoint));
}
void GrTessellateStrokeOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
- if (!fVertexBuffer) {
- return;
- }
-
GrPipeline::InitArgs initArgs;
if (GrAAType::kNone != fAAType) {
initArgs.fInputFlags |= GrPipeline::InputFlags::kHWAntialias;
@@ -345,9 +131,14 @@
GrTessellateStrokeShader strokeShader(fViewMatrix, fColor, fMiterLimitOrZero);
GrPathShader::ProgramInfo programInfo(flushState->writeView(), &pipeline, &strokeShader);
- flushState->bindPipelineAndScissorClip(programInfo, this->bounds() /*chainBounds??*/);
+ SkASSERT(chainBounds == this->bounds());
+ flushState->bindPipelineAndScissorClip(programInfo, this->bounds());
flushState->bindTextures(strokeShader, nullptr, pipeline);
- flushState->bindBuffers(nullptr, nullptr, std::move(fVertexBuffer));
- flushState->draw(fVertexCount, fBaseVertex);
+ for (const auto& chunk : fVertexChunks) {
+ if (chunk.fVertexBuffer) {
+ flushState->bindBuffers(nullptr, nullptr, std::move(chunk.fVertexBuffer));
+ flushState->draw(chunk.fVertexCount, chunk.fBaseVertex);
+ }
+ }
}
diff --git a/src/gpu/tessellate/GrTessellateStrokeOp.h b/src/gpu/tessellate/GrTessellateStrokeOp.h
index 5326164..23b0f2e 100644
--- a/src/gpu/tessellate/GrTessellateStrokeOp.h
+++ b/src/gpu/tessellate/GrTessellateStrokeOp.h
@@ -11,6 +11,7 @@
#include "include/core/SkStrokeRec.h"
#include "src/gpu/GrSTArenaList.h"
#include "src/gpu/ops/GrDrawOp.h"
+#include "src/gpu/tessellate/GrStrokeGeometry.h"
// Renders opaque, constant-color strokes by decomposing them into standalone tessellation patches.
// Each patch is either a "cubic" (single stroked bezier curve with butt caps) or a "join". Requires
@@ -45,8 +46,7 @@
};
GrSTArenaList<PathStroke> fPathStrokes;
- int fNumVerbs;
- int fNumPoints;
+ int fTotalCombinedVerbCnt;
SkPMColor4f fColor;
const SkMatrix fViewMatrix = SkMatrix::I();
@@ -54,9 +54,8 @@
float fMiterLimitOrZero = 0; // Zero if there is not a stroke with a miter join type.
GrProcessorSet fProcessors;
- sk_sp<const GrBuffer> fVertexBuffer;
- int fVertexCount = 0;
- int fBaseVertex;
+ // S=1 because we will almost always fit everything into one single chunk.
+ SkSTArray<1, GrStrokeGeometry::VertexChunk> fVertexChunks;
friend class GrOpMemoryPool; // For ctor.
};
diff --git a/src/gpu/tessellate/GrTessellateStrokeShader.h b/src/gpu/tessellate/GrTessellateStrokeShader.h
index 262a40d..5e5fbc4 100644
--- a/src/gpu/tessellate/GrTessellateStrokeShader.h
+++ b/src/gpu/tessellate/GrTessellateStrokeShader.h
@@ -38,9 +38,16 @@
// tessellationPatchVertexCount of 5.
class GrTessellateStrokeShader : public GrPathShader {
public:
+ constexpr static float kBevelJoinType = 1;
+ constexpr static float kMiterJoinType = 2;
+ constexpr static float kRoundJoinType = 3;
+ constexpr static float kInternalRoundJoinType = 4;
+
+ constexpr static int kNumVerticesPerPatch = 5;
+
GrTessellateStrokeShader(const SkMatrix& viewMatrix, SkPMColor4f color, float miterLimitOrZero)
: GrPathShader(kTessellate_GrTessellateStrokeShader_ClassID, viewMatrix,
- GrPrimitiveType::kPatches, 5)
+ GrPrimitiveType::kPatches, kNumVerticesPerPatch)
, fColor(color)
, fMiterLimitOrZero(miterLimitOrZero) {
constexpr static Attribute kInputPointAttrib{"inputPoint", kFloat2_GrVertexAttribType,