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>
This commit is contained in:
parent
d140e767b8
commit
9eea916c05
@ -26,7 +26,6 @@
|
|||||||
#include "src/gpu/GrResourceProvider.h"
|
#include "src/gpu/GrResourceProvider.h"
|
||||||
#include "src/gpu/ccpr/GrCCCoverageProcessor.h"
|
#include "src/gpu/ccpr/GrCCCoverageProcessor.h"
|
||||||
#include "src/gpu/ccpr/GrCCFillGeometry.h"
|
#include "src/gpu/ccpr/GrCCFillGeometry.h"
|
||||||
#include "src/gpu/ccpr/GrCCStroker.h"
|
|
||||||
#include "src/gpu/ccpr/GrGSCoverageProcessor.h"
|
#include "src/gpu/ccpr/GrGSCoverageProcessor.h"
|
||||||
#include "src/gpu/ccpr/GrVSCoverageProcessor.h"
|
#include "src/gpu/ccpr/GrVSCoverageProcessor.h"
|
||||||
#include "src/gpu/geometry/GrPathUtils.h"
|
#include "src/gpu/geometry/GrPathUtils.h"
|
||||||
@ -66,7 +65,7 @@ class CCPRGeometryView : public Sample {
|
|||||||
|
|
||||||
void updateGpuData();
|
void updateGpuData();
|
||||||
|
|
||||||
PrimitiveType fPrimitiveType = PrimitiveType::kTriangles;
|
PrimitiveType fPrimitiveType = PrimitiveType::kCubics;
|
||||||
SkCubicType fCubicType;
|
SkCubicType fCubicType;
|
||||||
SkMatrix fCubicKLM;
|
SkMatrix fCubicKLM;
|
||||||
|
|
||||||
@ -75,7 +74,9 @@ class CCPRGeometryView : public Sample {
|
|||||||
|
|
||||||
float fConicWeight = .5;
|
float fConicWeight = .5;
|
||||||
float fStrokeWidth = 40;
|
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<TriPointInstance> fTriPointInstances;
|
||||||
SkTArray<QuadPointInstance> fQuadPointInstances;
|
SkTArray<QuadPointInstance> fQuadPointInstances;
|
||||||
@ -176,14 +177,18 @@ static void draw_klm_line(int w, int h, SkCanvas* canvas, const SkScalar line[3]
|
|||||||
void CCPRGeometryView::onDrawContent(SkCanvas* canvas) {
|
void CCPRGeometryView::onDrawContent(SkCanvas* canvas) {
|
||||||
canvas->clear(SK_ColorBLACK);
|
canvas->clear(SK_ColorBLACK);
|
||||||
|
|
||||||
if (!fDoStroke) {
|
SkPaint outlinePaint;
|
||||||
SkPaint outlinePaint;
|
outlinePaint.setColor(0xff808080);
|
||||||
outlinePaint.setColor(0x80ffffff);
|
outlinePaint.setStyle(SkPaint::kStroke_Style);
|
||||||
outlinePaint.setStyle(SkPaint::kStroke_Style);
|
if (fDoStroke) {
|
||||||
|
outlinePaint.setStrokeWidth(fStrokeWidth);
|
||||||
|
} else {
|
||||||
outlinePaint.setStrokeWidth(0);
|
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
|
#if 0
|
||||||
SkPaint gridPaint;
|
SkPaint gridPaint;
|
||||||
@ -200,7 +205,18 @@ void CCPRGeometryView::onDrawContent(SkCanvas* canvas) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
SkString caption;
|
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.
|
// Render coverage count.
|
||||||
auto ctx = canvas->recordingContext();
|
auto ctx = canvas->recordingContext();
|
||||||
SkASSERT(ctx);
|
SkASSERT(ctx);
|
||||||
@ -222,18 +238,6 @@ void CCPRGeometryView::onDrawContent(SkCanvas* canvas) {
|
|||||||
paint.setPorterDuffXPFactory(SkBlendMode::kSrcOver);
|
paint.setPorterDuffXPFactory(SkBlendMode::kSrcOver);
|
||||||
rtc->drawRect(nullptr, std::move(paint), GrAA::kNo, SkMatrix::I(),
|
rtc->drawRect(nullptr, std::move(paint), GrAA::kNo, SkMatrix::I(),
|
||||||
SkRect::MakeIWH(this->width(), this->height()));
|
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 {
|
} else {
|
||||||
caption = "Use GPU backend to visualize geometry.";
|
caption = "Use GPU backend to visualize geometry.";
|
||||||
}
|
}
|
||||||
@ -345,8 +349,8 @@ void CCPRGeometryView::updateGpuData() {
|
|||||||
void CCPRGeometryView::DrawCoverageCountOp::onExecute(GrOpFlushState* state,
|
void CCPRGeometryView::DrawCoverageCountOp::onExecute(GrOpFlushState* state,
|
||||||
const SkRect& chainBounds) {
|
const SkRect& chainBounds) {
|
||||||
GrResourceProvider* rp = state->resourceProvider();
|
GrResourceProvider* rp = state->resourceProvider();
|
||||||
auto direct = state->gpu()->getContext();
|
|
||||||
#ifdef SK_GL
|
#ifdef SK_GL
|
||||||
|
auto direct = state->gpu()->getContext();
|
||||||
GrGLGpu* glGpu = GrBackendApi::kOpenGL == direct->backend()
|
GrGLGpu* glGpu = GrBackendApi::kOpenGL == direct->backend()
|
||||||
? static_cast<GrGLGpu*>(state->gpu())
|
? static_cast<GrGLGpu*>(state->gpu())
|
||||||
: nullptr;
|
: nullptr;
|
||||||
@ -370,52 +374,30 @@ void CCPRGeometryView::DrawCoverageCountOp::onExecute(GrOpFlushState* state,
|
|||||||
|
|
||||||
GrOpsRenderPass* renderPass = state->opsRenderPass();
|
GrOpsRenderPass* renderPass = state->opsRenderPass();
|
||||||
|
|
||||||
if (!fView->fDoStroke) {
|
for (int i = 0; i < proc->numSubpasses(); ++i) {
|
||||||
for (int i = 0; i < proc->numSubpasses(); ++i) {
|
proc->reset(fView->fPrimitiveType, i, rp);
|
||||||
proc->reset(fView->fPrimitiveType, i, rp);
|
proc->bindPipeline(state, pipeline, this->bounds());
|
||||||
proc->bindPipeline(state, pipeline, this->bounds());
|
|
||||||
|
|
||||||
if (PrimitiveType::kCubics == fView->fPrimitiveType ||
|
if (PrimitiveType::kCubics == fView->fPrimitiveType ||
|
||||||
PrimitiveType::kConics == fView->fPrimitiveType) {
|
PrimitiveType::kConics == fView->fPrimitiveType) {
|
||||||
sk_sp<GrGpuBuffer> instBuff(rp->createBuffer(
|
sk_sp<GrGpuBuffer> instBuff(rp->createBuffer(
|
||||||
fView->fQuadPointInstances.count() * sizeof(QuadPointInstance),
|
fView->fQuadPointInstances.count() * sizeof(QuadPointInstance),
|
||||||
GrGpuBufferType::kVertex, kDynamic_GrAccessPattern,
|
GrGpuBufferType::kVertex, kDynamic_GrAccessPattern,
|
||||||
fView->fQuadPointInstances.begin()));
|
fView->fQuadPointInstances.begin()));
|
||||||
if (!fView->fQuadPointInstances.empty() && instBuff) {
|
if (!fView->fQuadPointInstances.empty() && instBuff) {
|
||||||
proc->bindBuffers(renderPass, std::move(instBuff));
|
proc->bindBuffers(renderPass, std::move(instBuff));
|
||||||
proc->drawInstances(renderPass, fView->fQuadPointInstances.count(), 0);
|
proc->drawInstances(renderPass, fView->fQuadPointInstances.count(), 0);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sk_sp<GrGpuBuffer> instBuff(rp->createBuffer(
|
sk_sp<GrGpuBuffer> instBuff(rp->createBuffer(
|
||||||
fView->fTriPointInstances.count() * sizeof(TriPointInstance),
|
fView->fTriPointInstances.count() * sizeof(TriPointInstance),
|
||||||
GrGpuBufferType::kVertex, kDynamic_GrAccessPattern,
|
GrGpuBufferType::kVertex, kDynamic_GrAccessPattern,
|
||||||
fView->fTriPointInstances.begin()));
|
fView->fTriPointInstances.begin()));
|
||||||
if (!fView->fTriPointInstances.empty() && instBuff) {
|
if (!fView->fTriPointInstances.empty() && instBuff) {
|
||||||
proc->bindBuffers(renderPass, std::move(instBuff));
|
proc->bindBuffers(renderPass, std::move(instBuff));
|
||||||
proc->drawInstances(renderPass, fView->fTriPointInstances.count(), 0);
|
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
|
#ifdef SK_GL
|
||||||
@ -511,6 +493,17 @@ bool CCPRGeometryView::onChar(SkUnichar unichar) {
|
|||||||
if (unichar == 'S') {
|
if (unichar == 'S') {
|
||||||
fDoStroke = !fDoStroke;
|
fDoStroke = !fDoStroke;
|
||||||
this->updateAndInval();
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -11,22 +11,24 @@
|
|||||||
#include "include/private/SkNx.h"
|
#include "include/private/SkNx.h"
|
||||||
#include "src/core/SkGeometry.h"
|
#include "src/core/SkGeometry.h"
|
||||||
#include "src/core/SkMathPriv.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
|
// This is the maximum distance in pixels that we can stray from the edge of a stroke when
|
||||||
// converting it to flat line segments.
|
// converting it to flat line segments.
|
||||||
static constexpr float kMaxErrorFromLinearization = 1/8.f;
|
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) {
|
static inline float length(const Sk2f& n) {
|
||||||
Sk2f nn = n*n;
|
Sk2f nn = n*n;
|
||||||
return SkScalarSqrt(nn[0] + nn[1]);
|
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) {
|
static inline void transpose(const Sk2f& a, const Sk2f& b, Sk2f* X, Sk2f* Y) {
|
||||||
float transpose[4];
|
float transpose[4];
|
||||||
a.store(transpose);
|
a.store(transpose);
|
||||||
@ -34,13 +36,6 @@ static inline void transpose(const Sk2f& a, const Sk2f& b, Sk2f* X, Sk2f* Y) {
|
|||||||
Sk2f::Load2(transpose, X, Y);
|
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) {
|
static inline float calc_curvature_costheta(const Sk2f& leftTan, const Sk2f& rightTan) {
|
||||||
Sk2f X, Y;
|
Sk2f X, Y;
|
||||||
transpose(leftTan, rightTan, &X, &Y);
|
transpose(leftTan, rightTan, &X, &Y);
|
||||||
@ -49,80 +44,194 @@ static inline float calc_curvature_costheta(const Sk2f& leftTan, const Sk2f& rig
|
|||||||
return (dotprod[0] + dotprod[1]) * invlength[0] * invlength[1];
|
return (dotprod[0] + dotprod[1]) * invlength[0] * invlength[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
static GrStrokeGeometry::Verb join_verb_from_join(SkPaint::Join join) {
|
void GrStrokeGeometry::allocVertexChunk(int minVertexAllocCount) {
|
||||||
using Verb = GrStrokeGeometry::Verb;
|
VertexChunk* chunk = &fVertexChunkArray->push_back();
|
||||||
switch (join) {
|
fCurrChunkVertexData = (SkPoint*)fTarget->makeVertexSpaceAtLeast(
|
||||||
case SkPaint::kBevel_Join:
|
sizeof(SkPoint), minVertexAllocCount, minVertexAllocCount, &chunk->fVertexBuffer,
|
||||||
return Verb::kBevelJoin;
|
&chunk->fBaseVertex, &fCurrChunkVertexCapacity);
|
||||||
case SkPaint::kMiter_Join:
|
fCurrChunkMinVertexAllocCount = minVertexAllocCount;
|
||||||
return Verb::kMiterJoin;
|
|
||||||
case SkPaint::kRound_Join:
|
|
||||||
return Verb::kRoundJoin;
|
|
||||||
}
|
|
||||||
SK_ABORT("Invalid SkPaint::Join.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GrStrokeGeometry::beginPath(const SkStrokeRec& stroke, float strokeDevWidth,
|
SkPoint* GrStrokeGeometry::reservePatch() {
|
||||||
InstanceTallies* tallies) {
|
constexpr static int kNumVerticesPerPatch = GrTessellateStrokeShader::kNumVerticesPerPatch;
|
||||||
SkASSERT(!fInsideContour);
|
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).
|
// Client should have already converted the stroke to device space (i.e. width=1 for hairline).
|
||||||
SkASSERT(strokeDevWidth > 0);
|
SkASSERT(strokeDevWidth > 0);
|
||||||
|
|
||||||
fCurrStrokeRadius = strokeDevWidth/2;
|
fCurrStrokeRadius = strokeDevWidth/2;
|
||||||
fCurrStrokeJoinVerb = join_verb_from_join(stroke.getJoin());
|
fCurrStrokeJoinType = join_type_from_join(stroke.getJoin());
|
||||||
fCurrStrokeCapType = stroke.getCap();
|
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
|
// Find the angle of curvature where the arc height above a simple line from point A to point B
|
||||||
// is equal to kMaxErrorFromLinearization.
|
// is equal to kMaxErrorFromLinearization.
|
||||||
float r = std::max(1 - kMaxErrorFromLinearization / fCurrStrokeRadius, 0.f);
|
float r = std::max(1 - kMaxErrorFromLinearization / fCurrStrokeRadius, 0.f);
|
||||||
fMaxCurvatureCosTheta = 2*r*r - 1;
|
fMaxCurvatureCosTheta = 2*r*r - 1;
|
||||||
|
|
||||||
fCurrContourFirstPtIdx = -1;
|
fHasPreviousSegment = false;
|
||||||
fCurrContourFirstNormalIdx = -1;
|
|
||||||
|
|
||||||
fVerbs.push_back(Verb::kBeginPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GrStrokeGeometry::moveTo(SkPoint pt) {
|
void GrStrokeGeometry::moveTo(const SkPoint& pt) {
|
||||||
SkASSERT(!fInsideContour);
|
fHasPreviousSegment = false;
|
||||||
fCurrContourFirstPtIdx = fPoints.count();
|
fCurrContourStartPoint = pt;
|
||||||
fCurrContourFirstNormalIdx = fNormals.count();
|
|
||||||
fPoints.push_back(pt);
|
|
||||||
SkDEBUGCODE(fInsideContour = true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GrStrokeGeometry::lineTo(SkPoint pt) {
|
void GrStrokeGeometry::lineTo(const SkPoint& p0, const SkPoint& p1) {
|
||||||
SkASSERT(fInsideContour);
|
this->lineTo(fCurrStrokeJoinType, p0, p1);
|
||||||
this->lineTo(fCurrStrokeJoinVerb, pt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GrStrokeGeometry::lineTo(Verb leftJoinVerb, SkPoint pt) {
|
void GrStrokeGeometry::lineTo(float leftJoinType, const SkPoint& pt0, const SkPoint& pt1) {
|
||||||
Sk2f tan = Sk2f::Load(&pt) - Sk2f::Load(&fPoints.back());
|
Sk2f p0 = Sk2f::Load(&pt0);
|
||||||
if ((tan == 0).allTrue()) {
|
Sk2f p1 = Sk2f::Load(&pt1);
|
||||||
|
if ((p0 == p1).allTrue()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this->writeCubicSegment(leftJoinType, p0, lerp(p0, p1, 1/3.f), lerp(p0, p1, 2/3.f), p1, 1);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GrStrokeGeometry::quadraticTo(const SkPoint P[3]) {
|
void GrStrokeGeometry::quadraticTo(const SkPoint P[3]) {
|
||||||
SkASSERT(fInsideContour);
|
this->quadraticTo(fCurrStrokeJoinType, P, SkFindQuadMaxCurvature(P));
|
||||||
this->quadraticTo(fCurrStrokeJoinVerb, P, SkFindQuadMaxCurvature(P));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wang's formula for quadratics (1985) gives us the number of evenly spaced (in the parametric
|
// Wang's formula for quadratics (1985) gives us the number of evenly spaced (in the parametric
|
||||||
@ -134,7 +243,7 @@ static inline float wangs_formula_quadratic(const Sk2f& p0, const Sk2f& p1, cons
|
|||||||
return SkScalarCeilToInt(f);
|
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 p0 = Sk2f::Load(P);
|
||||||
Sk2f p1 = Sk2f::Load(P+1);
|
Sk2f p1 = Sk2f::Load(P+1);
|
||||||
Sk2f p2 = Sk2f::Load(P+2);
|
Sk2f p2 = Sk2f::Load(P+2);
|
||||||
@ -146,16 +255,13 @@ void GrStrokeGeometry::quadraticTo(Verb leftJoinVerb, const SkPoint P[3], float
|
|||||||
// an issue.
|
// an issue.
|
||||||
if ((tan0.abs() < SK_ScalarNearlyZero).allTrue() || // p0 ~= p1
|
if ((tan0.abs() < SK_ScalarNearlyZero).allTrue() || // p0 ~= p1
|
||||||
(tan1.abs() < SK_ScalarNearlyZero).allTrue()) { // p1 ~= p2
|
(tan1.abs() < SK_ScalarNearlyZero).allTrue()) { // p1 ~= p2
|
||||||
this->lineTo(leftJoinVerb, P[2]);
|
this->lineTo(leftJoinType, P[0], P[2]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SkPoint normals[2];
|
|
||||||
normalize2(tan0, tan1, normals);
|
|
||||||
|
|
||||||
// Decide how many flat line segments to chop the curve into.
|
// Decide how many flat line segments to chop the curve into.
|
||||||
int numSegments = wangs_formula_quadratic(p0, p1, p2);
|
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.
|
// At + B gives a vector tangent to the quadratic.
|
||||||
Sk2f A = p0 - p1*2 + p2;
|
Sk2f A = p0 - p1*2 + p2;
|
||||||
@ -193,22 +299,22 @@ void GrStrokeGeometry::quadraticTo(Verb leftJoinVerb, const SkPoint P[3], float
|
|||||||
|
|
||||||
if (leftT > 0) {
|
if (leftT > 0) {
|
||||||
SkChopQuadAt(currQuadratic, ptsBuffer, leftT);
|
SkChopQuadAt(currQuadratic, ptsBuffer, leftT);
|
||||||
this->quadraticTo(leftJoinVerb, ptsBuffer, /*maxCurvatureT=*/1);
|
this->quadraticTo(leftJoinType, ptsBuffer, /*maxCurvatureT=*/1);
|
||||||
if (rightT < 1) {
|
if (rightT < 1) {
|
||||||
rightT = (rightT - leftT) / (1 - leftT);
|
rightT = (rightT - leftT) / (1 - leftT);
|
||||||
}
|
}
|
||||||
currQuadratic = ptsBuffer + 2;
|
currQuadratic = ptsBuffer + 2;
|
||||||
} else {
|
} else {
|
||||||
this->rotateTo(leftJoinVerb, normals[0], currQuadratic[1]);
|
this->rotateTo(leftJoinType, currQuadratic[0], currQuadratic[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rightT < 1) {
|
if (rightT < 1) {
|
||||||
SkChopQuadAt(currQuadratic, ptsBuffer, rightT);
|
SkChopQuadAt(currQuadratic, ptsBuffer, rightT);
|
||||||
this->lineTo(Verb::kInternalRoundJoin, ptsBuffer[2]);
|
this->lineTo(kInternalRoundJoinType, ptsBuffer[0], ptsBuffer[2]);
|
||||||
this->quadraticTo(Verb::kInternalRoundJoin, ptsBuffer + 2, /*maxCurvatureT=*/0);
|
this->quadraticTo(kInternalRoundJoinType, ptsBuffer + 2, /*maxCurvatureT=*/0);
|
||||||
} else {
|
} else {
|
||||||
this->lineTo(Verb::kInternalRoundJoin, currQuadratic[2]);
|
this->lineTo(kInternalRoundJoinType, currQuadratic[0], currQuadratic[2]);
|
||||||
this->rotateTo(Verb::kInternalRoundJoin, normals[1],
|
this->rotateTo(kInternalRoundJoinType, currQuadratic[2],
|
||||||
currQuadratic[2]*2 - currQuadratic[1]);
|
currQuadratic[2]*2 - currQuadratic[1]);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -216,24 +322,18 @@ void GrStrokeGeometry::quadraticTo(Verb leftJoinVerb, const SkPoint P[3], float
|
|||||||
if (numSegments > fMaxTessellationSegments) {
|
if (numSegments > fMaxTessellationSegments) {
|
||||||
SkPoint ptsBuffer[5];
|
SkPoint ptsBuffer[5];
|
||||||
SkChopQuadAt(P, ptsBuffer, 0.5f);
|
SkChopQuadAt(P, ptsBuffer, 0.5f);
|
||||||
this->quadraticTo(leftJoinVerb, ptsBuffer, 0);
|
this->quadraticTo(leftJoinType, ptsBuffer, 0);
|
||||||
this->quadraticTo(Verb::kInternalRoundJoin, ptsBuffer + 3, 0);
|
this->quadraticTo(kInternalRoundJoinType, ptsBuffer + 3, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->recordLeftJoinIfNotEmpty(leftJoinVerb, normals[0]);
|
this->writeCubicSegment(leftJoinType, p0, lerp(p0, p1, 2/3.f), lerp(p1, p2, 1/3.f), p2);
|
||||||
fNormals.push_back_n(2, normals);
|
|
||||||
|
|
||||||
this->recordStroke(Verb::kQuadraticStroke, SkNextLog2(numSegments));
|
|
||||||
p1.store(&fPoints.push_back());
|
|
||||||
p2.store(&fPoints.push_back());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GrStrokeGeometry::cubicTo(const SkPoint P[4]) {
|
void GrStrokeGeometry::cubicTo(const SkPoint P[4]) {
|
||||||
SkASSERT(fInsideContour);
|
|
||||||
float roots[3];
|
float roots[3];
|
||||||
int numRoots = SkFindCubicMaxCurvature(P, roots);
|
int numRoots = SkFindCubicMaxCurvature(P, roots);
|
||||||
this->cubicTo(fCurrStrokeJoinVerb, P,
|
this->cubicTo(fCurrStrokeJoinType, P,
|
||||||
numRoots > 0 ? roots[numRoots/2] : 0,
|
numRoots > 0 ? roots[numRoots/2] : 0,
|
||||||
numRoots > 1 ? roots[0] : kLeftMaxCurvatureNone,
|
numRoots > 1 ? roots[0] : kLeftMaxCurvatureNone,
|
||||||
numRoots > 2 ? roots[2] : kRightMaxCurvatureNone);
|
numRoots > 2 ? roots[2] : kRightMaxCurvatureNone);
|
||||||
@ -250,8 +350,8 @@ static inline float wangs_formula_cubic(const Sk2f& p0, const Sk2f& p1, const Sk
|
|||||||
return SkScalarCeilToInt(f);
|
return SkScalarCeilToInt(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GrStrokeGeometry::cubicTo(Verb leftJoinVerb, const SkPoint P[4], float maxCurvatureT,
|
void GrStrokeGeometry::cubicTo(float leftJoinType, const SkPoint P[4], float maxCurvatureT,
|
||||||
float leftMaxCurvatureT, float rightMaxCurvatureT) {
|
float leftMaxCurvatureT, float rightMaxCurvatureT) {
|
||||||
Sk2f p0 = Sk2f::Load(P);
|
Sk2f p0 = Sk2f::Load(P);
|
||||||
Sk2f p1 = Sk2f::Load(P+1);
|
Sk2f p1 = Sk2f::Load(P+1);
|
||||||
Sk2f p2 = Sk2f::Load(P+2);
|
Sk2f p2 = Sk2f::Load(P+2);
|
||||||
@ -265,7 +365,7 @@ void GrStrokeGeometry::cubicTo(Verb leftJoinVerb, const SkPoint P[4], float maxC
|
|||||||
p1 = p0;
|
p1 = p0;
|
||||||
tan0 = p2 - p0;
|
tan0 = p2 - p0;
|
||||||
if ((tan0.abs() < SK_ScalarNearlyZero).allTrue()) { // p0 ~= p1 ~= p2
|
if ((tan0.abs() < SK_ScalarNearlyZero).allTrue()) { // p0 ~= p1 ~= p2
|
||||||
this->lineTo(leftJoinVerb, P[3]);
|
this->lineTo(leftJoinType, P[0], P[3]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -274,17 +374,14 @@ void GrStrokeGeometry::cubicTo(Verb leftJoinVerb, const SkPoint P[4], float maxC
|
|||||||
tan1 = p3 - p1;
|
tan1 = p3 - p1;
|
||||||
if ((tan1.abs() < SK_ScalarNearlyZero).allTrue() || // p1 ~= p2 ~= p3
|
if ((tan1.abs() < SK_ScalarNearlyZero).allTrue() || // p1 ~= p2 ~= p3
|
||||||
(p0 == p1).allTrue()) { // p0 ~= p1 AND p2 ~= p3
|
(p0 == p1).allTrue()) { // p0 ~= p1 AND p2 ~= p3
|
||||||
this->lineTo(leftJoinVerb, P[3]);
|
this->lineTo(leftJoinType, P[0], P[3]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SkPoint normals[2];
|
|
||||||
normalize2(tan0, tan1, normals);
|
|
||||||
|
|
||||||
// Decide how many flat line segments to chop the curve into.
|
// Decide how many flat line segments to chop the curve into.
|
||||||
int numSegments = wangs_formula_cubic(p0, p1, p2, p3);
|
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
|
// 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.)
|
// minus an irrelevant scale by 3, since all we care about is the direction.)
|
||||||
@ -328,7 +425,7 @@ void GrStrokeGeometry::cubicTo(Verb leftJoinVerb, const SkPoint P[4], float maxC
|
|||||||
|
|
||||||
if (leftT > 0) {
|
if (leftT > 0) {
|
||||||
SkChopCubicAt(currCubic, ptsBuffer, leftT);
|
SkChopCubicAt(currCubic, ptsBuffer, leftT);
|
||||||
this->cubicTo(leftJoinVerb, ptsBuffer, /*maxCurvatureT=*/1,
|
this->cubicTo(leftJoinType, ptsBuffer, /*maxCurvatureT=*/1,
|
||||||
(kLeftMaxCurvatureNone != leftMaxCurvatureT)
|
(kLeftMaxCurvatureNone != leftMaxCurvatureT)
|
||||||
? leftMaxCurvatureT/leftT : kLeftMaxCurvatureNone,
|
? leftMaxCurvatureT/leftT : kLeftMaxCurvatureNone,
|
||||||
kRightMaxCurvatureNone);
|
kRightMaxCurvatureNone);
|
||||||
@ -341,144 +438,73 @@ void GrStrokeGeometry::cubicTo(Verb leftJoinVerb, const SkPoint P[4], float maxC
|
|||||||
currCubic = ptsBuffer + 3;
|
currCubic = ptsBuffer + 3;
|
||||||
} else {
|
} else {
|
||||||
SkPoint c1 = (ptsBuffer[1] == ptsBuffer[0]) ? ptsBuffer[2] : ptsBuffer[1];
|
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) {
|
if (rightT < 1) {
|
||||||
SkChopCubicAt(currCubic, ptsBuffer, rightT);
|
SkChopCubicAt(currCubic, ptsBuffer, rightT);
|
||||||
this->lineTo(Verb::kInternalRoundJoin, ptsBuffer[3]);
|
this->lineTo(kInternalRoundJoinType, ptsBuffer[0], ptsBuffer[3]);
|
||||||
currCubic = ptsBuffer + 3;
|
currCubic = ptsBuffer + 3;
|
||||||
this->cubicTo(Verb::kInternalRoundJoin, currCubic, /*maxCurvatureT=*/0,
|
this->cubicTo(kInternalRoundJoinType, currCubic, /*maxCurvatureT=*/0,
|
||||||
kLeftMaxCurvatureNone, kRightMaxCurvatureNone);
|
kLeftMaxCurvatureNone, kRightMaxCurvatureNone);
|
||||||
} else {
|
} else {
|
||||||
this->lineTo(Verb::kInternalRoundJoin, currCubic[3]);
|
this->lineTo(kInternalRoundJoinType, currCubic[0], currCubic[3]);
|
||||||
SkPoint c2 = (currCubic[2] == currCubic[3]) ? currCubic[1] : currCubic[2];
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recurse and check the other two points of max curvature, if any.
|
// Recurse and check the other two points of max curvature, if any.
|
||||||
if (kRightMaxCurvatureNone != rightMaxCurvatureT) {
|
if (kRightMaxCurvatureNone != rightMaxCurvatureT) {
|
||||||
this->cubicTo(leftJoinVerb, P, rightMaxCurvatureT, leftMaxCurvatureT,
|
this->cubicTo(leftJoinType, P, rightMaxCurvatureT, leftMaxCurvatureT,
|
||||||
kRightMaxCurvatureNone);
|
kRightMaxCurvatureNone);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (kLeftMaxCurvatureNone != leftMaxCurvatureT) {
|
if (kLeftMaxCurvatureNone != leftMaxCurvatureT) {
|
||||||
SkASSERT(kRightMaxCurvatureNone == rightMaxCurvatureT);
|
SkASSERT(kRightMaxCurvatureNone == rightMaxCurvatureT);
|
||||||
this->cubicTo(leftJoinVerb, P, leftMaxCurvatureT, kLeftMaxCurvatureNone,
|
this->cubicTo(leftJoinType, P, leftMaxCurvatureT, kLeftMaxCurvatureNone,
|
||||||
kRightMaxCurvatureNone);
|
kRightMaxCurvatureNone);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (numSegments > fMaxTessellationSegments) {
|
if (numSegments > fMaxTessellationSegments) {
|
||||||
SkPoint ptsBuffer[7];
|
SkPoint ptsBuffer[7];
|
||||||
SkChopCubicAt(P, ptsBuffer, 0.5f);
|
SkChopCubicAt(P, ptsBuffer, 0.5f);
|
||||||
this->cubicTo(leftJoinVerb, ptsBuffer, 0, kLeftMaxCurvatureNone, kRightMaxCurvatureNone);
|
this->cubicTo(leftJoinType, ptsBuffer, 0, kLeftMaxCurvatureNone, kRightMaxCurvatureNone);
|
||||||
this->cubicTo(Verb::kInternalRoundJoin, ptsBuffer + 3, 0, kLeftMaxCurvatureNone,
|
this->cubicTo(kInternalRoundJoinType, ptsBuffer + 3, 0, kLeftMaxCurvatureNone,
|
||||||
kRightMaxCurvatureNone);
|
kRightMaxCurvatureNone);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->recordLeftJoinIfNotEmpty(leftJoinVerb, normals[0]);
|
this->writeCubicSegment(leftJoinType, p0, p1, p2, p3);
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GrStrokeGeometry::recordStroke(Verb verb, int numSegmentsLog2) {
|
void GrStrokeGeometry::rotateTo(float leftJoinType, const SkPoint& anchorPoint,
|
||||||
SkASSERT(Verb::kLinearStroke != verb || 0 == numSegmentsLog2);
|
const SkPoint& controlPoint) {
|
||||||
fVerbs.push_back(verb);
|
// Effectively rotate the current normal by drawing a zero length, 1-segment cubic.
|
||||||
if (Verb::kLinearStroke != verb) {
|
// writeCubicSegment automatically adds the necessary join and the zero length cubic serves as
|
||||||
fParams.push_back().fNumLinearSegmentsLog2 = numSegmentsLog2;
|
// a glue that guarantees a water tight rasterized edge between the new join and the segment
|
||||||
}
|
// that comes after the rotate.
|
||||||
++fCurrStrokeTallies->fStrokes[numSegmentsLog2];
|
SkPoint pts[4] = {anchorPoint, controlPoint, anchorPoint*2 - controlPoint, anchorPoint};
|
||||||
|
this->writeCubicSegment(leftJoinType, pts, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GrStrokeGeometry::rotateTo(Verb leftJoinVerb, SkVector normal, SkPoint controlPoint) {
|
void GrStrokeGeometry::close() {
|
||||||
SkASSERT(fPoints.count() > fCurrContourFirstPtIdx);
|
if (!fHasPreviousSegment) {
|
||||||
this->recordLeftJoinIfNotEmpty(leftJoinVerb, normal);
|
// Draw caps instead of closing if the subpath is zero length:
|
||||||
fVerbs.push_back(Verb::kRotate);
|
//
|
||||||
fPoints.push_back(controlPoint);
|
// "Any zero length subpath ... shall be stroked if the 'stroke-linecap' property has a
|
||||||
fPoints.push_back(fPoints[fPoints.count() - 2]);
|
// value of round or square producing respectively a circle or a square."
|
||||||
fNormals.push_back(normal);
|
//
|
||||||
}
|
// (https://www.w3.org/TR/SVG11/painting.html#StrokeProperties)
|
||||||
|
//
|
||||||
void GrStrokeGeometry::recordLeftJoinIfNotEmpty(Verb joinVerb, SkVector nextNormal) {
|
this->writeCaps();
|
||||||
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) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Verb capVerb;
|
// Draw a line back to the beginning. (This will be discarded if
|
||||||
if (SkPaint::kSquare_Cap == fCurrStrokeCapType) {
|
// fCurrentPoint == fCurrContourStartPoint.)
|
||||||
if (fCurrStrokeRadius * SK_ScalarRoot2Over2 < kMaxErrorFromLinearization) {
|
this->lineTo(fCurrStrokeJoinType, fCurrentPoint, fCurrContourStartPoint);
|
||||||
return;
|
this->writeJoin(fCurrStrokeJoinType, fCurrContourStartPoint, fLastControlPoint,
|
||||||
}
|
fCurrContourFirstControlPoint);
|
||||||
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]);
|
|
||||||
}
|
}
|
||||||
|
@ -11,120 +11,108 @@
|
|||||||
#include "include/core/SkPaint.h"
|
#include "include/core/SkPaint.h"
|
||||||
#include "include/core/SkPoint.h"
|
#include "include/core/SkPoint.h"
|
||||||
#include "include/private/SkTArray.h"
|
#include "include/private/SkTArray.h"
|
||||||
|
#include "src/gpu/ops/GrMeshDrawOp.h"
|
||||||
|
#include "src/gpu/tessellate/GrTessellateStrokeShader.h"
|
||||||
|
|
||||||
class SkStrokeRec;
|
class SkStrokeRec;
|
||||||
|
|
||||||
/**
|
// This is an RAII class that expands strokes into tessellation patches for consumption by
|
||||||
* This class converts post-transform stroked paths into a set of independent strokes, joins, and
|
// GrTessellateStrokeShader. The provided GrMeshDrawOp::Target must not be used externally for the
|
||||||
* caps that map directly to GPU instances.
|
// 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 {
|
class GrStrokeGeometry {
|
||||||
public:
|
public:
|
||||||
static constexpr int kMaxNumLinearSegmentsLog2 = 15;
|
// 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.
|
||||||
GrStrokeGeometry(int maxTessellationSegments, int numSkPoints = 0, int numSkVerbs = 0)
|
struct VertexChunk {
|
||||||
: fMaxTessellationSegments(maxTessellationSegments)
|
sk_sp<const GrBuffer> fVertexBuffer;
|
||||||
, fVerbs(numSkVerbs * 5/2) // Reserve for a 2.5x expansion in verbs. (Joins get their
|
int fVertexCount = 0;
|
||||||
// own separate verb in our representation.)
|
int fBaseVertex;
|
||||||
, 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.
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const SkTArray<Verb, true>& verbs() const { SkASSERT(!fInsideContour); return fVerbs; }
|
// Stores raw pointers to the provided target and vertexChunkArray, which this class will use
|
||||||
const SkTArray<Parameter, true>& params() const { SkASSERT(!fInsideContour); return fParams; }
|
// and push to as addPath is called. The caller is responsible to bind and draw each chunk that
|
||||||
const SkTArray<SkPoint, true>& points() const { SkASSERT(!fInsideContour); return fPoints; }
|
// gets pushed to the array. (See GrTessellateStrokeShader.)
|
||||||
const SkTArray<SkVector, true>& normals() const { SkASSERT(!fInsideContour); return fNormals; }
|
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.
|
// "Releases" the target to be used externally again by putting back any unused pre-allocated
|
||||||
struct InstanceTallies {
|
// vertices.
|
||||||
int fStrokes[kMaxNumLinearSegmentsLog2 + 1];
|
~GrStrokeGeometry() {
|
||||||
int fTriangles;
|
fTarget->putBackVertices(fCurrChunkVertexCapacity - fVertexChunkArray->back().fVertexCount,
|
||||||
int fConics;
|
sizeof(SkPoint));
|
||||||
|
}
|
||||||
|
|
||||||
InstanceTallies operator+(const InstanceTallies&) const;
|
void addPath(const SkPath&, const SkStrokeRec&);
|
||||||
};
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void lineTo(Verb leftJoinVerb, SkPoint);
|
void allocVertexChunk(int minVertexAllocCount);
|
||||||
void quadraticTo(Verb leftJoinVerb, const SkPoint[3], float maxCurvatureT);
|
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 kLeftMaxCurvatureNone = 1;
|
||||||
static constexpr float kRightMaxCurvatureNone = 0;
|
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);
|
float rightMaxCurvatureT);
|
||||||
|
|
||||||
// Pushes a new normal to fNormals and records a join, without changing the current position.
|
// TEMPORARY: Rotates the current control point without changing the current position.
|
||||||
void rotateTo(Verb leftJoinVerb, SkVector normal, SkPoint controlPoint);
|
// 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.
|
const int fMaxTessellationSegments;
|
||||||
void recordStroke(Verb, int numSegmentsLog2);
|
|
||||||
|
|
||||||
// Records a join in fElememts with the previous stroke, if the cuurent contour is not empty.
|
// These are raw pointers whose lifetimes are controlled outside this class.
|
||||||
void recordLeftJoinIfNotEmpty(Verb joinType, SkVector nextNormal);
|
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;
|
float fCurrStrokeRadius;
|
||||||
Verb fCurrStrokeJoinVerb;
|
float fCurrStrokeJoinType; // See GrTessellateStrokeShader for join type definitions .
|
||||||
SkPaint::Cap fCurrStrokeCapType;
|
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,
|
// 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
|
// 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
|
// can tolerate using the current stroke radius, before linearization artifacts begin to appear
|
||||||
@ -134,47 +122,12 @@ private:
|
|||||||
// section with strong curvature into lineTo's with round joins in between.)
|
// section with strong curvature into lineTo's with round joins in between.)
|
||||||
float fMaxCurvatureCosTheta;
|
float fMaxCurvatureCosTheta;
|
||||||
|
|
||||||
int fCurrContourFirstPtIdx;
|
// Variables related to the specific contour that we are currently iterating.
|
||||||
int fCurrContourFirstNormalIdx;
|
bool fHasPreviousSegment = false;
|
||||||
|
SkPoint fCurrContourStartPoint;
|
||||||
SkDEBUGCODE(bool fInsideContour = false);
|
SkPoint fCurrContourFirstControlPoint;
|
||||||
|
SkPoint fLastControlPoint;
|
||||||
const int fMaxTessellationSegments;
|
SkPoint fCurrentPoint;
|
||||||
SkSTArray<128, Verb, true> fVerbs;
|
|
||||||
SkSTArray<128, Parameter, true> fParams;
|
|
||||||
SkSTArray<128, SkPoint, true> fPoints;
|
|
||||||
SkSTArray<128, SkVector, true> fNormals;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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
|
#endif
|
||||||
|
@ -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
|
* Use of this source code is governed by a BSD-style license that can be
|
||||||
* found in the LICENSE file.
|
* found in the LICENSE file.
|
||||||
@ -44,8 +44,7 @@ GrTessellateStrokeOp::GrTessellateStrokeOp(const SkMatrix& viewMatrix, const SkP
|
|||||||
GrAAType aaType)
|
GrAAType aaType)
|
||||||
: GrDrawOp(ClassID())
|
: GrDrawOp(ClassID())
|
||||||
, fPathStrokes(transform_path(viewMatrix, path), transform_stroke(viewMatrix, stroke))
|
, fPathStrokes(transform_path(viewMatrix, path), transform_stroke(viewMatrix, stroke))
|
||||||
, fNumVerbs(path.countVerbs())
|
, fTotalCombinedVerbCnt(path.countVerbs())
|
||||||
, fNumPoints(path.countPoints())
|
|
||||||
, fColor(get_paint_constant_blended_color(paint))
|
, fColor(get_paint_constant_blended_color(paint))
|
||||||
, fAAType(aaType)
|
, fAAType(aaType)
|
||||||
, fProcessors(std::move(paint)) {
|
, fProcessors(std::move(paint)) {
|
||||||
@ -100,8 +99,7 @@ GrOp::CombineResult GrTessellateStrokeOp::onCombineIfPossible(GrOp* grOp,
|
|||||||
SkASSERT(fMiterLimitOrZero == 0 || fMiterLimitOrZero == op->fMiterLimitOrZero);
|
SkASSERT(fMiterLimitOrZero == 0 || fMiterLimitOrZero == op->fMiterLimitOrZero);
|
||||||
fMiterLimitOrZero = op->fMiterLimitOrZero;
|
fMiterLimitOrZero = op->fMiterLimitOrZero;
|
||||||
}
|
}
|
||||||
fNumVerbs += op->fNumVerbs;
|
fTotalCombinedVerbCnt += op->fTotalCombinedVerbCnt;
|
||||||
fNumPoints += op->fNumPoints;
|
|
||||||
|
|
||||||
return CombineResult::kMerged;
|
return CombineResult::kMerged;
|
||||||
}
|
}
|
||||||
@ -110,226 +108,14 @@ void GrTessellateStrokeOp::onPrePrepare(GrRecordingContext*, const GrSurfaceProx
|
|||||||
GrAppliedClip*, const GrXferProcessor::DstProxyView&) {
|
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) {
|
void GrTessellateStrokeOp::onPrepare(GrOpFlushState* flushState) {
|
||||||
// Rebuild the stroke using GrStrokeGeometry.
|
GrStrokeGeometry strokeGeometry(flushState, &fVertexChunks, fTotalCombinedVerbCnt);
|
||||||
GrStrokeGeometry strokeGeometry(flushState->caps().shaderCaps()->maxTessellationSegments(),
|
|
||||||
fNumPoints, fNumVerbs);
|
|
||||||
for (auto& [path, stroke] : fPathStrokes) {
|
for (auto& [path, stroke] : fPathStrokes) {
|
||||||
float strokeRadius = stroke.getWidth() * .5f;
|
strokeGeometry.addPath(path, stroke);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
void GrTessellateStrokeOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
|
||||||
if (!fVertexBuffer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GrPipeline::InitArgs initArgs;
|
GrPipeline::InitArgs initArgs;
|
||||||
if (GrAAType::kNone != fAAType) {
|
if (GrAAType::kNone != fAAType) {
|
||||||
initArgs.fInputFlags |= GrPipeline::InputFlags::kHWAntialias;
|
initArgs.fInputFlags |= GrPipeline::InputFlags::kHWAntialias;
|
||||||
@ -345,9 +131,14 @@ void GrTessellateStrokeOp::onExecute(GrOpFlushState* flushState, const SkRect& c
|
|||||||
GrTessellateStrokeShader strokeShader(fViewMatrix, fColor, fMiterLimitOrZero);
|
GrTessellateStrokeShader strokeShader(fViewMatrix, fColor, fMiterLimitOrZero);
|
||||||
GrPathShader::ProgramInfo programInfo(flushState->writeView(), &pipeline, &strokeShader);
|
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->bindTextures(strokeShader, nullptr, pipeline);
|
||||||
|
|
||||||
flushState->bindBuffers(nullptr, nullptr, std::move(fVertexBuffer));
|
for (const auto& chunk : fVertexChunks) {
|
||||||
flushState->draw(fVertexCount, fBaseVertex);
|
if (chunk.fVertexBuffer) {
|
||||||
|
flushState->bindBuffers(nullptr, nullptr, std::move(chunk.fVertexBuffer));
|
||||||
|
flushState->draw(chunk.fVertexCount, chunk.fBaseVertex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include "include/core/SkStrokeRec.h"
|
#include "include/core/SkStrokeRec.h"
|
||||||
#include "src/gpu/GrSTArenaList.h"
|
#include "src/gpu/GrSTArenaList.h"
|
||||||
#include "src/gpu/ops/GrDrawOp.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.
|
// 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
|
// Each patch is either a "cubic" (single stroked bezier curve with butt caps) or a "join". Requires
|
||||||
@ -45,8 +46,7 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
GrSTArenaList<PathStroke> fPathStrokes;
|
GrSTArenaList<PathStroke> fPathStrokes;
|
||||||
int fNumVerbs;
|
int fTotalCombinedVerbCnt;
|
||||||
int fNumPoints;
|
|
||||||
|
|
||||||
SkPMColor4f fColor;
|
SkPMColor4f fColor;
|
||||||
const SkMatrix fViewMatrix = SkMatrix::I();
|
const SkMatrix fViewMatrix = SkMatrix::I();
|
||||||
@ -54,9 +54,8 @@ private:
|
|||||||
float fMiterLimitOrZero = 0; // Zero if there is not a stroke with a miter join type.
|
float fMiterLimitOrZero = 0; // Zero if there is not a stroke with a miter join type.
|
||||||
GrProcessorSet fProcessors;
|
GrProcessorSet fProcessors;
|
||||||
|
|
||||||
sk_sp<const GrBuffer> fVertexBuffer;
|
// S=1 because we will almost always fit everything into one single chunk.
|
||||||
int fVertexCount = 0;
|
SkSTArray<1, GrStrokeGeometry::VertexChunk> fVertexChunks;
|
||||||
int fBaseVertex;
|
|
||||||
|
|
||||||
friend class GrOpMemoryPool; // For ctor.
|
friend class GrOpMemoryPool; // For ctor.
|
||||||
};
|
};
|
||||||
|
@ -38,9 +38,16 @@ class GrGLSLUniformHandler;
|
|||||||
// tessellationPatchVertexCount of 5.
|
// tessellationPatchVertexCount of 5.
|
||||||
class GrTessellateStrokeShader : public GrPathShader {
|
class GrTessellateStrokeShader : public GrPathShader {
|
||||||
public:
|
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)
|
GrTessellateStrokeShader(const SkMatrix& viewMatrix, SkPMColor4f color, float miterLimitOrZero)
|
||||||
: GrPathShader(kTessellate_GrTessellateStrokeShader_ClassID, viewMatrix,
|
: GrPathShader(kTessellate_GrTessellateStrokeShader_ClassID, viewMatrix,
|
||||||
GrPrimitiveType::kPatches, 5)
|
GrPrimitiveType::kPatches, kNumVerticesPerPatch)
|
||||||
, fColor(color)
|
, fColor(color)
|
||||||
, fMiterLimitOrZero(miterLimitOrZero) {
|
, fMiterLimitOrZero(miterLimitOrZero) {
|
||||||
constexpr static Attribute kInputPointAttrib{"inputPoint", kFloat2_GrVertexAttribType,
|
constexpr static Attribute kInputPointAttrib{"inputPoint", kFloat2_GrVertexAttribType,
|
||||||
|
Loading…
Reference in New Issue
Block a user