Revert "ccpr: Implement stroking with fine triangle strips"
This reverts commit 2f2757fa6ba8134330e05694d08907f6e37abb41.
Reason for revert: issues with DDL
Original change's description:
> ccpr: Implement stroking with fine triangle strips
>
> Implements strokes by linearizing the curve into fine triangle strips
> and interpolating a coverage ramp for edge AA. Each triangle in the
> strip emits either positive or negative coverage, depending on its
> winding direction. Joins and caps are drawn with the existing CCPR
> shaders for triangles and conics.
>
> Conic strokes and non-rigid-body transforms are not yet supported.
>
> Bug: skia:
> Change-Id: I45a819abd64e91c2b62e992587eb85c703e09e77
> Reviewed-on: https://skia-review.googlesource.com/148243
> Commit-Queue: Chris Dalton <csmartdalton@google.com>
> Reviewed-by: Brian Salomon <bsalomon@google.com>
> Reviewed-by: Allan MacKinnon <allanmac@google.com>
TBR=egdaniel@google.com,bsalomon@google.com,robertphillips@google.com,caryclark@google.com,csmartdalton@google.com,reed@google.com,allanmac@google.com
Change-Id: I1980b09976df8275817eaffb6766dbd9fd3e59c7
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: skia:
Reviewed-on: https://skia-review.googlesource.com/150980
Reviewed-by: Chris Dalton <csmartdalton@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/src/gpu/ccpr/GrCCAtlas.cpp b/src/gpu/ccpr/GrCCAtlas.cpp
index 4ef632a..988d5fa 100644
--- a/src/gpu/ccpr/GrCCAtlas.cpp
+++ b/src/gpu/ccpr/GrCCAtlas.cpp
@@ -134,16 +134,10 @@
return true;
}
-void GrCCAtlas::setFillBatchID(int id) {
+void GrCCAtlas::setUserBatchID(int id) {
// This can't be called anymore once makeRenderTargetContext() has been called.
SkASSERT(!fTextureProxy->isInstantiated());
- fFillBatchID = id;
-}
-
-void GrCCAtlas::setStrokeBatchID(int id) {
- // This can't be called anymore once makeRenderTargetContext() has been called.
- SkASSERT(!fTextureProxy->isInstantiated());
- fStrokeBatchID = id;
+ fUserBatchID = id;
}
static uint32_t next_atlas_unique_id() {
diff --git a/src/gpu/ccpr/GrCCAtlas.h b/src/gpu/ccpr/GrCCAtlas.h
index 6412895..1a7ba1f 100644
--- a/src/gpu/ccpr/GrCCAtlas.h
+++ b/src/gpu/ccpr/GrCCAtlas.h
@@ -57,12 +57,10 @@
bool addRect(const SkIRect& devIBounds, SkIVector* atlasOffset);
const SkISize& drawBounds() { return fDrawBounds; }
- // This is an optional space for the caller to jot down which user-defined batches to use when
+ // This is an optional space for the caller to jot down which user-defined batch to use when
// they render the content of this atlas.
- void setFillBatchID(int id);
- int getFillBatchID() const { return fFillBatchID; }
- void setStrokeBatchID(int id);
- int getStrokeBatchID() const { return fStrokeBatchID; }
+ void setUserBatchID(int id);
+ int getUserBatchID() const { return fUserBatchID; }
// Manages a unique resource cache key that gets assigned to the atlas texture. The unique key
// does not get assigned to the texture proxy until it is instantiated.
@@ -100,8 +98,7 @@
std::unique_ptr<Node> fTopNode;
SkISize fDrawBounds = {0, 0};
- int fFillBatchID;
- int fStrokeBatchID;
+ int fUserBatchID;
// Not every atlas will have a unique key -- a mainline CCPR one won't if we don't stash any
// paths, and only the first atlas in the stack is eligible to be stashed.
diff --git a/src/gpu/ccpr/GrCCClipPath.cpp b/src/gpu/ccpr/GrCCClipPath.cpp
index d70b4f7..5702a96 100644
--- a/src/gpu/ccpr/GrCCClipPath.cpp
+++ b/src/gpu/ccpr/GrCCClipPath.cpp
@@ -52,7 +52,7 @@
SkASSERT(this->isInitialized());
++specs->fNumClipPaths;
- specs->fRenderedPathStats[GrCCPerFlushResourceSpecs::kFillIdx].statPath(fDeviceSpacePath);
+ specs->fRenderedPathStats.statPath(fDeviceSpacePath);
SkIRect ibounds;
if (ibounds.intersect(fAccessRect, fPathDevIBounds)) {
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor.h b/src/gpu/ccpr/GrCCCoverageProcessor.h
index c51f743..e5b6bc1 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor.h
+++ b/src/gpu/ccpr/GrCCCoverageProcessor.h
@@ -52,7 +52,6 @@
void set(const SkPoint[3], const Sk2f& trans);
void set(const SkPoint&, const SkPoint&, const SkPoint&, const Sk2f& trans);
- void set(const Sk2f& P0, const Sk2f& P1, const Sk2f& P2, const Sk2f& trans);
};
// Defines a single primitive shape with 4 input points, or 3 input points plus a "weight"
@@ -65,7 +64,6 @@
void set(const SkPoint[4], float dx, float dy);
void setW(const SkPoint[3], const Sk2f& trans, float w);
void setW(const SkPoint&, const SkPoint&, const SkPoint&, const Sk2f& trans, float w);
- void setW(const Sk2f& P0, const Sk2f& P1, const Sk2f& P2, const Sk2f& trans, float w);
};
GrCCCoverageProcessor(GrResourceProvider* rp, PrimitiveType type)
@@ -300,15 +298,10 @@
inline void GrCCCoverageProcessor::TriPointInstance::set(const SkPoint& p0, const SkPoint& p1,
const SkPoint& p2, const Sk2f& trans) {
- Sk2f P0 = Sk2f::Load(&p0);
- Sk2f P1 = Sk2f::Load(&p1);
- Sk2f P2 = Sk2f::Load(&p2);
- this->set(P0, P1, P2, trans);
-}
-
-inline void GrCCCoverageProcessor::TriPointInstance::set(const Sk2f& P0, const Sk2f& P1,
- const Sk2f& P2, const Sk2f& trans) {
- Sk2f::Store3(this, P0 + trans, P1 + trans, P2 + trans);
+ Sk2f P0 = Sk2f::Load(&p0) + trans;
+ Sk2f P1 = Sk2f::Load(&p1) + trans;
+ Sk2f P2 = Sk2f::Load(&p2) + trans;
+ Sk2f::Store3(this, P0, P1, P2);
}
inline void GrCCCoverageProcessor::QuadPointInstance::set(const SkPoint p[4], float dx, float dy) {
@@ -326,17 +319,11 @@
inline void GrCCCoverageProcessor::QuadPointInstance::setW(const SkPoint& p0, const SkPoint& p1,
const SkPoint& p2, const Sk2f& trans,
float w) {
- Sk2f P0 = Sk2f::Load(&p0);
- Sk2f P1 = Sk2f::Load(&p1);
- Sk2f P2 = Sk2f::Load(&p2);
- this->setW(P0, P1, P2, trans, w);
-}
-
-inline void GrCCCoverageProcessor::QuadPointInstance::setW(const Sk2f& P0, const Sk2f& P1,
- const Sk2f& P2, const Sk2f& trans,
- float w) {
+ Sk2f P0 = Sk2f::Load(&p0) + trans;
+ Sk2f P1 = Sk2f::Load(&p1) + trans;
+ Sk2f P2 = Sk2f::Load(&p2) + trans;
Sk2f W = Sk2f(w);
- Sk2f::Store4(this, P0 + trans, P1 + trans, P2 + trans, W);
+ Sk2f::Store4(this, P0, P1, P2, W);
}
#endif
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.cpp b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
index 2ec8379..aed0672 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.cpp
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
@@ -28,82 +28,25 @@
return sk_64_mul(r.height(), r.width());
}
-std::unique_ptr<GrCCDrawPathsOp> GrCCDrawPathsOp::Make(
- GrContext* context, const SkIRect& clipIBounds, const SkMatrix& m, const GrShape& shape,
- GrPaint&& paint) {
- static constexpr int kPathCropThreshold = GrCoverageCountingPathRenderer::kPathCropThreshold;
-
- SkRect conservativeDevBounds;
- m.mapRect(&conservativeDevBounds, shape.bounds());
-
- const SkStrokeRec& stroke = shape.style().strokeRec();
- float strokeDevWidth = 0;
- float conservativeInflationRadius = 0;
- if (!stroke.isFillStyle()) {
- if (stroke.isHairlineStyle()) {
- strokeDevWidth = 1;
- } else {
- SkASSERT(m.isSimilarity()); // Otherwise matrixScaleFactor = m.getMaxScale().
- float matrixScaleFactor = SkVector::Length(m.getScaleX(), m.getSkewY());
- strokeDevWidth = stroke.getWidth() * matrixScaleFactor;
- }
- // Inflate for a minimum stroke width of 1. In some cases when the stroke is less than 1px
- // wide, we may inflate it to 1px and instead reduce the opacity.
- conservativeInflationRadius = SkStrokeRec::GetInflationRadius(
- stroke.getJoin(), stroke.getMiter(), stroke.getCap(), SkTMax(strokeDevWidth, 1.f));
- conservativeDevBounds.outset(conservativeInflationRadius, conservativeInflationRadius);
- }
-
- std::unique_ptr<GrCCDrawPathsOp> op;
- float conservativeSize = SkTMax(conservativeDevBounds.height(), conservativeDevBounds.width());
- if (conservativeSize > kPathCropThreshold) {
- // The path is too large. Crop it or analytic AA can run out of fp32 precision.
- SkPath croppedDevPath;
- shape.asPath(&croppedDevPath);
- croppedDevPath.transform(m, &croppedDevPath);
-
- SkIRect cropBox = clipIBounds;
- GrShape croppedDevShape;
- if (stroke.isFillStyle()) {
- GrCoverageCountingPathRenderer::CropPath(croppedDevPath, cropBox, &croppedDevPath);
- croppedDevShape = GrShape(croppedDevPath);
- conservativeDevBounds = croppedDevShape.bounds();
- } else {
- int r = SkScalarCeilToInt(conservativeInflationRadius);
- cropBox.outset(r, r);
- GrCoverageCountingPathRenderer::CropPath(croppedDevPath, cropBox, &croppedDevPath);
- SkStrokeRec devStroke = stroke;
- devStroke.setStrokeStyle(strokeDevWidth);
- croppedDevShape = GrShape(croppedDevPath, GrStyle(devStroke, nullptr));
- conservativeDevBounds = croppedDevPath.getBounds();
- conservativeDevBounds.outset(conservativeInflationRadius, conservativeInflationRadius);
- }
-
- // FIXME: This breaks local coords: http://skbug.com/8003
- return InternalMake(context, clipIBounds, SkMatrix::I(), croppedDevShape, strokeDevWidth,
- conservativeDevBounds, std::move(paint));
- }
-
- return InternalMake(context, clipIBounds, m, shape, strokeDevWidth, conservativeDevBounds,
- std::move(paint));
-}
-
-std::unique_ptr<GrCCDrawPathsOp> GrCCDrawPathsOp::InternalMake(
- GrContext* context, const SkIRect& clipIBounds, const SkMatrix& m, const GrShape& shape,
- float strokeDevWidth, const SkRect& conservativeDevBounds, GrPaint&& paint) {
- SkIRect shapeConservativeIBounds;
- conservativeDevBounds.roundOut(&shapeConservativeIBounds);
+std::unique_ptr<GrCCDrawPathsOp> GrCCDrawPathsOp::Make(GrContext* context,
+ const SkIRect& clipIBounds,
+ const SkMatrix& m,
+ const GrShape& shape,
+ const SkRect& devBounds,
+ GrPaint&& paint) {
+ SkIRect shapeDevIBounds;
+ devBounds.roundOut(&shapeDevIBounds); // GrCCPathParser might find slightly tighter bounds.
SkIRect maskDevIBounds;
Visibility maskVisibility;
- if (clipIBounds.contains(shapeConservativeIBounds)) {
- maskDevIBounds = shapeConservativeIBounds;
+ if (clipIBounds.contains(shapeDevIBounds)) {
+ maskDevIBounds = shapeDevIBounds;
maskVisibility = Visibility::kComplete;
} else {
- if (!maskDevIBounds.intersect(clipIBounds, shapeConservativeIBounds)) {
+ if (!maskDevIBounds.intersect(clipIBounds, shapeDevIBounds)) {
return nullptr;
}
- int64_t unclippedArea = area(shapeConservativeIBounds);
+ int64_t unclippedArea = area(shapeDevIBounds);
int64_t clippedArea = area(maskDevIBounds);
maskVisibility = (clippedArea >= unclippedArea/2 || unclippedArea < 100*100)
? Visibility::kMostlyComplete // i.e., visible enough to justify rendering the
@@ -113,24 +56,22 @@
GrOpMemoryPool* pool = context->contextPriv().opMemoryPool();
- return pool->allocate<GrCCDrawPathsOp>(m, shape, strokeDevWidth, shapeConservativeIBounds,
- maskDevIBounds, maskVisibility, conservativeDevBounds,
- std::move(paint));
+ return pool->allocate<GrCCDrawPathsOp>(m, shape, shapeDevIBounds, maskDevIBounds,
+ maskVisibility, devBounds, std::move(paint));
}
-GrCCDrawPathsOp::GrCCDrawPathsOp(const SkMatrix& m, const GrShape& shape, float strokeDevWidth,
- const SkIRect& shapeConservativeIBounds,
- const SkIRect& maskDevIBounds, Visibility maskVisibility,
- const SkRect& conservativeDevBounds, GrPaint&& paint)
+GrCCDrawPathsOp::GrCCDrawPathsOp(const SkMatrix& m, const GrShape& shape,
+ const SkIRect& shapeDevIBounds, const SkIRect& maskDevIBounds,
+ Visibility maskVisibility, const SkRect& devBounds,
+ GrPaint&& paint)
: GrDrawOp(ClassID())
, fViewMatrixIfUsingLocalCoords(has_coord_transforms(paint) ? m : SkMatrix::I())
- , fDraws(m, shape, strokeDevWidth, shapeConservativeIBounds, maskDevIBounds, maskVisibility,
- paint.getColor())
+ , fDraws(m, shape, shapeDevIBounds, maskDevIBounds, maskVisibility, paint.getColor())
, fProcessors(std::move(paint)) { // Paint must be moved after fetching its color above.
SkDEBUGCODE(fBaseInstance = -1);
// FIXME: intersect with clip bounds to (hopefully) improve batching.
// (This is nontrivial due to assumptions in generating the octagon cover geometry.)
- this->setBounds(conservativeDevBounds, GrOp::HasAABloat::kYes, GrOp::IsZeroArea::kNo);
+ this->setBounds(devBounds, GrOp::HasAABloat::kYes, GrOp::IsZeroArea::kNo);
}
GrCCDrawPathsOp::~GrCCDrawPathsOp() {
@@ -141,14 +82,12 @@
}
GrCCDrawPathsOp::SingleDraw::SingleDraw(const SkMatrix& m, const GrShape& shape,
- float strokeDevWidth,
- const SkIRect& shapeConservativeIBounds,
+ const SkIRect& shapeDevIBounds,
const SkIRect& maskDevIBounds, Visibility maskVisibility,
GrColor color)
: fMatrix(m)
, fShape(shape)
- , fStrokeDevWidth(strokeDevWidth)
- , fShapeConservativeIBounds(shapeConservativeIBounds)
+ , fShapeDevIBounds(shapeDevIBounds)
, fMaskDevIBounds(maskDevIBounds)
, fMaskVisibility(maskVisibility)
, fColor(color) {
@@ -172,39 +111,9 @@
GrDrawOp::RequiresDstTexture GrCCDrawPathsOp::finalize(const GrCaps& caps,
const GrAppliedClip* clip) {
SkASSERT(1 == fNumDraws); // There should only be one single path draw in this Op right now.
- SingleDraw* draw = &fDraws.head();
-
- const GrProcessorSet::Analysis& analysis = fProcessors.finalize(
- draw->fColor, GrProcessorAnalysisCoverage::kSingleChannel, clip, false, caps,
- &draw->fColor);
-
- // Lines start looking jagged when they get thinner than 1px. For thin strokes it looks better
- // if we can convert them to hairline (i.e., inflate the stroke width to 1px), and instead
- // reduce the opacity to create the illusion of thin-ness. This strategy also helps reduce
- // artifacts from coverage dilation when there are self intersections.
- if (analysis.isCompatibleWithCoverageAsAlpha() &&
- !draw->fShape.style().strokeRec().isFillStyle() && draw->fStrokeDevWidth < 1) {
- // Modifying the shape affects its cache key. The draw can't have a cache entry yet or else
- // our next step would invalidate it.
- SkASSERT(!draw->fCacheEntry);
- SkASSERT(SkStrokeRec::kStroke_Style == draw->fShape.style().strokeRec().getStyle());
-
- SkPath path;
- draw->fShape.asPath(&path);
-
- // Create a hairline version of our stroke.
- SkStrokeRec hairlineStroke = draw->fShape.style().strokeRec();
- hairlineStroke.setStrokeStyle(0);
-
- // How transparent does a 1px stroke have to be in order to appear as thin as the real one?
- GrColor coverageAsAlpha = GrColorPackA4(SkScalarFloorToInt(draw->fStrokeDevWidth * 255));
-
- draw->fShape = GrShape(path, GrStyle(hairlineStroke, nullptr));
- draw->fStrokeDevWidth = 1;
- // fShapeConservativeIBounds already accounted for this possibility of inflating the stroke.
- draw->fColor = GrColorMul(draw->fColor, coverageAsAlpha);
- }
-
+ GrProcessorSet::Analysis analysis =
+ fProcessors.finalize(fDraws.head().fColor, GrProcessorAnalysisCoverage::kSingleChannel,
+ clip, false, caps, &fDraws.head().fColor);
return RequiresDstTexture(analysis.requiresDstTexture());
}
@@ -271,11 +180,8 @@
// can copy it into a new 8-bit atlas and keep it in the resource cache.
if (stashedAtlasKey.isValid() && stashedAtlasKey == cacheEntry->atlasKey()) {
SkASSERT(!cacheEntry->hasCachedAtlas());
- int idx = (draw.fShape.style().strokeRec().isFillStyle())
- ? GrCCPerFlushResourceSpecs::kFillIdx
- : GrCCPerFlushResourceSpecs::kStrokeIdx;
- ++specs->fNumCopiedPaths[idx];
- specs->fCopyPathStats[idx].statPath(path);
+ ++specs->fNumCopiedPaths;
+ specs->fCopyPathStats.statPath(path);
specs->fCopyAtlasSpecs.accountForSpace(cacheEntry->width(),
cacheEntry->height());
continue;
@@ -285,23 +191,18 @@
cacheEntry->resetAtlasKeyAndInfo();
}
- if (Visibility::kMostlyComplete == draw.fMaskVisibility && cacheEntry->hitCount() > 1) {
- int shapeSize = SkTMax(draw.fShapeConservativeIBounds.height(),
- draw.fShapeConservativeIBounds.width());
- if (shapeSize <= onFlushRP->caps()->maxRenderTargetSize()) {
- // We've seen this path before with a compatible matrix, and it's mostly
- // visible. Just render the whole mask so we can try to cache it.
- draw.fMaskDevIBounds = draw.fShapeConservativeIBounds;
- draw.fMaskVisibility = Visibility::kComplete;
- }
+ if (Visibility::kMostlyComplete == draw.fMaskVisibility && cacheEntry->hitCount() > 1 &&
+ SkTMax(draw.fShapeDevIBounds.height(),
+ draw.fShapeDevIBounds.width()) <= onFlushRP->caps()->maxRenderTargetSize()) {
+ // We've seen this path before with a compatible matrix, and it's mostly visible.
+ // Just render the whole mask so we can try to cache it.
+ draw.fMaskDevIBounds = draw.fShapeDevIBounds;
+ draw.fMaskVisibility = Visibility::kComplete;
}
}
- int idx = (draw.fShape.style().strokeRec().isFillStyle())
- ? GrCCPerFlushResourceSpecs::kFillIdx
- : GrCCPerFlushResourceSpecs::kStrokeIdx;
- ++specs->fNumRenderedPaths[idx];
- specs->fRenderedPathStats[idx].statPath(path);
+ ++specs->fNumRenderedPaths;
+ specs->fRenderedPathStats.statPath(path);
specs->fRenderedAtlasSpecs.accountForSpace(draw.fMaskDevIBounds.width(),
draw.fMaskDevIBounds.height());
}
@@ -318,8 +219,7 @@
SkPath path;
draw.fShape.asPath(&path);
- auto doEvenOddFill = DoEvenOddFill(draw.fShape.style().strokeRec().isFillStyle() &&
- SkPath::kEvenOdd_FillType == path.getFillType());
+ auto doEvenOddFill = DoEvenOddFill(SkPath::kEvenOdd_FillType == path.getFillType());
SkASSERT(SkPath::kEvenOdd_FillType == path.getFillType() ||
SkPath::kWinding_FillType == path.getFillType());
@@ -370,9 +270,9 @@
SkRect devBounds, devBounds45;
SkIRect devIBounds;
SkIVector devToAtlasOffset;
- if (auto atlas = resources->renderShapeInAtlas(
- draw.fMaskDevIBounds, draw.fMatrix, draw.fShape, draw.fStrokeDevWidth,
- &devBounds, &devBounds45, &devIBounds, &devToAtlasOffset)) {
+ if (auto atlas = resources->renderPathInAtlas(draw.fMaskDevIBounds, draw.fMatrix, path,
+ &devBounds, &devBounds45, &devIBounds,
+ &devToAtlasOffset)) {
this->recordInstance(atlas->textureProxy(), resources->nextPathInstanceIdx());
resources->appendDrawPathInstance().set(devBounds, devBounds45, devToAtlasOffset,
draw.fColor, doEvenOddFill);
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.h b/src/gpu/ccpr/GrCCDrawPathsOp.h
index 2716d59..40d9df4 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.h
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.h
@@ -30,7 +30,8 @@
SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrCCDrawPathsOp);
static std::unique_ptr<GrCCDrawPathsOp> Make(GrContext*, const SkIRect& clipIBounds,
- const SkMatrix&, const GrShape&, GrPaint&&);
+ const SkMatrix&, const GrShape&,
+ const SkRect& devBounds, GrPaint&&);
~GrCCDrawPathsOp() override;
const char* name() const override { return "GrCCDrawPathsOp"; }
@@ -69,35 +70,28 @@
private:
friend class GrOpMemoryPool;
- static std::unique_ptr<GrCCDrawPathsOp> InternalMake(GrContext*, const SkIRect& clipIBounds,
- const SkMatrix&, const GrShape&,
- float strokeDevWidth,
- const SkRect& conservativeDevBounds,
- GrPaint&&);
enum class Visibility {
kPartial,
kMostlyComplete, // (i.e., can we cache the whole path mask if we think it will be reused?)
kComplete
};
- GrCCDrawPathsOp(const SkMatrix&, const GrShape&, float strokeDevWidth,
- const SkIRect& shapeConservativeIBounds, const SkIRect& maskDevIBounds,
- Visibility maskVisibility, const SkRect& conservativeDevBounds, GrPaint&&);
+ GrCCDrawPathsOp(const SkMatrix&, const GrShape&, const SkIRect& shapeDevIBounds,
+ const SkIRect& maskDevIBounds, Visibility maskVisibility,
+ const SkRect& devBounds, GrPaint&&);
void recordInstance(GrTextureProxy* atlasProxy, int instanceIdx);
const SkMatrix fViewMatrixIfUsingLocalCoords;
struct SingleDraw {
- SingleDraw(const SkMatrix&, const GrShape&, float strokeDevWidth,
- const SkIRect& shapeConservativeIBounds, const SkIRect& maskDevIBounds,
- Visibility maskVisibility, GrColor);
+ SingleDraw(const SkMatrix&, const GrShape&, const SkIRect& shapeDevIBounds,
+ const SkIRect& maskDevIBounds, Visibility maskVisibility, GrColor);
~SingleDraw();
SkMatrix fMatrix;
- GrShape fShape;
- float fStrokeDevWidth;
- const SkIRect fShapeConservativeIBounds;
+ const GrShape fShape;
+ const SkIRect fShapeDevIBounds;
SkIRect fMaskDevIBounds;
Visibility fMaskVisibility;
GrColor fColor;
diff --git a/src/gpu/ccpr/GrCCFiller.cpp b/src/gpu/ccpr/GrCCFiller.cpp
index 1460077..cdace98 100644
--- a/src/gpu/ccpr/GrCCFiller.cpp
+++ b/src/gpu/ccpr/GrCCFiller.cpp
@@ -20,8 +20,9 @@
using TriPointInstance = GrCCCoverageProcessor::TriPointInstance;
using QuadPointInstance = GrCCCoverageProcessor::QuadPointInstance;
-GrCCFiller::GrCCFiller(int numPaths, int numSkPoints, int numSkVerbs, int numConicWeights)
- : fGeometry(numSkPoints, numSkVerbs, numConicWeights)
+GrCCFiller::GrCCFiller(int numPaths, const PathStats& pathStats)
+ : fGeometry(pathStats.fNumTotalSkPoints, pathStats.fNumTotalSkVerbs,
+ pathStats.fNumTotalConicWeights)
, fPathInfos(numPaths)
, fScissorSubBatches(numPaths)
, fTotalPrimitiveCounts{PrimitiveTallies(), PrimitiveTallies()} {
diff --git a/src/gpu/ccpr/GrCCFiller.h b/src/gpu/ccpr/GrCCFiller.h
index 45a03a4..40dc657 100644
--- a/src/gpu/ccpr/GrCCFiller.h
+++ b/src/gpu/ccpr/GrCCFiller.h
@@ -9,6 +9,7 @@
#define GrCCPathParser_DEFINED
#include "GrMesh.h"
+#include "SkPath.h"
#include "SkPathPriv.h"
#include "SkRect.h"
#include "SkRefCnt.h"
@@ -27,7 +28,16 @@
*/
class GrCCFiller {
public:
- GrCCFiller(int numPaths, int numSkPoints, int numSkVerbs, int numConicWeights);
+ struct PathStats {
+ int fMaxPointsPerPath = 0;
+ int fNumTotalSkPoints = 0;
+ int fNumTotalSkVerbs = 0;
+ int fNumTotalConicWeights = 0;
+
+ void statPath(const SkPath&);
+ };
+
+ GrCCFiller(int numPaths, const PathStats&);
// Parses a device-space SkPath into the current batch, using the SkPath's original verbs and
// 'deviceSpacePts'. Accepts an optional post-device-space translate for placement in an atlas.
@@ -112,4 +122,11 @@
mutable SkSTArray<32, SkIRect> fScissorRectScratchBuffer;
};
+inline void GrCCFiller::PathStats::statPath(const SkPath& path) {
+ fMaxPointsPerPath = SkTMax(fMaxPointsPerPath, path.countPoints());
+ fNumTotalSkPoints += path.countPoints();
+ fNumTotalSkVerbs += path.countVerbs();
+ fNumTotalConicWeights += SkPathPriv::ConicWeightCnt(path);
+}
+
#endif
diff --git a/src/gpu/ccpr/GrCCPathCache.cpp b/src/gpu/ccpr/GrCCPathCache.cpp
index 7d3fe2a..01781b0 100644
--- a/src/gpu/ccpr/GrCCPathCache.cpp
+++ b/src/gpu/ccpr/GrCCPathCache.cpp
@@ -45,47 +45,27 @@
// Produces a key that accounts both for a shape's path geometry, as well as any stroke/style.
class WriteStyledKey {
public:
- static constexpr int kStyledKeySizeInBytesIdx = 0;
- static constexpr int kStrokeWidthIdx = 1;
- static constexpr int kStrokeMiterIdx = 2;
- static constexpr int kStrokeCapJoinIdx = 3;
- static constexpr int kShapeUnstyledKeyIdx = 4;
-
- static constexpr int kStrokeKeyCount = 3; // [width, miterLimit, cap|join].
-
- WriteStyledKey(const GrShape& shape) : fShapeUnstyledKeyCount(shape.unstyledKeySize()) {}
+ WriteStyledKey(const GrShape& shape)
+ : fShapeUnstyledKeyCount(shape.unstyledKeySize())
+ , fStyleKeyCount(
+ GrStyle::KeySize(shape.style(), GrStyle::Apply::kPathEffectAndStrokeRec)) {}
// Returns the total number of uint32_t's to allocate for the key.
- int allocCountU32() const { return kShapeUnstyledKeyIdx + fShapeUnstyledKeyCount; }
+ int allocCountU32() const { return 2 + fShapeUnstyledKeyCount + fStyleKeyCount; }
// Writes the key to out[].
void write(const GrShape& shape, uint32_t* out) {
- out[kStyledKeySizeInBytesIdx] =
- (kStrokeKeyCount + fShapeUnstyledKeyCount) * sizeof(uint32_t);
-
- // Stroke key.
- // We don't use GrStyle::WriteKey() because it does not account for hairlines.
- // http://skbug.com/8273
- SkASSERT(!shape.style().hasPathEffect());
- const SkStrokeRec& stroke = shape.style().strokeRec();
- if (stroke.isFillStyle()) {
- // Use a value for width that won't collide with a valid fp32 value >= 0.
- out[kStrokeWidthIdx] = ~0;
- out[kStrokeMiterIdx] = out[kStrokeCapJoinIdx] = 0;
- } else {
- float width = stroke.getWidth(), miterLimit = stroke.getMiter();
- memcpy(&out[kStrokeWidthIdx], &width, sizeof(float));
- memcpy(&out[kStrokeMiterIdx], &miterLimit, sizeof(float));
- out[kStrokeCapJoinIdx] = (stroke.getCap() << 16) | stroke.getJoin();
- GR_STATIC_ASSERT(sizeof(out[kStrokeWidthIdx]) == sizeof(float));
- }
-
- // Shape unstyled key.
- shape.writeUnstyledKey(&out[kShapeUnstyledKeyIdx]);
+ // How many bytes remain in the key, beginning on out[1]?
+ out[0] = (1 + fShapeUnstyledKeyCount + fStyleKeyCount) * sizeof(uint32_t);
+ out[1] = fStyleKeyCount;
+ shape.writeUnstyledKey(&out[2]);
+ GrStyle::WriteKey(&out[2 + fShapeUnstyledKeyCount], shape.style(),
+ GrStyle::Apply::kPathEffectAndStrokeRec, 1);
}
private:
int fShapeUnstyledKeyCount;
+ int fStyleKeyCount;
};
}
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.cpp b/src/gpu/ccpr/GrCCPerFlushResources.cpp
index e761141..0ab4e5d 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.cpp
+++ b/src/gpu/ccpr/GrCCPerFlushResources.cpp
@@ -12,17 +12,12 @@
#include "GrOnFlushResourceProvider.h"
#include "GrSurfaceContextPriv.h"
#include "GrRenderTargetContext.h"
-#include "GrShape.h"
#include "SkMakeUnique.h"
#include "ccpr/GrCCPathCache.h"
using FillBatchID = GrCCFiller::BatchID;
-using StrokeBatchID = GrCCStroker::BatchID;
using PathInstance = GrCCPathProcessor::Instance;
-static constexpr int kFillIdx = GrCCPerFlushResourceSpecs::kFillIdx;
-static constexpr int kStrokeIdx = GrCCPerFlushResourceSpecs::kStrokeIdx;
-
namespace {
// Base class for an Op that renders a CCPR atlas.
@@ -106,35 +101,30 @@
static std::unique_ptr<GrDrawOp> Make(GrContext* context,
sk_sp<const GrCCPerFlushResources> resources,
- FillBatchID fillBatchID, StrokeBatchID strokeBatchID,
- const SkISize& drawBounds) {
+ FillBatchID batchID, const SkISize& drawBounds) {
GrOpMemoryPool* pool = context->contextPriv().opMemoryPool();
- return pool->allocate<RenderAtlasOp>(std::move(resources), fillBatchID, strokeBatchID,
- drawBounds);
+ return pool->allocate<RenderAtlasOp>(std::move(resources), batchID, drawBounds);
}
// GrDrawOp interface.
const char* name() const override { return "RenderAtlasOp (CCPR)"; }
void onExecute(GrOpFlushState* flushState) override {
- fResources->filler().drawFills(flushState, fFillBatchID, fDrawBounds);
- fResources->stroker().drawStrokes(flushState, fStrokeBatchID, fDrawBounds);
+ fResources->filler().drawFills(flushState, fBatchID, fDrawBounds);
}
private:
friend class ::GrOpMemoryPool; // for ctor
- RenderAtlasOp(sk_sp<const GrCCPerFlushResources> resources, FillBatchID fillBatchID,
- StrokeBatchID strokeBatchID, const SkISize& drawBounds)
+ RenderAtlasOp(sk_sp<const GrCCPerFlushResources> resources, FillBatchID batchID,
+ const SkISize& drawBounds)
: AtlasOp(ClassID(), std::move(resources), drawBounds)
- , fFillBatchID(fillBatchID)
- , fStrokeBatchID(strokeBatchID)
+ , fBatchID(batchID)
, fDrawBounds(SkIRect::MakeWH(drawBounds.width(), drawBounds.height())) {
}
- const FillBatchID fFillBatchID;
- const StrokeBatchID fStrokeBatchID;
+ const FillBatchID fBatchID;
const SkIRect fDrawBounds;
};
@@ -142,10 +132,8 @@
static int inst_buffer_count(const GrCCPerFlushResourceSpecs& specs) {
return specs.fNumCachedPaths +
- // Copies get two instances per draw: 1 copy + 1 draw.
- (specs.fNumCopiedPaths[kFillIdx] + specs.fNumCopiedPaths[kStrokeIdx]) * 2 +
- specs.fNumRenderedPaths[kFillIdx] + specs.fNumRenderedPaths[kStrokeIdx];
- // No clips in instance buffers.
+ specs.fNumCopiedPaths*2 + // 1 copy + 1 draw.
+ specs.fNumRenderedPaths;
}
GrCCPerFlushResources::GrCCPerFlushResources(GrOnFlushResourceProvider* onFlushRP,
@@ -153,15 +141,8 @@
// Overallocate by one point so we can call Sk4f::Store at the final SkPoint in the array.
// (See transform_path_pts below.)
// FIXME: instead use built-in instructions to write only the first two lanes of an Sk4f.
- : fLocalDevPtsBuffer(SkTMax(specs.fRenderedPathStats[kFillIdx].fMaxPointsPerPath,
- specs.fRenderedPathStats[kStrokeIdx].fMaxPointsPerPath) + 1)
- , fFiller(specs.fNumRenderedPaths[kFillIdx] + specs.fNumClipPaths,
- specs.fRenderedPathStats[kFillIdx].fNumTotalSkPoints,
- specs.fRenderedPathStats[kFillIdx].fNumTotalSkVerbs,
- specs.fRenderedPathStats[kFillIdx].fNumTotalConicWeights)
- , fStroker(specs.fNumRenderedPaths[kStrokeIdx],
- specs.fRenderedPathStats[kStrokeIdx].fNumTotalSkPoints,
- specs.fRenderedPathStats[kStrokeIdx].fNumTotalSkVerbs)
+ : fLocalDevPtsBuffer(specs.fRenderedPathStats.fMaxPointsPerPath + 1)
+ , fFiller(specs.fNumRenderedPaths + specs.fNumClipPaths, specs.fRenderedPathStats)
, fCopyAtlasStack(kAlpha_8_GrPixelConfig, specs.fCopyAtlasSpecs, onFlushRP->caps())
, fRenderedAtlasStack(kAlpha_half_GrPixelConfig, specs.fRenderedAtlasSpecs,
onFlushRP->caps())
@@ -170,8 +151,7 @@
, fInstanceBuffer(onFlushRP->makeBuffer(kVertex_GrBufferType,
inst_buffer_count(specs) * sizeof(PathInstance)))
, fNextCopyInstanceIdx(0)
- , fNextPathInstanceIdx(specs.fNumCopiedPaths[kFillIdx] +
- specs.fNumCopiedPaths[kStrokeIdx]) {
+ , fNextPathInstanceIdx(specs.fNumCopiedPaths) {
if (!fIndexBuffer) {
SkDebugf("WARNING: failed to allocate CCPR index buffer. No paths will be drawn.\n");
return;
@@ -186,8 +166,7 @@
}
fPathInstanceData = static_cast<PathInstance*>(fInstanceBuffer->map());
SkASSERT(fPathInstanceData);
- SkDEBUGCODE(fEndCopyInstance =
- specs.fNumCopiedPaths[kFillIdx] + specs.fNumCopiedPaths[kStrokeIdx]);
+ SkDEBUGCODE(fEndCopyInstance = specs.fNumCopiedPaths);
SkDEBUGCODE(fEndPathInstance = inst_buffer_count(specs));
}
@@ -201,7 +180,7 @@
if (GrCCAtlas* retiredAtlas = fCopyAtlasStack.addRect(entry.devIBounds(), newAtlasOffset)) {
// We did not fit in the previous copy atlas and it was retired. We will render the copies
// up until fNextCopyInstanceIdx into the retired atlas during finalize().
- retiredAtlas->setFillBatchID(fNextCopyInstanceIdx);
+ retiredAtlas->setUserBatchID(fNextCopyInstanceIdx);
}
fPathInstanceData[fNextCopyInstanceIdx++].set(entry, *newAtlasOffset, GrColor_WHITE, evenOdd);
@@ -258,29 +237,20 @@
bottomRightPts[1].y());
}
-const GrCCAtlas* GrCCPerFlushResources::renderShapeInAtlas(
- const SkIRect& clipIBounds, const SkMatrix& m, const GrShape& shape, float strokeDevWidth,
- SkRect* devBounds, SkRect* devBounds45, SkIRect* devIBounds, SkIVector* devToAtlasOffset) {
+const GrCCAtlas* GrCCPerFlushResources::renderPathInAtlas(const SkIRect& clipIBounds,
+ const SkMatrix& m, const SkPath& path,
+ SkRect* devBounds, SkRect* devBounds45,
+ SkIRect* devIBounds,
+ SkIVector* devToAtlasOffset) {
SkASSERT(this->isMapped());
SkASSERT(fNextPathInstanceIdx < fEndPathInstance);
- SkPath path;
- shape.asPath(&path);
if (path.isEmpty()) {
SkDEBUGCODE(--fEndPathInstance);
return nullptr;
}
- transform_path_pts(m, path, fLocalDevPtsBuffer, devBounds, devBounds45);
- const SkStrokeRec& stroke = shape.style().strokeRec();
- if (!stroke.isFillStyle()) {
- float r = SkStrokeRec::GetInflationRadius(stroke.getJoin(), stroke.getMiter(),
- stroke.getCap(), strokeDevWidth);
- devBounds->outset(r, r);
- // devBounds45 is in (| 1 -1 | * devCoords) space.
- // | 1 1 |
- devBounds45->outset(r*SK_ScalarSqrt2, r*SK_ScalarSqrt2);
- }
+ transform_path_pts(m, path, fLocalDevPtsBuffer, devBounds, devBounds45);
devBounds->roundOut(devIBounds);
GrScissorTest scissorTest;
@@ -291,17 +261,8 @@
return nullptr; // Path was degenerate or clipped away.
}
- if (stroke.isFillStyle()) {
- SkASSERT(0 == strokeDevWidth);
- fFiller.parseDeviceSpaceFill(path, fLocalDevPtsBuffer.begin(), scissorTest,
- clippedPathIBounds, *devToAtlasOffset);
- } else {
- // Stroke-and-fill is not yet supported.
- SkASSERT(SkStrokeRec::kStroke_Style == stroke.getStyle() || stroke.isHairlineStyle());
- SkASSERT(!stroke.isHairlineStyle() || 1 == strokeDevWidth);
- fStroker.parseDeviceSpaceStroke(path, fLocalDevPtsBuffer.begin(), stroke, strokeDevWidth,
- scissorTest, clippedPathIBounds, *devToAtlasOffset);
- }
+ fFiller.parseDeviceSpaceFill(path, fLocalDevPtsBuffer.begin(), scissorTest, clippedPathIBounds,
+ *devToAtlasOffset);
return &fRenderedAtlasStack.current();
}
@@ -345,8 +306,8 @@
// We did not fit in the previous coverage count atlas and it was retired. Close the path
// parser's current batch (which does not yet include the path we just parsed). We will
// render this batch into the retired atlas during finalize().
- retiredAtlas->setFillBatchID(fFiller.closeCurrentBatch());
- retiredAtlas->setStrokeBatchID(fStroker.closeCurrentBatch());
+ FillBatchID batchID = fFiller.closeCurrentBatch();
+ retiredAtlas->setUserBatchID(batchID);
}
return true;
}
@@ -362,26 +323,23 @@
fPathInstanceData = nullptr;
if (!fCopyAtlasStack.empty()) {
- fCopyAtlasStack.current().setFillBatchID(fNextCopyInstanceIdx);
+ fCopyAtlasStack.current().setUserBatchID(fNextCopyInstanceIdx);
}
if (!fRenderedAtlasStack.empty()) {
- fRenderedAtlasStack.current().setFillBatchID(fFiller.closeCurrentBatch());
- fRenderedAtlasStack.current().setStrokeBatchID(fStroker.closeCurrentBatch());
+ FillBatchID batchID = fFiller.closeCurrentBatch();
+ fRenderedAtlasStack.current().setUserBatchID(batchID);
}
// Build the GPU buffers to render path coverage counts. (This must not happen until after the
- // final calls to fFiller/fStroker.closeCurrentBatch().)
+ // final call to fPathParser.closeCurrentBatch().)
if (!fFiller.prepareToDraw(onFlushRP)) {
return false;
}
- if (!fStroker.prepareToDraw(onFlushRP)) {
- return false;
- }
// Draw the copies from the stashed atlas into 8-bit cached atlas(es).
int baseCopyInstance = 0;
for (GrCCAtlasStack::Iter atlas(fCopyAtlasStack); atlas.next();) {
- int endCopyInstance = atlas->getFillBatchID();
+ int endCopyInstance = atlas->getUserBatchID();
if (endCopyInstance <= baseCopyInstance) {
SkASSERT(endCopyInstance == baseCopyInstance);
continue;
@@ -409,8 +367,7 @@
if (auto rtc = atlas->makeRenderTargetContext(onFlushRP, std::move(backingTexture))) {
auto op = RenderAtlasOp::Make(rtc->surfPriv().getContext(), sk_ref_sp(this),
- atlas->getFillBatchID(), atlas->getStrokeBatchID(),
- atlas->drawBounds());
+ atlas->getUserBatchID(), atlas->drawBounds());
rtc->addDrawOp(GrNoClip(), std::move(op));
out->push_back(std::move(rtc));
}
@@ -420,17 +377,8 @@
}
void GrCCPerFlushResourceSpecs::convertCopiesToRenders() {
- for (int i = 0; i < 2; ++i) {
- fNumRenderedPaths[i] += fNumCopiedPaths[i];
- fNumCopiedPaths[i] = 0;
-
- fRenderedPathStats[i].fMaxPointsPerPath =
- SkTMax(fRenderedPathStats[i].fMaxPointsPerPath, fCopyPathStats[i].fMaxPointsPerPath);
- fRenderedPathStats[i].fNumTotalSkPoints += fCopyPathStats[i].fNumTotalSkPoints;
- fRenderedPathStats[i].fNumTotalSkVerbs += fCopyPathStats[i].fNumTotalSkVerbs;
- fRenderedPathStats[i].fNumTotalConicWeights += fCopyPathStats[i].fNumTotalConicWeights;
- fCopyPathStats[i] = GrCCRenderedPathStats();
- }
+ fNumRenderedPaths += fNumCopiedPaths;
+ fNumCopiedPaths = 0;
fRenderedAtlasSpecs.fApproxNumPixels += fCopyAtlasSpecs.fApproxNumPixels;
fRenderedAtlasSpecs.fMinWidth =
@@ -438,4 +386,11 @@
fRenderedAtlasSpecs.fMinHeight =
SkTMax(fRenderedAtlasSpecs.fMinHeight, fCopyAtlasSpecs.fMinHeight);
fCopyAtlasSpecs = GrCCAtlas::Specs();
+
+ fRenderedPathStats.fMaxPointsPerPath =
+ SkTMax(fRenderedPathStats.fMaxPointsPerPath, fCopyPathStats.fMaxPointsPerPath);
+ fRenderedPathStats.fNumTotalSkPoints += fCopyPathStats.fNumTotalSkPoints;
+ fRenderedPathStats.fNumTotalSkVerbs += fCopyPathStats.fNumTotalSkVerbs;
+ fRenderedPathStats.fNumTotalConicWeights += fCopyPathStats.fNumTotalConicWeights;
+ fCopyPathStats = GrCCFiller::PathStats();
}
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.h b/src/gpu/ccpr/GrCCPerFlushResources.h
index 132068f..3fa392e 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.h
+++ b/src/gpu/ccpr/GrCCPerFlushResources.h
@@ -11,47 +11,29 @@
#include "GrNonAtomicRef.h"
#include "ccpr/GrCCAtlas.h"
#include "ccpr/GrCCFiller.h"
-#include "ccpr/GrCCStroker.h"
#include "ccpr/GrCCPathProcessor.h"
class GrCCPathCacheEntry;
class GrOnFlushResourceProvider;
-class GrShape;
-
-/**
- * This struct counts values that help us preallocate buffers for rendered path geometry.
- */
-struct GrCCRenderedPathStats {
- int fMaxPointsPerPath = 0;
- int fNumTotalSkPoints = 0;
- int fNumTotalSkVerbs = 0;
- int fNumTotalConicWeights = 0;
-
- void statPath(const SkPath&);
-};
/**
* This struct encapsulates the minimum and desired requirements for the GPU resources required by
* CCPR in a given flush.
*/
struct GrCCPerFlushResourceSpecs {
- static constexpr int kFillIdx = 0;
- static constexpr int kStrokeIdx = 1;
-
int fNumCachedPaths = 0;
- int fNumCopiedPaths[2] = {0, 0};
- GrCCRenderedPathStats fCopyPathStats[2];
+ int fNumCopiedPaths = 0;
+ GrCCFiller::PathStats fCopyPathStats;
GrCCAtlas::Specs fCopyAtlasSpecs;
- int fNumRenderedPaths[2] = {0, 0};
+ int fNumRenderedPaths = 0;
int fNumClipPaths = 0;
- GrCCRenderedPathStats fRenderedPathStats[2];
+ GrCCFiller::PathStats fRenderedPathStats;
GrCCAtlas::Specs fRenderedAtlasSpecs;
bool isEmpty() const {
- return 0 == fNumCachedPaths + fNumCopiedPaths[kFillIdx] + fNumCopiedPaths[kStrokeIdx] +
- fNumRenderedPaths[kFillIdx] + fNumRenderedPaths[kStrokeIdx] + fNumClipPaths;
+ return 0 == fNumCachedPaths + fNumCopiedPaths + fNumRenderedPaths + fNumClipPaths;
}
void convertCopiesToRenders();
};
@@ -73,16 +55,12 @@
GrCCAtlas* copyPathToCachedAtlas(const GrCCPathCacheEntry&, GrCCPathProcessor::DoEvenOddFill,
SkIVector* newAtlasOffset);
- // These two methods render a path into a temporary coverage count atlas. See
- // GrCCPathProcessor::Instance for a description of the outputs. The returned atlases are
- // "const" to prevent the caller from assigning a unique key.
- //
- // strokeDevWidth must be 0 for fills, 1 for hairlines, or the stroke width in device-space
- // pixels for non-hairline strokes (implicitly requiring a rigid-body transform).
- const GrCCAtlas* renderShapeInAtlas(const SkIRect& clipIBounds, const SkMatrix&, const GrShape&,
- float strokeDevWidth, SkRect* devBounds,
- SkRect* devBounds45, SkIRect* devIBounds,
- SkIVector* devToAtlasOffset);
+ // These two methods render a path into a temporary coverage count atlas. See GrCCPathParser for
+ // a description of the arguments. The returned atlases are "const" to prevent the caller from
+ // assigning a unique key.
+ const GrCCAtlas* renderPathInAtlas(const SkIRect& clipIBounds, const SkMatrix&, const SkPath&,
+ SkRect* devBounds, SkRect* devBounds45, SkIRect* devIBounds,
+ SkIVector* devToAtlasOffset);
const GrCCAtlas* renderDeviceSpacePathInAtlas(const SkIRect& clipIBounds, const SkPath& devPath,
const SkIRect& devPathIBounds,
SkIVector* devToAtlasOffset);
@@ -108,7 +86,6 @@
// Accessors used by draw calls, once the resources have been finalized.
const GrCCFiller& filler() const { SkASSERT(!this->isMapped()); return fFiller; }
- const GrCCStroker& stroker() const { SkASSERT(!this->isMapped()); return fStroker; }
const GrBuffer* indexBuffer() const { SkASSERT(!this->isMapped()); return fIndexBuffer.get(); }
const GrBuffer* vertexBuffer() const { SkASSERT(!this->isMapped()); return fVertexBuffer.get();}
GrBuffer* instanceBuffer() const { SkASSERT(!this->isMapped()); return fInstanceBuffer.get(); }
@@ -136,7 +113,6 @@
const SkAutoSTArray<32, SkPoint> fLocalDevPtsBuffer;
GrCCFiller fFiller;
- GrCCStroker fStroker;
GrCCAtlasStack fCopyAtlasStack;
GrCCAtlasStack fRenderedAtlasStack;
@@ -151,11 +127,4 @@
SkDEBUGCODE(int fEndPathInstance);
};
-inline void GrCCRenderedPathStats::statPath(const SkPath& path) {
- fMaxPointsPerPath = SkTMax(fMaxPointsPerPath, path.countPoints());
- fNumTotalSkPoints += path.countPoints();
- fNumTotalSkVerbs += path.countVerbs();
- fNumTotalConicWeights += SkPathPriv::ConicWeightCnt(path);
-}
-
#endif
diff --git a/src/gpu/ccpr/GrCCStrokeGeometry.cpp b/src/gpu/ccpr/GrCCStrokeGeometry.cpp
deleted file mode 100644
index 3fcafec..0000000
--- a/src/gpu/ccpr/GrCCStrokeGeometry.cpp
+++ /dev/null
@@ -1,582 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "GrCCStrokeGeometry.h"
-
-#include "SkGeometry.h"
-#include "SkMathPriv.h"
-#include "SkNx.h"
-#include "SkStrokeRec.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;
-
-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);
- b.store(transpose+2);
- 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);
- Sk2f invlength = (X*X + Y*Y).rsqrt();
- Sk2f dotprod = leftTan * rightTan;
- return (dotprod[0] + dotprod[1]) * invlength[0] * invlength[1];
-}
-
-static GrCCStrokeGeometry::Verb join_verb_from_join(SkPaint::Join join) {
- using Verb = GrCCStrokeGeometry::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.");
- return Verb::kBevelJoin;
-}
-
-void GrCCStrokeGeometry::beginPath(const SkStrokeRec& stroke, float strokeDevWidth,
- InstanceTallies* tallies) {
- SkASSERT(!fInsideContour);
- // 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());
- 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 = SkTMax(1 - kMaxErrorFromLinearization / fCurrStrokeRadius, 0.f);
- fMaxCurvatureCosTheta = 2*r*r - 1;
-
- fCurrContourFirstPtIdx = -1;
- fCurrContourFirstNormalIdx = -1;
-
- fVerbs.push_back(Verb::kBeginPath);
-}
-
-void GrCCStrokeGeometry::moveTo(SkPoint pt) {
- SkASSERT(!fInsideContour);
- fCurrContourFirstPtIdx = fPoints.count();
- fCurrContourFirstNormalIdx = fNormals.count();
- fPoints.push_back(pt);
- SkDEBUGCODE(fInsideContour = true);
-}
-
-void GrCCStrokeGeometry::lineTo(SkPoint pt) {
- SkASSERT(fInsideContour);
- this->lineTo(fCurrStrokeJoinVerb, pt);
-}
-
-void GrCCStrokeGeometry::lineTo(Verb leftJoinVerb, SkPoint pt) {
- Sk2f tan = Sk2f::Load(&pt) - Sk2f::Load(&fPoints.back());
- if ((tan == 0).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);
-}
-
-void GrCCStrokeGeometry::quadraticTo(const SkPoint P[3]) {
- SkASSERT(fInsideContour);
- this->quadraticTo(fCurrStrokeJoinVerb, P, SkFindQuadMaxCurvature(P));
-}
-
-// Wang's formula for quadratics (1985) gives us the number of evenly spaced (in the parametric
-// sense) line segments that are guaranteed to be within a distance of "kMaxErrorFromLinearization"
-// from the actual curve.
-static inline float wangs_formula_quadratic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2) {
- static constexpr float k = 2 / (8 * kMaxErrorFromLinearization);
- float f = SkScalarSqrt(k * length(p2 - p1*2 + p0));
- return SkScalarCeilToInt(f);
-}
-
-void GrCCStrokeGeometry::quadraticTo(Verb leftJoinVerb, const SkPoint P[3], float maxCurvatureT) {
- Sk2f p0 = Sk2f::Load(P);
- Sk2f p1 = Sk2f::Load(P+1);
- Sk2f p2 = Sk2f::Load(P+2);
-
- Sk2f tan0 = p1 - p0;
- Sk2f tan1 = p2 - p1;
-
- // Snap to a "lineTo" if the control point is so close to an endpoint that FP error will become
- // an issue.
- if ((tan0.abs() < SK_ScalarNearlyZero).allTrue() || // p0 ~= p1
- (tan1.abs() < SK_ScalarNearlyZero).allTrue()) { // p1 ~= p2
- this->lineTo(leftJoinVerb, 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);
- if (numSegments <= 1) {
- this->rotateTo(leftJoinVerb, normals[0]);
- this->lineTo(Verb::kInternalRoundJoin, P[2]);
- this->rotateTo(Verb::kInternalRoundJoin, normals[1]);
- return;
- }
-
- // At + B gives a vector tangent to the quadratic.
- Sk2f A = p0 - p1*2 + p2;
- Sk2f B = p1 - p0;
-
- // Find a line segment that crosses max curvature.
- float segmentLength = SkScalarInvert(numSegments);
- float leftT = maxCurvatureT - segmentLength/2;
- float rightT = maxCurvatureT + segmentLength/2;
- Sk2f leftTan, rightTan;
- if (leftT <= 0) {
- leftT = 0;
- leftTan = tan0;
- rightT = segmentLength;
- rightTan = A*rightT + B;
- } else if (rightT >= 1) {
- leftT = 1 - segmentLength;
- leftTan = A*leftT + B;
- rightT = 1;
- rightTan = tan1;
- } else {
- leftTan = A*leftT + B;
- rightTan = A*rightT + B;
- }
-
- // Check if curvature is too strong for a triangle strip on the line segment that crosses max
- // curvature. If it is, we will chop and convert the segment to a "lineTo" with round joins.
- //
- // FIXME: This is quite costly and the vast majority of curves only have moderate curvature. We
- // would benefit significantly from a quick reject that detects curves that don't need special
- // treatment for strong curvature.
- bool isCurvatureTooStrong = calc_curvature_costheta(leftTan, rightTan) < fMaxCurvatureCosTheta;
- if (isCurvatureTooStrong) {
- SkPoint ptsBuffer[5];
- const SkPoint* currQuadratic = P;
-
- if (leftT > 0) {
- SkChopQuadAt(currQuadratic, ptsBuffer, leftT);
- this->quadraticTo(leftJoinVerb, ptsBuffer, /*maxCurvatureT=*/1);
- if (rightT < 1) {
- rightT = (rightT - leftT) / (1 - leftT);
- }
- currQuadratic = ptsBuffer + 2;
- } else {
- this->rotateTo(leftJoinVerb, normals[0]);
- }
-
- if (rightT < 1) {
- SkChopQuadAt(currQuadratic, ptsBuffer, rightT);
- this->lineTo(Verb::kInternalRoundJoin, ptsBuffer[2]);
- this->quadraticTo(Verb::kInternalRoundJoin, ptsBuffer + 2, /*maxCurvatureT=*/0);
- } else {
- this->lineTo(Verb::kInternalRoundJoin, currQuadratic[2]);
- this->rotateTo(Verb::kInternalRoundJoin, normals[1]);
- }
- 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());
-}
-
-void GrCCStrokeGeometry::cubicTo(const SkPoint P[4]) {
- SkASSERT(fInsideContour);
- float roots[3];
- int numRoots = SkFindCubicMaxCurvature(P, roots);
- this->cubicTo(fCurrStrokeJoinVerb, P,
- numRoots > 0 ? roots[numRoots/2] : 0,
- numRoots > 1 ? roots[0] : kLeftMaxCurvatureNone,
- numRoots > 2 ? roots[2] : kRightMaxCurvatureNone);
-}
-
-// Wang's formula for cubics (1985) gives us the number of evenly spaced (in the parametric sense)
-// line segments that are guaranteed to be within a distance of "kMaxErrorFromLinearization"
-// from the actual curve.
-static inline float wangs_formula_cubic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
- const Sk2f& p3) {
- static constexpr float k = (3 * 2) / (8 * kMaxErrorFromLinearization);
- float f = SkScalarSqrt(k * length(Sk2f::Max((p2 - p1*2 + p0).abs(),
- (p3 - p2*2 + p1).abs())));
- return SkScalarCeilToInt(f);
-}
-
-void GrCCStrokeGeometry::cubicTo(Verb leftJoinVerb, 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);
- Sk2f p3 = Sk2f::Load(P+3);
-
- Sk2f tan0 = p1 - p0;
- Sk2f tan1 = p3 - p2;
-
- // Snap control points to endpoints if they are so close that FP error will become an issue.
- if ((tan0.abs() < SK_ScalarNearlyZero).allTrue()) { // p0 ~= p1
- p1 = p0;
- tan0 = p2 - p0;
- if ((tan0.abs() < SK_ScalarNearlyZero).allTrue()) { // p0 ~= p1 ~= p2
- this->lineTo(leftJoinVerb, P[3]);
- return;
- }
- }
- if ((tan1.abs() < SK_ScalarNearlyZero).allTrue()) { // p2 ~= p3
- p2 = p3;
- 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]);
- 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);
- if (numSegments <= 1) {
- this->rotateTo(leftJoinVerb, normals[0]);
- this->lineTo(leftJoinVerb, P[3]);
- this->rotateTo(Verb::kInternalRoundJoin, normals[1]);
- return;
- }
-
- // 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.)
- Sk2f A = p3 + (p1 - p2)*3 - p0;
- Sk2f B = (p0 - p1*2 + p2)*2;
- Sk2f C = p1 - p0;
-
- // Find a line segment that crosses max curvature.
- float segmentLength = SkScalarInvert(numSegments);
- float leftT = maxCurvatureT - segmentLength/2;
- float rightT = maxCurvatureT + segmentLength/2;
- Sk2f leftTan, rightTan;
- if (leftT <= 0) {
- leftT = 0;
- leftTan = tan0;
- rightT = segmentLength;
- rightTan = A*rightT*rightT + B*rightT + C;
- } else if (rightT >= 1) {
- leftT = 1 - segmentLength;
- leftTan = A*leftT*leftT + B*leftT + C;
- rightT = 1;
- rightTan = tan1;
- } else {
- leftTan = A*leftT*leftT + B*leftT + C;
- rightTan = A*rightT*rightT + B*rightT + C;
- }
-
- // Check if curvature is too strong for a triangle strip on the line segment that crosses max
- // curvature. If it is, we will chop and convert the segment to a "lineTo" with round joins.
- //
- // FIXME: This is quite costly and the vast majority of curves only have moderate curvature. We
- // would benefit significantly from a quick reject that detects curves that don't need special
- // treatment for strong curvature.
- bool isCurvatureTooStrong = calc_curvature_costheta(leftTan, rightTan) < fMaxCurvatureCosTheta;
- if (isCurvatureTooStrong) {
- SkPoint ptsBuffer[7];
- p0.store(ptsBuffer);
- p1.store(ptsBuffer + 1);
- p2.store(ptsBuffer + 2);
- p3.store(ptsBuffer + 3);
- const SkPoint* currCubic = ptsBuffer;
-
- if (leftT > 0) {
- SkChopCubicAt(currCubic, ptsBuffer, leftT);
- this->cubicTo(leftJoinVerb, ptsBuffer, /*maxCurvatureT=*/1,
- (kLeftMaxCurvatureNone != leftMaxCurvatureT)
- ? leftMaxCurvatureT/leftT : kLeftMaxCurvatureNone,
- kRightMaxCurvatureNone);
- if (rightT < 1) {
- rightT = (rightT - leftT) / (1 - leftT);
- }
- if (rightMaxCurvatureT < 1 && kRightMaxCurvatureNone != rightMaxCurvatureT) {
- rightMaxCurvatureT = (rightMaxCurvatureT - leftT) / (1 - leftT);
- }
- currCubic = ptsBuffer + 3;
- } else {
- this->rotateTo(leftJoinVerb, normals[0]);
- }
-
- if (rightT < 1) {
- SkChopCubicAt(currCubic, ptsBuffer, rightT);
- this->lineTo(Verb::kInternalRoundJoin, ptsBuffer[3]);
- currCubic = ptsBuffer + 3;
- this->cubicTo(Verb::kInternalRoundJoin, currCubic, /*maxCurvatureT=*/0,
- kLeftMaxCurvatureNone, kRightMaxCurvatureNone);
- } else {
- this->lineTo(Verb::kInternalRoundJoin, currCubic[3]);
- this->rotateTo(Verb::kInternalRoundJoin, normals[1]);
- }
- return;
- }
-
- // Recurse and check the other two points of max curvature, if any.
- if (kRightMaxCurvatureNone != rightMaxCurvatureT) {
- this->cubicTo(leftJoinVerb, P, rightMaxCurvatureT, leftMaxCurvatureT,
- kRightMaxCurvatureNone);
- return;
- }
- if (kLeftMaxCurvatureNone != leftMaxCurvatureT) {
- SkASSERT(kRightMaxCurvatureNone == rightMaxCurvatureT);
- this->cubicTo(leftJoinVerb, P, leftMaxCurvatureT, 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());
-}
-
-void GrCCStrokeGeometry::recordStroke(Verb verb, int numSegmentsLog2) {
- SkASSERT(Verb::kLinearStroke != verb || 0 == numSegmentsLog2);
- SkASSERT(numSegmentsLog2 <= kMaxNumLinearSegmentsLog2);
- fVerbs.push_back(verb);
- if (Verb::kLinearStroke != verb) {
- SkASSERT(numSegmentsLog2 > 0);
- fParams.push_back().fNumLinearSegmentsLog2 = numSegmentsLog2;
- }
- ++fCurrStrokeTallies->fStrokes[numSegmentsLog2];
-}
-
-void GrCCStrokeGeometry::rotateTo(Verb leftJoinVerb, SkVector normal) {
- this->recordLeftJoinIfNotEmpty(leftJoinVerb, normal);
- fNormals.push_back(normal);
-}
-
-void GrCCStrokeGeometry::recordLeftJoinIfNotEmpty(Verb joinVerb, SkVector nextNormal) {
- if (fNormals.count() <= fCurrContourFirstNormalIdx) {
- // The contour is empty. Nothing to join with.
- SkASSERT(fNormals.count() == fCurrContourFirstNormalIdx);
- return;
- }
-
- if (Verb::kBevelJoin == joinVerb) {
- this->recordBevelJoin(Verb::kBevelJoin);
- return;
- }
-
- Sk2f n0 = Sk2f::Load(&fNormals.back());
- Sk2f n1 = Sk2f::Load(&nextNormal);
- Sk2f base = n1 - n0;
- if ((base.abs() * fCurrStrokeRadius < kMaxErrorFromLinearization).allTrue()) {
- // Treat any join as a bevel when the outside corners of the two adjoining strokes are
- // close enough to each other. This is important because "miterCapHeightOverWidth" becomes
- // unstable when n0 and n1 are nearly equal.
- this->recordBevelJoin(joinVerb);
- return;
- }
-
- // We implement miters and round joins by placing a triangle-shaped cap on top of a bevel join.
- // (For round joins this triangle cap comprises the conic control points.) Find how tall to make
- // this triangle cap, relative to its width.
- //
- // NOTE: This value would be infinite at 180 degrees, but we clamp miterCapHeightOverWidth at
- // near-infinity. 180-degree round joins still look perfectly acceptable like this (though
- // technically not pure arcs).
- Sk2f cross = base * SkNx_shuffle<1,0>(n0);
- Sk2f dot = base * n0;
- float miterCapHeight = SkScalarAbs(dot[0] + dot[1]);
- float miterCapWidth = SkScalarAbs(cross[0] - cross[1]) * 2;
-
- if (Verb::kMiterJoin == joinVerb) {
- if (miterCapHeight > fMiterMaxCapHeightOverWidth * miterCapWidth) {
- // This join is tighter than the miter limit. Treat it as a bevel.
- this->recordBevelJoin(Verb::kMiterJoin);
- return;
- }
- this->recordMiterJoin(miterCapHeight / miterCapWidth);
- return;
- }
-
- SkASSERT(Verb::kRoundJoin == joinVerb || Verb::kInternalRoundJoin == joinVerb);
-
- // Conic arcs become unstable when they approach 180 degrees. When the conic control point
- // begins shooting off to infinity (i.e., height/width > 32), split the conic into two.
- static constexpr float kAlmost180Degrees = 32;
- if (miterCapHeight > kAlmost180Degrees * miterCapWidth) {
- Sk2f bisect = normalize(n0 - n1);
- this->rotateTo(joinVerb, SkVector::Make(-bisect[1], bisect[0]));
- this->recordLeftJoinIfNotEmpty(joinVerb, nextNormal);
- return;
- }
-
- float miterCapHeightOverWidth = miterCapHeight / miterCapWidth;
-
- // Find the heights of this round join's conic control point as well as the arc itself.
- Sk2f X, Y;
- transpose(base * base, n0 * n1, &X, &Y);
- Sk2f r = Sk2f::Max(X + Y + Sk2f(0, 1), 0.f).sqrt();
- Sk2f heights = SkNx_fma(r, Sk2f(miterCapHeightOverWidth, -SK_ScalarRoot2Over2), Sk2f(0, 1));
- float controlPointHeight = SkScalarAbs(heights[0]);
- float curveHeight = heights[1];
- if (curveHeight * fCurrStrokeRadius < kMaxErrorFromLinearization) {
- // Treat round joins as bevels when their curvature is nearly flat.
- this->recordBevelJoin(joinVerb);
- return;
- }
-
- float w = curveHeight / (controlPointHeight - curveHeight);
- this->recordRoundJoin(joinVerb, miterCapHeightOverWidth, w);
-}
-
-void GrCCStrokeGeometry::recordBevelJoin(Verb originalJoinVerb) {
- if (!IsInternalJoinVerb(originalJoinVerb)) {
- fVerbs.push_back(Verb::kBevelJoin);
- ++fCurrStrokeTallies->fTriangles;
- } else {
- fVerbs.push_back(Verb::kInternalBevelJoin);
- fCurrStrokeTallies->fTriangles += 2;
- }
-}
-
-void GrCCStrokeGeometry::recordMiterJoin(float miterCapHeightOverWidth) {
- fVerbs.push_back(Verb::kMiterJoin);
- fParams.push_back().fMiterCapHeightOverWidth = miterCapHeightOverWidth;
- fCurrStrokeTallies->fTriangles += 2;
-}
-
-void GrCCStrokeGeometry::recordRoundJoin(Verb joinVerb, float miterCapHeightOverWidth,
- float conicWeight) {
- fVerbs.push_back(joinVerb);
- fParams.push_back().fConicWeight = conicWeight;
- fParams.push_back().fMiterCapHeightOverWidth = miterCapHeightOverWidth;
- if (Verb::kRoundJoin == joinVerb) {
- ++fCurrStrokeTallies->fTriangles;
- ++fCurrStrokeTallies->fConics;
- } else {
- SkASSERT(Verb::kInternalRoundJoin == joinVerb);
- fCurrStrokeTallies->fTriangles += 2;
- fCurrStrokeTallies->fConics += 2;
- }
-}
-
-void GrCCStrokeGeometry::closeContour() {
- SkASSERT(fInsideContour);
- SkASSERT(fPoints.count() > fCurrContourFirstPtIdx);
- if (fPoints.back() != fPoints[fCurrContourFirstPtIdx]) {
- // Draw a line back to the beginning.
- this->lineTo(fCurrStrokeJoinVerb, fPoints[fCurrContourFirstPtIdx]);
- }
- if (fNormals.count() > fCurrContourFirstNormalIdx) {
- // Join the first and last lines.
- this->rotateTo(fCurrStrokeJoinVerb,fNormals[fCurrContourFirstNormalIdx]);
- } else {
- // This contour is empty. Add a bogus normal since the iterator always expects one.
- SkASSERT(fNormals.count() == fCurrContourFirstNormalIdx);
- fNormals.push_back({0, 0});
- }
- fVerbs.push_back(Verb::kEndContour);
- SkDEBUGCODE(fInsideContour = false);
-}
-
-void GrCCStrokeGeometry::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 GrCCStrokeGeometry::recordCapsIfAny() {
- SkASSERT(fInsideContour);
- SkASSERT(fCurrContourFirstNormalIdx < fNormals.count());
-
- if (SkPaint::kButt_Cap == fCurrStrokeCapType) {
- 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]);
-}
diff --git a/src/gpu/ccpr/GrCCStrokeGeometry.h b/src/gpu/ccpr/GrCCStrokeGeometry.h
deleted file mode 100644
index 56871f2..0000000
--- a/src/gpu/ccpr/GrCCStrokeGeometry.h
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef GrGrCCStrokeGeometry_DEFINED
-#define GrGrCCStrokeGeometry_DEFINED
-
-#include "SkPaint.h"
-#include "SkPoint.h"
-#include "SkTArray.h"
-
-class SkStrokeRec;
-
-/**
- * This class converts device-space stroked paths into a set of independent strokes, joins, and caps
- * that map directly to coverage-counted GPU instances. Non-hairline strokes can only be drawn with
- * rigid body transforms; we don't yet support skewing the stroke lines themselves.
- */
-class GrCCStrokeGeometry {
-public:
- static constexpr int kMaxNumLinearSegmentsLog2 = 15;
-
- GrCCStrokeGeometry(int numSkPoints = 0, int numSkVerbs = 0)
- : 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. When added
- // up, the combined coverage of all these instances will make complete stroked paths.
- 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,
-
- // 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; }
- 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; }
-
- // These track the numbers of instances required to draw all the recorded strokes.
- struct InstanceTallies {
- int fStrokes[kMaxNumLinearSegmentsLog2 + 1];
- int fTriangles;
- int fConics;
-
- 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.
-
-private:
- void lineTo(Verb leftJoinVerb, SkPoint);
- void quadraticTo(Verb leftJoinVerb, 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,
- float rightMaxCurvatureT);
-
- // Pushes a new normal to fNormals and records a join, without changing the current position.
- void rotateTo(Verb leftJoinVerb, SkVector normal);
-
- // Records a stroke in fElememts.
- void recordStroke(Verb, int numSegmentsLog2);
-
- // Records a join in fElememts with the previous stroke, if the cuurent contour is not empty.
- void recordLeftJoinIfNotEmpty(Verb joinType, SkVector nextNormal);
- void recordBevelJoin(Verb originalJoinVerb);
- void recordMiterJoin(float miterCapHeightOverWidth);
- void recordRoundJoin(Verb roundJoinVerb, float miterCapHeightOverWidth, float conicWeight);
-
- void recordCapsIfAny();
-
- float fCurrStrokeRadius;
- Verb fCurrStrokeJoinVerb;
- 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
- // on the outer edge.
- //
- // (Curvature this strong is quite rare in practice, but when it does happen, we decompose the
- // section with strong curvature into lineTo's with round joins in between.)
- float fMaxCurvatureCosTheta;
-
- int fCurrContourFirstPtIdx;
- int fCurrContourFirstNormalIdx;
-
- SkDEBUGCODE(bool fInsideContour = false);
-
- SkSTArray<128, Verb, true> fVerbs;
- SkSTArray<128, Parameter, true> fParams;
- SkSTArray<128, SkPoint, true> fPoints;
- SkSTArray<128, SkVector, true> fNormals;
-};
-
-inline GrCCStrokeGeometry::InstanceTallies GrCCStrokeGeometry::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 GrCCStrokeGeometry::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::kBevelJoin:
- case Verb::kMiterJoin:
- case Verb::kRoundJoin:
- case Verb::kSquareCap:
- case Verb::kRoundCap:
- case Verb::kEndContour:
- return false;
- }
- SK_ABORT("Invalid GrCCStrokeGeometry::Verb.");
- return false;
-}
-#endif
diff --git a/src/gpu/ccpr/GrCCStroker.cpp b/src/gpu/ccpr/GrCCStroker.cpp
deleted file mode 100644
index ab3906c..0000000
--- a/src/gpu/ccpr/GrCCStroker.cpp
+++ /dev/null
@@ -1,832 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "GrCCStroker.h"
-
-#include "GrGpuCommandBuffer.h"
-#include "GrOnFlushResourceProvider.h"
-#include "SkPathPriv.h"
-#include "SkStrokeRec.h"
-#include "ccpr/GrCCCoverageProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLVertexGeoBuilder.h"
-
-static constexpr int kMaxNumLinearSegmentsLog2 = GrCCStrokeGeometry::kMaxNumLinearSegmentsLog2;
-using TriangleInstance = GrCCCoverageProcessor::TriPointInstance;
-using ConicInstance = GrCCCoverageProcessor::QuadPointInstance;
-
-namespace {
-
-struct LinearStrokeInstance {
- float fEndpoints[4];
- float fStrokeRadius;
-
- inline void set(const SkPoint[2], float dx, float dy, float strokeRadius);
-};
-
-inline void LinearStrokeInstance::set(const SkPoint P[2], float dx, float dy, float strokeRadius) {
- Sk2f X, Y;
- Sk2f::Load2(P, &X, &Y);
- Sk2f::Store2(fEndpoints, X + dx, Y + dy);
- fStrokeRadius = strokeRadius;
-}
-
-struct CubicStrokeInstance {
- float fX[4];
- float fY[4];
- float fStrokeRadius;
- float fNumSegments;
-
- inline void set(const SkPoint[4], float dx, float dy, float strokeRadius, int numSegments);
- inline void set(const Sk4f& X, const Sk4f& Y, float dx, float dy, float strokeRadius,
- int numSegments);
-};
-
-inline void CubicStrokeInstance::set(const SkPoint P[4], float dx, float dy, float strokeRadius,
- int numSegments) {
- Sk4f X, Y;
- Sk4f::Load2(P, &X, &Y);
- this->set(X, Y, dx, dy, strokeRadius, numSegments);
-}
-
-inline void CubicStrokeInstance::set(const Sk4f& X, const Sk4f& Y, float dx, float dy,
- float strokeRadius, int numSegments) {
- (X + dx).store(&fX);
- (Y + dy).store(&fY);
- fStrokeRadius = strokeRadius;
- fNumSegments = static_cast<float>(numSegments);
-}
-
-// This class draws stroked lines in post-transform device space (a.k.a. rectangles). Rigid-body
-// transforms can be achieved by transforming the line ahead of time and adjusting the stroke
-// width. Skews of the stroke itself are not yet supported.
-//
-// Corner coverage is AA-correct, meaning, n^2 attenuation along the diagonals. This is important
-// for seamless integration with the connecting geometry.
-class LinearStrokeProcessor : public GrGeometryProcessor {
-public:
- LinearStrokeProcessor() : GrGeometryProcessor(kLinearStrokeProcessor_ClassID) {
- this->setInstanceAttributeCnt(2);
-#ifdef SK_DEBUG
- // Check that instance attributes exactly match the LinearStrokeInstance struct layout.
- using Instance = LinearStrokeInstance;
- SkASSERT(!strcmp(this->instanceAttribute(0).name(), "endpts"));
- SkASSERT(this->debugOnly_instanceAttributeOffset(0) == offsetof(Instance, fEndpoints));
- SkASSERT(!strcmp(this->instanceAttribute(1).name(), "stroke_radius"));
- SkASSERT(this->debugOnly_instanceAttributeOffset(1) == offsetof(Instance, fStrokeRadius));
- SkASSERT(this->debugOnly_instanceStride() == sizeof(Instance));
-#endif
- }
-
-private:
- const char* name() const override { return "LinearStrokeProcessor"; }
- void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
-
- static constexpr Attribute kInstanceAttribs[2] = {
- {"endpts", kFloat4_GrVertexAttribType},
- {"stroke_radius", kFloat_GrVertexAttribType}
- };
-
- const Attribute& onInstanceAttribute(int i) const override { return kInstanceAttribs[i]; }
-
- class Impl : public GrGLSLGeometryProcessor {
- void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&,
- FPCoordTransformIter&&) override {}
- void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override;
- };
-
- GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override {
- return new Impl();
- }
-};
-
-void LinearStrokeProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
- GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
- GrGLSLUniformHandler* uniHandler = args.fUniformHandler;
-
- varyingHandler->emitAttributes(args.fGP.cast<LinearStrokeProcessor>());
-
- GrGLSLVertexBuilder* v = args.fVertBuilder;
- v->codeAppend ("float2 tan = normalize(endpts.zw - endpts.xy);");
- v->codeAppend ("float2 n = float2(tan.y, -tan.x);");
- v->codeAppend ("float nwidth = abs(n.x) + abs(n.y);");
-
- // Outset the vertex position for AA butt caps.
- v->codeAppend ("float2 outset = tan*nwidth/2;");
- v->codeAppend ("float2 position = (sk_VertexID < 2) "
- "? endpts.xy - outset : endpts.zw + outset;");
-
- // Calculate Manhattan distance from both butt caps, where distance=0 on the actual endpoint and
- // distance=-.5 on the outset edge.
- GrGLSLVarying edgeDistances(kFloat4_GrSLType);
- varyingHandler->addVarying("edge_distances", &edgeDistances);
- v->codeAppendf("%s.xz = float2(-.5, dot(endpts.zw - endpts.xy, tan) / nwidth + .5);",
- edgeDistances.vsOut());
- v->codeAppendf("%s.xz = (sk_VertexID < 2) ? %s.xz : %s.zx;",
- edgeDistances.vsOut(), edgeDistances.vsOut(), edgeDistances.vsOut());
-
- // Outset the vertex position for stroke radius plus edge AA.
- v->codeAppend ("outset = n * (stroke_radius + nwidth/2);");
- v->codeAppend ("position += (0 == (sk_VertexID & 1)) ? +outset : -outset;");
-
- // Calculate Manhattan distance from both edges, where distance=0 on the actual edge and
- // distance=-.5 on the outset.
- v->codeAppendf("%s.yw = float2(-.5, 2*stroke_radius / nwidth + .5);", edgeDistances.vsOut());
- v->codeAppendf("%s.yw = (0 == (sk_VertexID & 1)) ? %s.yw : %s.wy;",
- edgeDistances.vsOut(), edgeDistances.vsOut(), edgeDistances.vsOut());
-
- gpArgs->fPositionVar.set(kFloat2_GrSLType, "position");
- this->emitTransforms(v, varyingHandler, uniHandler, GrShaderVar("position", kFloat2_GrSLType),
- SkMatrix::I(), args.fFPCoordTransformHandler);
-
- // Use the 4 edge distances to calculate coverage in the fragment shader.
- GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
- f->codeAppendf("half2 coverages = min(%s.xy, .5) + min(%s.zw, .5);",
- edgeDistances.fsIn(), edgeDistances.fsIn());
- f->codeAppendf("%s = half4(coverages.x * coverages.y);", args.fOutputColor);
-
- // This shader doesn't use the built-in Ganesh coverage.
- f->codeAppendf("%s = half4(1);", args.fOutputCoverage);
-}
-
-constexpr GrPrimitiveProcessor::Attribute LinearStrokeProcessor::kInstanceAttribs[];
-
-// This class draws stroked cubics in post-transform device space. Rigid-body transforms can be
-// achieved by transforming the curve ahead of time and adjusting the stroke width. Skews of the
-// stroke itself are not yet supported. Quadratics can be drawn by converting them to cubics.
-//
-// This class works by finding stroke-width line segments orthogonal to the curve at a
-// pre-determined number of evenly spaced points along the curve (evenly spaced in the parametric
-// sense). It then connects the segments with a triangle strip. As for common in CCPR, clockwise-
-// winding triangles from the strip emit positive coverage, counter-clockwise triangles emit
-// negative, and we use SkBlendMode::kPlus.
-class CubicStrokeProcessor : public GrGeometryProcessor {
-public:
- CubicStrokeProcessor() : GrGeometryProcessor(kCubicStrokeProcessor_ClassID) {
- this->setInstanceAttributeCnt(3);
-#ifdef SK_DEBUG
- // Check that instance attributes exactly match the CubicStrokeInstance struct layout.
- using Instance = CubicStrokeInstance;
- SkASSERT(!strcmp(this->instanceAttribute(0).name(), "X"));
- SkASSERT(this->debugOnly_instanceAttributeOffset(0) == offsetof(Instance, fX));
- SkASSERT(!strcmp(this->instanceAttribute(1).name(), "Y"));
- SkASSERT(this->debugOnly_instanceAttributeOffset(1) == offsetof(Instance, fY));
- SkASSERT(!strcmp(this->instanceAttribute(2).name(), "stroke_info"));
- SkASSERT(this->debugOnly_instanceAttributeOffset(2) == offsetof(Instance, fStrokeRadius));
- SkASSERT(this->debugOnly_instanceStride() == sizeof(Instance));
-#endif
- }
-
-private:
- const char* name() const override { return "CubicStrokeProcessor"; }
- void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
-
- static constexpr Attribute kInstanceAttribs[3] = {
- {"X", kFloat4_GrVertexAttribType},
- {"Y", kFloat4_GrVertexAttribType},
- {"stroke_info", kFloat2_GrVertexAttribType}
- };
-
- const Attribute& onInstanceAttribute(int i) const override { return kInstanceAttribs[i]; }
-
- class Impl : public GrGLSLGeometryProcessor {
- void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&,
- FPCoordTransformIter&&) override {}
- void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override;
- };
-
- GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override {
- return new Impl();
- }
-};
-
-void CubicStrokeProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
- GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
- GrGLSLUniformHandler* uniHandler = args.fUniformHandler;
-
- varyingHandler->emitAttributes(args.fGP.cast<CubicStrokeProcessor>());
-
- GrGLSLVertexBuilder* v = args.fVertBuilder;
- v->codeAppend ("float4x2 P = transpose(float2x4(X, Y));");
- v->codeAppend ("float stroke_radius = stroke_info[0];");
- v->codeAppend ("float num_segments = stroke_info[1];");
-
- // Find the parametric T value at which we will emit our orthogonal line segment. We emit two
- // line segments at T=0 and double at T=1 as well for AA butt caps.
- v->codeAppend ("float point_id = float(sk_VertexID/2);");
- v->codeAppend ("float T = max((point_id - 1) / num_segments, 0);");
- v->codeAppend ("T = (point_id >= num_segments + 1) ? 1 : T;"); // In case x/x !== 1.
-
- // Use De Casteljau's algorithm to find the position and tangent for our orthogonal line
- // segment. De Casteljau's is more numerically stable than evaluating the curve and derivative
- // directly.
- v->codeAppend ("float2 ab = mix(P[0], P[1], T);");
- v->codeAppend ("float2 bc = mix(P[1], P[2], T);");
- v->codeAppend ("float2 cd = mix(P[2], P[3], T);");
- v->codeAppend ("float2 abc = mix(ab, bc, T);");
- v->codeAppend ("float2 bcd = mix(bc, cd, T);");
- v->codeAppend ("float2 position = mix(abc, bcd, T);");
- v->codeAppend ("float2 tan = bcd - abc;");
-
- // Find actual tangents for the corner cases when De Casteljau's yields tan=0. (We shouldn't
- // encounter other numerically unstable cases where tan ~= 0, because GrCCStrokeGeometry snaps
- // control points to endpoints in curves where they are almost equal.)
- v->codeAppend ("if (0 == T && P[0] == P[1]) {");
- v->codeAppend ( "tan = P[2] - P[0];");
- v->codeAppend ("}");
- v->codeAppend ("if (1 == T && P[2] == P[3]) {");
- v->codeAppend ( "tan = P[3] - P[1];");
- v->codeAppend ("}");
- v->codeAppend ("tan = normalize(tan);");
- v->codeAppend ("float2 n = float2(tan.y, -tan.x);");
- v->codeAppend ("float nwidth = abs(n.x) + abs(n.y);");
-
- // Outset the vertex position for stroke radius plus edge AA.
- v->codeAppend ("float2 outset = n * (stroke_radius + nwidth/2);");
- v->codeAppend ("position += (0 == (sk_VertexID & 1)) ? -outset : +outset;");
-
- // Calculate the Manhattan distance from both edges, where distance=0 on the actual edge and
- // distance=-.5 on the outset.
- GrGLSLVarying coverages(kFloat3_GrSLType);
- varyingHandler->addVarying("coverages", &coverages);
- v->codeAppendf("%s.xy = float2(-.5, 2*stroke_radius / nwidth + .5);", coverages.vsOut());
- v->codeAppendf("%s.xy = (0 == (sk_VertexID & 1)) ? %s.xy : %s.yx;",
- coverages.vsOut(), coverages.vsOut(), coverages.vsOut());
-
- // Adjust the orthogonal line segments on the endpoints so they straddle the actual endpoint
- // at a Manhattan distance of .5 on either side.
- v->codeAppend ("if (0 == point_id || num_segments+1 == point_id) {");
- v->codeAppend ( "position -= tan*nwidth/2;");
- v->codeAppend ("}");
- v->codeAppend ("if (1 == point_id || num_segments+2 == point_id) {");
- v->codeAppend ( "position += tan*nwidth/2;");
- v->codeAppend ("}");
-
- // Interpolate coverage for butt cap AA from 0 on the outer segment to 1 on the inner.
- v->codeAppendf("%s.z = (0 == point_id || num_segments+2 == point_id) ? 0 : 1;",
- coverages.vsOut());
-
- gpArgs->fPositionVar.set(kFloat2_GrSLType, "position");
- this->emitTransforms(v, varyingHandler, uniHandler, GrShaderVar("position", kFloat2_GrSLType),
- SkMatrix::I(), args.fFPCoordTransformHandler);
-
- // Use the 2 edge distances and interpolated butt cap AA to calculate fragment coverage.
- GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
- f->codeAppendf("half2 edge_coverages = min(%s.xy, .5);", coverages.fsIn());
- f->codeAppend ("half coverage = edge_coverages.x + edge_coverages.y;");
- f->codeAppendf("coverage *= %s.z;", coverages.fsIn()); // Butt cap AA.
-
- // As is common for CCPR, clockwise-winding triangles from the strip emit positive coverage, and
- // counter-clockwise triangles emit negative.
- f->codeAppendf("%s = half4(sk_Clockwise ? +coverage : -coverage);", args.fOutputColor);
-
- // This shader doesn't use the built-in Ganesh coverage.
- f->codeAppendf("%s = half4(1);", args.fOutputCoverage);
-}
-
-constexpr GrPrimitiveProcessor::Attribute CubicStrokeProcessor::kInstanceAttribs[];
-
-} // anonymous namespace
-
-void GrCCStroker::parseDeviceSpaceStroke(const SkPath& path, const SkPoint* deviceSpacePts,
- const SkStrokeRec& stroke, float strokeDevWidth,
- GrScissorTest scissorTest,
- const SkIRect& clippedDevIBounds,
- const SkIVector& devToAtlasOffset) {
- SkASSERT(SkStrokeRec::kStroke_Style == stroke.getStyle() ||
- SkStrokeRec::kHairline_Style == stroke.getStyle());
- SkASSERT(!fInstanceBuffer);
- SkASSERT(!path.isEmpty());
-
- if (!fHasOpenBatch) {
- fBatches.emplace_back(&fTalliesAllocator, *fInstanceCounts[(int)GrScissorTest::kDisabled],
- fScissorSubBatches.count());
- fInstanceCounts[(int)GrScissorTest::kDisabled] = fBatches.back().fNonScissorEndInstances;
- fHasOpenBatch = true;
- }
-
- InstanceTallies* currStrokeEndIndices;
- if (GrScissorTest::kEnabled == scissorTest) {
- SkASSERT(fBatches.back().fEndScissorSubBatch == fScissorSubBatches.count());
- fScissorSubBatches.emplace_back(
- &fTalliesAllocator, *fInstanceCounts[(int)GrScissorTest::kEnabled],
- clippedDevIBounds.makeOffset(devToAtlasOffset.x(), devToAtlasOffset.y()));
- fBatches.back().fEndScissorSubBatch = fScissorSubBatches.count();
- fInstanceCounts[(int)GrScissorTest::kEnabled] =
- currStrokeEndIndices = fScissorSubBatches.back().fEndInstances;
- } else {
- currStrokeEndIndices = fBatches.back().fNonScissorEndInstances;
- }
-
- fGeometry.beginPath(stroke, strokeDevWidth, currStrokeEndIndices);
-
- fPathInfos.push_back() = {devToAtlasOffset, strokeDevWidth/2, scissorTest};
-
- int devPtsIdx = 0;
- SkPath::Verb previousVerb = SkPath::kClose_Verb;
-
- for (SkPath::Verb verb : SkPathPriv::Verbs(path)) {
- SkASSERT(SkPath::kDone_Verb != previousVerb);
- const SkPoint* P = &deviceSpacePts[devPtsIdx - 1];
- switch (verb) {
- case SkPath::kMove_Verb:
- if (devPtsIdx > 0 && SkPath::kClose_Verb != previousVerb) {
- fGeometry.capContourAndExit();
- }
- fGeometry.moveTo(deviceSpacePts[devPtsIdx]);
- ++devPtsIdx;
- break;
- case SkPath::kClose_Verb:
- SkASSERT(SkPath::kClose_Verb != previousVerb);
- fGeometry.closeContour();
- break;
- case SkPath::kLine_Verb:
- SkASSERT(SkPath::kClose_Verb != previousVerb);
- fGeometry.lineTo(P[1]);
- ++devPtsIdx;
- break;
- case SkPath::kQuad_Verb:
- SkASSERT(SkPath::kClose_Verb != previousVerb);
- fGeometry.quadraticTo(P);
- devPtsIdx += 2;
- break;
- case SkPath::kCubic_Verb: {
- SkASSERT(SkPath::kClose_Verb != previousVerb);
- fGeometry.cubicTo(P);
- devPtsIdx += 3;
- break;
- }
- case SkPath::kConic_Verb:
- SkASSERT(SkPath::kClose_Verb != previousVerb);
- SK_ABORT("Stroked conics not supported.");
- break;
- case SkPath::kDone_Verb:
- break;
- }
- previousVerb = verb;
- }
-
- if (devPtsIdx > 0 && SkPath::kClose_Verb != previousVerb) {
- fGeometry.capContourAndExit();
- }
-}
-
-// This class encapsulates the process of expanding ready-to-draw geometry from GrCCStrokeGeometry
-// directly into GPU instance buffers.
-class GrCCStroker::InstanceBufferBuilder {
-public:
- InstanceBufferBuilder(GrOnFlushResourceProvider* onFlushRP, GrCCStroker* stroker) {
- memcpy(fNextInstances, stroker->fBaseInstances, sizeof(fNextInstances));
-#ifdef SK_DEBUG
- fEndInstances[0] = stroker->fBaseInstances[0] + *stroker->fInstanceCounts[0];
- fEndInstances[1] = stroker->fBaseInstances[1] + *stroker->fInstanceCounts[1];
-#endif
-
- int endConicsIdx = stroker->fBaseInstances[1].fConics +
- stroker->fInstanceCounts[1]->fConics;
- fInstanceBuffer = onFlushRP->makeBuffer(kVertex_GrBufferType,
- endConicsIdx * sizeof(ConicInstance));
- if (!fInstanceBuffer) {
- SkDebugf("WARNING: failed to allocate CCPR stroke instance buffer.\n");
- return;
- }
- fInstanceBufferData = fInstanceBuffer->map();
- }
-
- bool isMapped() const { return SkToBool(fInstanceBufferData); }
-
- void updateCurrentInfo(const PathInfo& pathInfo) {
- SkASSERT(this->isMapped());
- fCurrDX = static_cast<float>(pathInfo.fDevToAtlasOffset.x());
- fCurrDY = static_cast<float>(pathInfo.fDevToAtlasOffset.y());
- fCurrStrokeRadius = pathInfo.fStrokeRadius;
- fCurrNextInstances = &fNextInstances[(int)pathInfo.fScissorTest];
- SkDEBUGCODE(fCurrEndInstances = &fEndInstances[(int)pathInfo.fScissorTest]);
- }
-
- void appendLinearStroke(const SkPoint endpts[2]) {
- SkASSERT(this->isMapped());
- this->appendLinearStrokeInstance().set(endpts, fCurrDX, fCurrDY, fCurrStrokeRadius);
- }
-
- void appendQuadraticStroke(const SkPoint P[3], int numLinearSegmentsLog2) {
- SkASSERT(this->isMapped());
- SkASSERT(numLinearSegmentsLog2 > 0);
-
- Sk4f ptsT[2];
- Sk2f p0 = Sk2f::Load(P);
- Sk2f p1 = Sk2f::Load(P+1);
- Sk2f p2 = Sk2f::Load(P+2);
-
- // Convert the quadratic to cubic.
- Sk2f c1 = SkNx_fma(Sk2f(2/3.f), p1 - p0, p0);
- Sk2f c2 = SkNx_fma(Sk2f(1/3.f), p2 - p1, p1);
- Sk2f::Store4(ptsT, p0, c1, c2, p2);
-
- this->appendCubicStrokeInstance(numLinearSegmentsLog2).set(
- ptsT[0], ptsT[1], fCurrDX, fCurrDY, fCurrStrokeRadius, 1 << numLinearSegmentsLog2);
- }
-
- void appendCubicStroke(const SkPoint P[3], int numLinearSegmentsLog2) {
- SkASSERT(this->isMapped());
- SkASSERT(numLinearSegmentsLog2 > 0);
- this->appendCubicStrokeInstance(numLinearSegmentsLog2).set(
- P, fCurrDX, fCurrDY, fCurrStrokeRadius, 1 << numLinearSegmentsLog2);
- }
-
- void appendJoin(Verb joinVerb, const SkPoint& center, const SkVector& leftNorm,
- const SkVector& rightNorm, float miterCapHeightOverWidth, float conicWeight) {
- SkASSERT(this->isMapped());
-
- Sk2f offset = Sk2f::Load(¢er) + Sk2f(fCurrDX, fCurrDY);
- Sk2f n0 = Sk2f::Load(&leftNorm);
- Sk2f n1 = Sk2f::Load(&rightNorm);
-
- // Identify the outer edge.
- Sk2f cross = n0 * SkNx_shuffle<1,0>(n1);
- if (cross[0] < cross[1]) {
- Sk2f tmp = n0;
- n0 = -n1;
- n1 = -tmp;
- }
-
- if (!GrCCStrokeGeometry::IsInternalJoinVerb(joinVerb)) {
- // Normal joins are a triangle that connects the outer corners of two adjoining strokes.
- this->appendTriangleInstance().set(n1 * fCurrStrokeRadius, Sk2f(0, 0),
- n0 * fCurrStrokeRadius, offset);
- if (Verb::kBevelJoin == joinVerb) {
- return;
- }
- } else {
- // Internal joins 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 implement this geometry with a pair of triangles.
- this->appendTriangleInstance().set(-n0 * fCurrStrokeRadius, n0 * fCurrStrokeRadius,
- n1 * fCurrStrokeRadius, offset);
- this->appendTriangleInstance().set(-n0 * fCurrStrokeRadius, n1 * fCurrStrokeRadius,
- -n1 * fCurrStrokeRadius, offset);
- if (Verb::kInternalBevelJoin == joinVerb) {
- return;
- }
- }
-
- // For miter and round joins, we place an additional triangle cap on top of the bevel. This
- // triangle is literal for miters and is conic control points for round joins.
- SkASSERT(miterCapHeightOverWidth >= 0);
- Sk2f base = n1 - n0;
- Sk2f baseNorm = Sk2f(base[1], -base[0]);
- Sk2f c = (n0 + n1) * .5f + baseNorm * miterCapHeightOverWidth;
-
- if (Verb::kMiterJoin == joinVerb) {
- this->appendTriangleInstance().set(n0 * fCurrStrokeRadius, c * fCurrStrokeRadius,
- n1 * fCurrStrokeRadius, offset);
- } else {
- SkASSERT(Verb::kRoundJoin == joinVerb || Verb::kInternalRoundJoin == joinVerb);
- this->appendConicInstance().setW(n0 * fCurrStrokeRadius, c * fCurrStrokeRadius,
- n1 * fCurrStrokeRadius, offset, conicWeight);
- if (Verb::kInternalRoundJoin == joinVerb) {
- this->appendConicInstance().setW(-n1 * fCurrStrokeRadius, c * -fCurrStrokeRadius,
- -n0 * fCurrStrokeRadius, offset, conicWeight);
- }
- }
- }
-
- void appendCap(Verb capType, const SkPoint& pt, const SkVector& norm) {
- SkASSERT(this->isMapped());
-
- Sk2f n = Sk2f::Load(&norm) * fCurrStrokeRadius;
- Sk2f v = Sk2f(-n[1], n[0]);
- Sk2f offset = Sk2f::Load(&pt) + Sk2f(fCurrDX, fCurrDY);
-
- if (Verb::kSquareCap == capType) {
- SkPoint endPts[2] = {{0, 0}, {v[0], v[1]}};
- this->appendLinearStrokeInstance().set(endPts, offset[0], offset[1], fCurrStrokeRadius);
- } else {
- SkASSERT(Verb::kRoundCap == capType);
- this->appendTriangleInstance().set(n, v, -n, offset);
- this->appendConicInstance().setW(n, n + v, v, offset, SK_ScalarRoot2Over2);
- this->appendConicInstance().setW(v, v - n, -n, offset, SK_ScalarRoot2Over2);
- }
- }
-
- sk_sp<GrBuffer> finish() {
- SkASSERT(this->isMapped());
- SkASSERT(!memcmp(fNextInstances, fEndInstances, sizeof(fNextInstances)));
- fInstanceBuffer->unmap();
- fInstanceBufferData = nullptr;
- SkASSERT(!this->isMapped());
- return std::move(fInstanceBuffer);
- }
-
-private:
- LinearStrokeInstance& appendLinearStrokeInstance() {
- int instanceIdx = fCurrNextInstances->fStrokes[0]++;
- SkASSERT(instanceIdx < fCurrEndInstances->fStrokes[0]);
-
- return reinterpret_cast<LinearStrokeInstance*>(fInstanceBufferData)[instanceIdx];
- }
-
- CubicStrokeInstance& appendCubicStrokeInstance(int numLinearSegmentsLog2) {
- SkASSERT(numLinearSegmentsLog2 > 0);
- SkASSERT(numLinearSegmentsLog2 <= kMaxNumLinearSegmentsLog2);
-
- int instanceIdx = fCurrNextInstances->fStrokes[numLinearSegmentsLog2]++;
- SkASSERT(instanceIdx < fCurrEndInstances->fStrokes[numLinearSegmentsLog2]);
-
- return reinterpret_cast<CubicStrokeInstance*>(fInstanceBufferData)[instanceIdx];
- }
-
- TriangleInstance& appendTriangleInstance() {
- int instanceIdx = fCurrNextInstances->fTriangles++;
- SkASSERT(instanceIdx < fCurrEndInstances->fTriangles);
-
- return reinterpret_cast<TriangleInstance*>(fInstanceBufferData)[instanceIdx];
- }
-
- ConicInstance& appendConicInstance() {
- int instanceIdx = fCurrNextInstances->fConics++;
- SkASSERT(instanceIdx < fCurrEndInstances->fConics);
-
- return reinterpret_cast<ConicInstance*>(fInstanceBufferData)[instanceIdx];
- }
-
- float fCurrDX, fCurrDY;
- float fCurrStrokeRadius;
- InstanceTallies* fCurrNextInstances;
- SkDEBUGCODE(const InstanceTallies* fCurrEndInstances);
-
- sk_sp<GrBuffer> fInstanceBuffer;
- void* fInstanceBufferData = nullptr;
- InstanceTallies fNextInstances[2];
- SkDEBUGCODE(InstanceTallies fEndInstances[2]);
-};
-
-GrCCStroker::BatchID GrCCStroker::closeCurrentBatch() {
- if (!fHasOpenBatch) {
- return kEmptyBatchID;
- }
- int start = (fBatches.count() < 2) ? 0 : fBatches[fBatches.count() - 2].fEndScissorSubBatch;
- int end = fBatches.back().fEndScissorSubBatch;
- fMaxNumScissorSubBatches = SkTMax(fMaxNumScissorSubBatches, end - start);
- fHasOpenBatch = false;
- return fBatches.count() - 1;
-}
-
-bool GrCCStroker::prepareToDraw(GrOnFlushResourceProvider* onFlushRP) {
- SkASSERT(!fInstanceBuffer);
- SkASSERT(!fHasOpenBatch); // Call closeCurrentBatch() first.
-
- // Here we layout a single instance buffer to share with every internal batch.
- //
- // Rather than place each instance array in its own GPU buffer, we allocate a single
- // megabuffer and lay them all out side-by-side. We can offset the "baseInstance" parameter in
- // our draw calls to direct the GPU to the applicable elements within a given array.
- fBaseInstances[0].fStrokes[0] = 0;
- fBaseInstances[1].fStrokes[0] = fInstanceCounts[0]->fStrokes[0];
- int endLinearStrokesIdx = fBaseInstances[1].fStrokes[0] + fInstanceCounts[1]->fStrokes[0];
-
- int cubicStrokesIdx = GR_CT_DIV_ROUND_UP(endLinearStrokesIdx * sizeof(LinearStrokeInstance),
- sizeof(CubicStrokeInstance));
- for (int i = 1; i <= kMaxNumLinearSegmentsLog2; ++i) {
- for (int j = 0; j < kNumScissorModes; ++j) {
- fBaseInstances[j].fStrokes[i] = cubicStrokesIdx;
- cubicStrokesIdx += fInstanceCounts[j]->fStrokes[i];
- }
- }
-
- int trianglesIdx = GR_CT_DIV_ROUND_UP(cubicStrokesIdx * sizeof(CubicStrokeInstance),
- sizeof(TriangleInstance));
- fBaseInstances[0].fTriangles = trianglesIdx;
- fBaseInstances[1].fTriangles =
- fBaseInstances[0].fTriangles + fInstanceCounts[0]->fTriangles;
- int endTrianglesIdx =
- fBaseInstances[1].fTriangles + fInstanceCounts[1]->fTriangles;
-
- int conicsIdx = GR_CT_DIV_ROUND_UP(endTrianglesIdx * sizeof(TriangleInstance),
- sizeof(ConicInstance));
- fBaseInstances[0].fConics = conicsIdx;
- fBaseInstances[1].fConics = fBaseInstances[0].fConics + fInstanceCounts[0]->fConics;
-
- InstanceBufferBuilder builder(onFlushRP, this);
- if (!builder.isMapped()) {
- return false; // Buffer allocation failed.
- }
-
- // Now parse the GrCCStrokeGeometry and expand it into the instance buffer.
- int pathIdx = 0;
- int ptsIdx = 0;
- int paramsIdx = 0;
- int normalsIdx = 0;
-
- const SkTArray<GrCCStrokeGeometry::Parameter, true>& params = fGeometry.params();
- const SkTArray<SkPoint, true>& pts = fGeometry.points();
- const SkTArray<SkVector, true>& normals = fGeometry.normals();
-
- float miterCapHeightOverWidth=0, conicWeight=0;
-
- for (Verb verb : fGeometry.verbs()) {
- switch (verb) {
- case Verb::kBeginPath:
- builder.updateCurrentInfo(fPathInfos[pathIdx]);
- ++pathIdx;
- continue;
-
- case Verb::kLinearStroke:
- builder.appendLinearStroke(&pts[ptsIdx]);
- ++ptsIdx;
- continue;
- case Verb::kQuadraticStroke:
- builder.appendQuadraticStroke(&pts[ptsIdx],
- params[paramsIdx++].fNumLinearSegmentsLog2);
- ptsIdx += 2;
- ++normalsIdx;
- continue;
- case Verb::kCubicStroke:
- builder.appendCubicStroke(&pts[ptsIdx], params[paramsIdx++].fNumLinearSegmentsLog2);
- ptsIdx += 3;
- ++normalsIdx;
- continue;
-
- case Verb::kRoundJoin:
- case Verb::kInternalRoundJoin:
- conicWeight = params[paramsIdx++].fConicWeight;
- // fallthru
- case Verb::kMiterJoin:
- miterCapHeightOverWidth = params[paramsIdx++].fMiterCapHeightOverWidth;
- // fallthru
- case Verb::kBevelJoin:
- case Verb::kInternalBevelJoin:
- builder.appendJoin(verb, pts[ptsIdx], normals[normalsIdx], normals[normalsIdx + 1],
- miterCapHeightOverWidth, conicWeight);
- ++normalsIdx;
- continue;
-
- case Verb::kSquareCap:
- case Verb::kRoundCap:
- builder.appendCap(verb, pts[ptsIdx], normals[normalsIdx]);
- continue;
-
- case Verb::kEndContour:
- ++ptsIdx;
- ++normalsIdx;
- continue;
- }
- SK_ABORT("Invalid CCPR stroke element.");
- }
-
- fInstanceBuffer = builder.finish();
- SkASSERT(fPathInfos.count() == pathIdx);
- SkASSERT(pts.count() == ptsIdx);
- SkASSERT(normals.count() == normalsIdx);
-
- fMeshesBuffer.reserve((1 + fMaxNumScissorSubBatches) * kMaxNumLinearSegmentsLog2);
- fScissorsBuffer.reserve((1 + fMaxNumScissorSubBatches) * kMaxNumLinearSegmentsLog2);
- return true;
-}
-
-void GrCCStroker::drawStrokes(GrOpFlushState* flushState, BatchID batchID,
- const SkIRect& drawBounds) const {
- using PrimitiveType = GrCCCoverageProcessor::PrimitiveType;
- SkASSERT(fInstanceBuffer);
-
- if (kEmptyBatchID == batchID) {
- return;
- }
- const Batch& batch = fBatches[batchID];
- int startScissorSubBatch = (!batchID) ? 0 : fBatches[batchID - 1].fEndScissorSubBatch;
-
- const InstanceTallies* startIndices[2];
- startIndices[(int)GrScissorTest::kDisabled] = (!batchID)
- ? &fZeroTallies : fBatches[batchID - 1].fNonScissorEndInstances;
- startIndices[(int)GrScissorTest::kEnabled] = (!startScissorSubBatch)
- ? &fZeroTallies : fScissorSubBatches[startScissorSubBatch - 1].fEndInstances;
-
- GrPipeline pipeline(flushState->drawOpArgs().fProxy, GrScissorTest::kEnabled,
- SkBlendMode::kPlus);
-
- // Draw linear strokes.
- this->appendStrokeMeshesToBuffers(0, batch, startIndices, startScissorSubBatch, drawBounds);
- if (!fMeshesBuffer.empty()) {
- LinearStrokeProcessor linearProc;
- this->flushBufferedMeshesAsStrokes(linearProc, flushState, pipeline, drawBounds);
- }
-
- // Draw cubic strokes. (Quadratics were converted to cubics for GPU processing.)
- for (int i = 1; i <= kMaxNumLinearSegmentsLog2; ++i) {
- this->appendStrokeMeshesToBuffers(i, batch, startIndices, startScissorSubBatch, drawBounds);
- }
- if (!fMeshesBuffer.empty()) {
- CubicStrokeProcessor cubicProc;
- this->flushBufferedMeshesAsStrokes(cubicProc, flushState, pipeline, drawBounds);
- }
-
- // Draw triangles.
- GrCCCoverageProcessor triProc(flushState->resourceProvider(), PrimitiveType::kTriangles);
- this->drawConnectingGeometry<&InstanceTallies::fTriangles>(
- flushState, pipeline, triProc, batch, startIndices, startScissorSubBatch, drawBounds);
-
- // Draw conics.
- GrCCCoverageProcessor conicProc(flushState->resourceProvider(), PrimitiveType::kConics);
- this->drawConnectingGeometry<&InstanceTallies::fConics>(
- flushState, pipeline, conicProc, batch, startIndices, startScissorSubBatch, drawBounds);
-}
-
-void GrCCStroker::appendStrokeMeshesToBuffers(int numSegmentsLog2, const Batch& batch,
- const InstanceTallies* startIndices[2],
- int startScissorSubBatch,
- const SkIRect& drawBounds) const {
- // Linear strokes draw a quad. Cubic strokes emit a strip with normals at "numSegments"
- // evenly-spaced points along the curve, plus one more for the final endpoint, plus two more for
- // AA butt caps. (i.e., 2 vertices * (numSegments + 3).)
- int numStripVertices = (0 == numSegmentsLog2) ? 4 : ((1 << numSegmentsLog2) + 3) * 2;
-
- // Append non-scissored meshes.
- int baseInstance = fBaseInstances[(int)GrScissorTest::kDisabled].fStrokes[numSegmentsLog2];
- int startIdx = startIndices[(int)GrScissorTest::kDisabled]->fStrokes[numSegmentsLog2];
- int endIdx = batch.fNonScissorEndInstances->fStrokes[numSegmentsLog2];
- SkASSERT(endIdx >= startIdx);
- if (int instanceCount = endIdx - startIdx) {
- GrMesh& mesh = fMeshesBuffer.emplace_back(GrPrimitiveType::kTriangleStrip);
- mesh.setInstanced(fInstanceBuffer.get(), instanceCount, baseInstance + startIdx,
- numStripVertices);
- fScissorsBuffer.push_back(drawBounds);
- }
-
- // Append scissored meshes.
- baseInstance = fBaseInstances[(int)GrScissorTest::kEnabled].fStrokes[numSegmentsLog2];
- startIdx = startIndices[(int)GrScissorTest::kEnabled]->fStrokes[numSegmentsLog2];
- for (int i = startScissorSubBatch; i < batch.fEndScissorSubBatch; ++i) {
- const ScissorSubBatch& subBatch = fScissorSubBatches[i];
- endIdx = subBatch.fEndInstances->fStrokes[numSegmentsLog2];
- SkASSERT(endIdx >= startIdx);
- if (int instanceCount = endIdx - startIdx) {
- GrMesh& mesh = fMeshesBuffer.emplace_back(GrPrimitiveType::kTriangleStrip);
- mesh.setInstanced(fInstanceBuffer.get(), instanceCount, baseInstance + startIdx,
- numStripVertices);
- fScissorsBuffer.push_back(subBatch.fScissor);
- startIdx = endIdx;
- }
- }
-}
-
-void GrCCStroker::flushBufferedMeshesAsStrokes(const GrPrimitiveProcessor& processor,
- GrOpFlushState* flushState,
- const GrPipeline& pipeline,
- const SkIRect& drawBounds) const {
- SkASSERT(fMeshesBuffer.count() == fScissorsBuffer.count());
- GrPipeline::DynamicStateArrays dynamicStateArrays;
- dynamicStateArrays.fScissorRects = fScissorsBuffer.begin();
- flushState->rtCommandBuffer()->draw(processor, pipeline, nullptr, &dynamicStateArrays,
- fMeshesBuffer.begin(), fMeshesBuffer.count(),
- SkRect::Make(drawBounds));
- // Don't call reset(), as that also resets the reserve count.
- fMeshesBuffer.pop_back_n(fMeshesBuffer.count());
- fScissorsBuffer.pop_back_n(fScissorsBuffer.count());
-}
-
-template<int GrCCStrokeGeometry::InstanceTallies::* InstanceType>
-void GrCCStroker::drawConnectingGeometry(GrOpFlushState* flushState, const GrPipeline& pipeline,
- const GrCCCoverageProcessor& processor,
- const Batch& batch, const InstanceTallies* startIndices[2],
- int startScissorSubBatch,
- const SkIRect& drawBounds) const {
- // Append non-scissored meshes.
- int baseInstance = fBaseInstances[(int)GrScissorTest::kDisabled].*InstanceType;
- int startIdx = startIndices[(int)GrScissorTest::kDisabled]->*InstanceType;
- int endIdx = batch.fNonScissorEndInstances->*InstanceType;
- SkASSERT(endIdx >= startIdx);
- if (int instanceCount = endIdx - startIdx) {
- processor.appendMesh(fInstanceBuffer.get(), instanceCount, baseInstance + startIdx,
- &fMeshesBuffer);
- fScissorsBuffer.push_back(drawBounds);
- }
-
- // Append scissored meshes.
- baseInstance = fBaseInstances[(int)GrScissorTest::kEnabled].*InstanceType;
- startIdx = startIndices[(int)GrScissorTest::kEnabled]->*InstanceType;
- for (int i = startScissorSubBatch; i < batch.fEndScissorSubBatch; ++i) {
- const ScissorSubBatch& subBatch = fScissorSubBatches[i];
- endIdx = subBatch.fEndInstances->*InstanceType;
- SkASSERT(endIdx >= startIdx);
- if (int instanceCount = endIdx - startIdx) {
- processor.appendMesh(fInstanceBuffer.get(), instanceCount, baseInstance + startIdx,
- &fMeshesBuffer);
- fScissorsBuffer.push_back(subBatch.fScissor);
- startIdx = endIdx;
- }
- }
-
- // Flush the geometry.
- if (!fMeshesBuffer.empty()) {
- SkASSERT(fMeshesBuffer.count() == fScissorsBuffer.count());
- processor.draw(flushState, pipeline, fScissorsBuffer.begin(), fMeshesBuffer.begin(),
- fMeshesBuffer.count(), SkRect::Make(drawBounds));
- // Don't call reset(), as that also resets the reserve count.
- fMeshesBuffer.pop_back_n(fMeshesBuffer.count());
- fScissorsBuffer.pop_back_n(fScissorsBuffer.count());
- }
-}
diff --git a/src/gpu/ccpr/GrCCStroker.h b/src/gpu/ccpr/GrCCStroker.h
deleted file mode 100644
index ac71011..0000000
--- a/src/gpu/ccpr/GrCCStroker.h
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef GrCCStroker_DEFINED
-#define GrCCStroker_DEFINED
-
-#include "GrAllocator.h"
-#include "GrMesh.h"
-#include "SkNx.h"
-#include "ccpr/GrCCStrokeGeometry.h"
-
-class GrBuffer;
-class GrCCCoverageProcessor;
-class GrOnFlushResourceProvider;
-class GrOpFlushState;
-class GrPipeline;
-class GrPrimitiveProcessor;
-class SkMatrix;
-class SkPath;
-class SkStrokeRec;
-
-/**
- * This class parses stroked SkPaths into a GPU instance buffer, then issues calls to draw their
- * coverage counts.
- */
-class GrCCStroker {
-public:
- GrCCStroker(int numPaths, int numSkPoints, int numSkVerbs)
- : fGeometry(numSkPoints, numSkVerbs), fPathInfos(numPaths) {}
-
- // Parses a device-space SkPath into the current batch, using the SkPath's original verbs with
- // 'deviceSpacePts', and the SkStrokeRec's original settings with 'strokeDevWidth'. Accepts an
- // optional post-device-space translate for placement in an atlas.
- //
- // Strokes intended as hairlines must have a strokeDevWidth of 1. Non-hairline strokes can only
- // be drawn with rigid body transforms; affine transformation of the stroke lines themselves is
- // not yet supported.
- void parseDeviceSpaceStroke(const SkPath&, const SkPoint* deviceSpacePts, const SkStrokeRec&,
- float strokeDevWidth, GrScissorTest,
- const SkIRect& clippedDevIBounds,
- const SkIVector& devToAtlasOffset);
-
- using BatchID = int;
-
- // Compiles the outstanding parsed paths into a batch, and returns an ID that can be used to
- // draw their strokes in the future.
- BatchID closeCurrentBatch();
-
- // Builds an internal GPU buffer and prepares for calls to drawStrokes(). Caller must close the
- // current batch before calling this method, and cannot parse new paths afer.
- bool prepareToDraw(GrOnFlushResourceProvider*);
-
- // Called after prepareToDraw(). Draws the given batch of path strokes.
- void drawStrokes(GrOpFlushState*, BatchID, const SkIRect& drawBounds) const;
-
-private:
- static constexpr int kNumScissorModes = 2;
- static constexpr BatchID kEmptyBatchID = -1;
- using Verb = GrCCStrokeGeometry::Verb;
- using InstanceTallies = GrCCStrokeGeometry::InstanceTallies;
-
- // Every kBeginPath verb has a corresponding PathInfo entry.
- struct PathInfo {
- SkIVector fDevToAtlasOffset;
- float fStrokeRadius;
- GrScissorTest fScissorTest;
- };
-
- // Defines a sub-batch of stroke instances that have a scissor test and the same scissor rect.
- // Start indices are deduced by looking at the previous ScissorSubBatch.
- struct ScissorSubBatch {
- ScissorSubBatch(GrTAllocator<InstanceTallies>* alloc, const InstanceTallies& startIndices,
- const SkIRect& scissor)
- : fEndInstances(&alloc->emplace_back(startIndices)), fScissor(scissor) {}
- InstanceTallies* fEndInstances;
- SkIRect fScissor;
- };
-
- // Defines a batch of stroke instances that can be drawn with drawStrokes(). Start indices are
- // deduced by looking at the previous Batch in the list.
- struct Batch {
- Batch(GrTAllocator<InstanceTallies>* alloc, const InstanceTallies& startNonScissorIndices,
- int startScissorSubBatch)
- : fNonScissorEndInstances(&alloc->emplace_back(startNonScissorIndices))
- , fEndScissorSubBatch(startScissorSubBatch) {}
- InstanceTallies* fNonScissorEndInstances;
- int fEndScissorSubBatch;
- };
-
- class InstanceBufferBuilder;
-
- void appendStrokeMeshesToBuffers(int numSegmentsLog2, const Batch&,
- const InstanceTallies* startIndices[2],
- int startScissorSubBatch, const SkIRect& drawBounds) const;
- void flushBufferedMeshesAsStrokes(const GrPrimitiveProcessor&, GrOpFlushState*, const
- GrPipeline&, const SkIRect& drawBounds) const;
-
- template<int GrCCStrokeGeometry::InstanceTallies::* InstanceType>
- void drawConnectingGeometry(GrOpFlushState*, const GrPipeline&,
- const GrCCCoverageProcessor&, const Batch&,
- const InstanceTallies* startIndices[2], int startScissorSubBatch,
- const SkIRect& drawBounds) const;
-
- GrCCStrokeGeometry fGeometry;
- SkSTArray<32, PathInfo> fPathInfos;
- SkSTArray<32, Batch> fBatches;
- SkSTArray<32, ScissorSubBatch> fScissorSubBatches;
- int fMaxNumScissorSubBatches = 0;
- bool fHasOpenBatch = false;
-
- const InstanceTallies fZeroTallies = InstanceTallies();
- GrSTAllocator<128, InstanceTallies> fTalliesAllocator;
- const InstanceTallies* fInstanceCounts[kNumScissorModes] = {&fZeroTallies, &fZeroTallies};
-
- sk_sp<GrBuffer> fInstanceBuffer;
- // The indices stored in batches are relative to these base instances.
- InstanceTallies fBaseInstances[kNumScissorModes];
-
- mutable SkSTArray<32, GrMesh> fMeshesBuffer;
- mutable SkSTArray<32, SkIRect> fScissorsBuffer;
-};
-
-#endif
diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
index 0899690..f783259 100644
--- a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
+++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
@@ -18,6 +18,21 @@
using PathInstance = GrCCPathProcessor::Instance;
+// If a path spans more pixels than this, we need to crop it or else analytic AA can run out of fp32
+// precision.
+static constexpr float kPathCropThreshold = 1 << 16;
+
+static void crop_path(const SkPath& path, const SkIRect& cropbox, SkPath* out) {
+ SkPath cropboxPath;
+ cropboxPath.addRect(SkRect::Make(cropbox));
+ if (!Op(cropboxPath, path, kIntersect_SkPathOp, out)) {
+ // This can fail if the PathOps encounter NaN or infinities.
+ out->reset();
+ }
+ out->setIsVolatile(true);
+}
+
+
GrCCPerOpListPaths::~GrCCPerOpListPaths() {
// Ensure there are no surviving DrawPathsOps with a dangling pointer into this class.
if (!fDrawOps.isEmpty()) {
@@ -69,69 +84,45 @@
GrPathRenderer::CanDrawPath GrCoverageCountingPathRenderer::onCanDrawPath(
const CanDrawPathArgs& args) const {
- const GrShape& shape = *args.fShape;
- if (GrAAType::kCoverage != args.fAAType || shape.style().hasPathEffect() ||
- args.fViewMatrix->hasPerspective() || shape.inverseFilled()) {
+ if (!args.fShape->style().isSimpleFill() || args.fShape->inverseFilled() ||
+ args.fViewMatrix->hasPerspective() || GrAAType::kCoverage != args.fAAType) {
return CanDrawPath::kNo;
}
SkPath path;
- shape.asPath(&path);
+ args.fShape->asPath(&path);
- switch (shape.style().strokeRec().getStyle()) {
- case SkStrokeRec::kFill_Style: {
- SkRect devBounds;
- args.fViewMatrix->mapRect(&devBounds, path.getBounds());
+ SkRect devBounds;
+ args.fViewMatrix->mapRect(&devBounds, path.getBounds());
- SkIRect clippedIBounds;
- devBounds.roundOut(&clippedIBounds);
- if (!clippedIBounds.intersect(*args.fClipConservativeBounds)) {
- // The path is completely clipped away. Our code will eventually notice this before
- // doing any real work.
- return CanDrawPath::kYes;
- }
-
- int64_t numPixels = sk_64_mul(clippedIBounds.height(), clippedIBounds.width());
- if (path.countVerbs() > 1000 && path.countPoints() > numPixels) {
- // This is a complicated path that has more vertices than pixels! Let's let the SW
- // renderer have this one: It will probably be faster and a bitmap will require less
- // total memory on the GPU than CCPR instance buffers would for the raw path data.
- return CanDrawPath::kNo;
- }
-
- if (numPixels > 256 * 256) {
- // Large paths can blow up the atlas fast. And they are not ideal for a two-pass
- // rendering algorithm. Give the simpler direct renderers a chance before we commit
- // to drawing it.
- return CanDrawPath::kAsBackup;
- }
-
- if (args.fShape->hasUnstyledKey() && path.countVerbs() > 50) {
- // Complex paths do better cached in an SDF, if the renderer will accept them.
- return CanDrawPath::kAsBackup;
- }
-
- return CanDrawPath::kYes;
- }
-
- case SkStrokeRec::kStroke_Style:
- if (!args.fViewMatrix->isSimilarity()) {
- // The stroker currently only supports rigid-body transfoms for the stroke lines
- // themselves. This limitation doesn't affect hairlines since their stroke lines are
- // defined relative to device space.
- return CanDrawPath::kNo;
- }
- // fallthru
- case SkStrokeRec::kHairline_Style:
- // The stroker does not support conics yet.
- return !SkPathPriv::ConicWeightCnt(path) ? CanDrawPath::kYes : CanDrawPath::kNo;
-
- case SkStrokeRec::kStrokeAndFill_Style:
- return CanDrawPath::kNo;
+ SkIRect clippedIBounds;
+ devBounds.roundOut(&clippedIBounds);
+ if (!clippedIBounds.intersect(*args.fClipConservativeBounds)) {
+ // Path is completely clipped away. Our code will eventually notice this before doing any
+ // real work.
+ return CanDrawPath::kYes;
}
- SK_ABORT("Invalid stroke style.");
- return CanDrawPath::kNo;
+ int64_t numPixels = sk_64_mul(clippedIBounds.height(), clippedIBounds.width());
+ if (path.countVerbs() > 1000 && path.countPoints() > numPixels) {
+ // This is a complicated path that has more vertices than pixels! Let's let the SW renderer
+ // have this one: It will probably be faster and a bitmap will require less total memory on
+ // the GPU than CCPR instance buffers would for the raw path data.
+ return CanDrawPath::kNo;
+ }
+
+ if (numPixels > 256 * 256) {
+ // Large paths can blow up the atlas fast. And they are not ideal for a two-pass rendering
+ // algorithm. Give the simpler direct renderers a chance before we commit to drawing it.
+ return CanDrawPath::kAsBackup;
+ }
+
+ if (args.fShape->hasUnstyledKey() && path.countVerbs() > 50) {
+ // Complex paths do better cached in an SDF, if the renderer will accept them.
+ return CanDrawPath::kAsBackup;
+ }
+
+ return CanDrawPath::kYes;
}
bool GrCoverageCountingPathRenderer::onDrawPath(const DrawPathArgs& args) {
@@ -141,8 +132,24 @@
GrRenderTargetContext* rtc = args.fRenderTargetContext;
args.fClip->getConservativeBounds(rtc->width(), rtc->height(), &clipIBounds, nullptr);
- auto op = GrCCDrawPathsOp::Make(args.fContext, clipIBounds, *args.fViewMatrix, *args.fShape,
- std::move(args.fPaint));
+ SkRect devBounds;
+ args.fViewMatrix->mapRect(&devBounds, args.fShape->bounds());
+
+ std::unique_ptr<GrCCDrawPathsOp> op;
+ if (SkTMax(devBounds.height(), devBounds.width()) > kPathCropThreshold) {
+ // The path is too large. Crop it or analytic AA can run out of fp32 precision.
+ SkPath croppedPath;
+ args.fShape->asPath(&croppedPath);
+ croppedPath.transform(*args.fViewMatrix, &croppedPath);
+ crop_path(croppedPath, clipIBounds, &croppedPath);
+ // FIXME: This breaks local coords: http://skbug.com/8003
+ op = GrCCDrawPathsOp::Make(args.fContext, clipIBounds, SkMatrix::I(), GrShape(croppedPath),
+ croppedPath.getBounds(), std::move(args.fPaint));
+ } else {
+ op = GrCCDrawPathsOp::Make(args.fContext, clipIBounds, *args.fViewMatrix, *args.fShape,
+ devBounds, std::move(args.fPaint));
+ }
+
this->recordOp(std::move(op), args);
return true;
}
@@ -173,7 +180,7 @@
// The path is too large. Crop it or analytic AA can run out of fp32 precision.
SkPath croppedPath;
int maxRTSize = caps.maxRenderTargetSize();
- CropPath(deviceSpacePath, SkIRect::MakeWH(maxRTSize, maxRTSize), &croppedPath);
+ crop_path(deviceSpacePath, SkIRect::MakeWH(maxRTSize, maxRTSize), &croppedPath);
clipPath.init(croppedPath, accessRect, rtWidth, rtHeight, caps);
} else {
clipPath.init(deviceSpacePath, accessRect, rtWidth, rtHeight, caps);
@@ -249,14 +256,11 @@
// Determine if there are enough reusable paths from last flush for it to be worth our time to
// copy them to cached atlas(es).
- int numCopies = specs.fNumCopiedPaths[GrCCPerFlushResourceSpecs::kFillIdx] +
- specs.fNumCopiedPaths[GrCCPerFlushResourceSpecs::kStrokeIdx];
- DoCopiesToCache doCopies = DoCopiesToCache(numCopies > 100 ||
+ DoCopiesToCache doCopies = DoCopiesToCache(specs.fNumCopiedPaths > 100 ||
specs.fCopyAtlasSpecs.fApproxNumPixels > 256 * 256);
- if (numCopies && DoCopiesToCache::kNo == doCopies) {
+ if (specs.fNumCopiedPaths && DoCopiesToCache::kNo == doCopies) {
specs.convertCopiesToRenders();
- SkASSERT(!specs.fNumCopiedPaths[GrCCPerFlushResourceSpecs::kFillIdx]);
- SkASSERT(!specs.fNumCopiedPaths[GrCCPerFlushResourceSpecs::kStrokeIdx]);
+ SkASSERT(!specs.fNumCopiedPaths);
}
auto resources = sk_make_sp<GrCCPerFlushResources>(onFlushRP, specs);
@@ -312,14 +316,3 @@
SkDEBUGCODE(fFlushing = false);
}
-
-void GrCoverageCountingPathRenderer::CropPath(const SkPath& path, const SkIRect& cropbox,
- SkPath* out) {
- SkPath cropboxPath;
- cropboxPath.addRect(SkRect::Make(cropbox));
- if (!Op(cropboxPath, path, kIntersect_SkPathOp, out)) {
- // This can fail if the PathOps encounter NaN or infinities.
- out->reset();
- }
- out->setIsVolatile(true);
-}
diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.h b/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
index bc336a5..0ec9aa0 100644
--- a/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
+++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
@@ -70,12 +70,6 @@
void testingOnly_drawPathDirectly(const DrawPathArgs&);
const GrUniqueKey& testingOnly_getStashedAtlasKey() const;
- // If a path spans more pixels than this, we need to crop it or else analytic AA can run out of
- // fp32 precision.
- static constexpr float kPathCropThreshold = 1 << 16;
-
- static void CropPath(const SkPath&, const SkIRect& cropbox, SkPath* out);
-
private:
GrCoverageCountingPathRenderer(AllowCaching);