diff --git a/bench/GrCCGeometryBench.cpp b/bench/GrCCFillGeometryBench.cpp similarity index 96% rename from bench/GrCCGeometryBench.cpp rename to bench/GrCCFillGeometryBench.cpp index 5406cea057..84a9e1ef9c 100644 --- a/bench/GrCCGeometryBench.cpp +++ b/bench/GrCCFillGeometryBench.cpp @@ -7,7 +7,7 @@ #include "Benchmark.h" -#include "ccpr/GrCCGeometry.h" +#include "ccpr/GrCCFillGeometry.h" #include "SkGeometry.h" static int kNumBaseLoops = 50000; @@ -61,7 +61,7 @@ public: private: SkPoint fPoints[5]; SkString fName; - GrCCGeometry fGeometry{4*100*kNumBaseLoops, 2*100*kNumBaseLoops}; + GrCCFillGeometry fGeometry{4*100*kNumBaseLoops, 2*100*kNumBaseLoops}; typedef Benchmark INHERITED; }; diff --git a/gn/bench.gni b/gn/bench.gni index 07a7e57116..60410ade4e 100644 --- a/gn/bench.gni +++ b/gn/bench.gni @@ -51,7 +51,7 @@ bench_sources = [ "$_bench/GeometryBench.cpp", "$_bench/GMBench.cpp", "$_bench/GradientBench.cpp", - "$_bench/GrCCGeometryBench.cpp", + "$_bench/GrCCFillGeometryBench.cpp", "$_bench/GrMemoryPoolBench.cpp", "$_bench/GrMipMapBench.cpp", "$_bench/GrResourceCacheBench.cpp", diff --git a/gn/gpu.gni b/gn/gpu.gni index fc115fb7de..b92b6cd303 100644 --- a/gn/gpu.gni +++ b/gn/gpu.gni @@ -313,12 +313,12 @@ skia_gpu_sources = [ "$_src/gpu/ccpr/GrCCCubicShader.h", "$_src/gpu/ccpr/GrCCDrawPathsOp.cpp", "$_src/gpu/ccpr/GrCCDrawPathsOp.h", - "$_src/gpu/ccpr/GrCCGeometry.cpp", - "$_src/gpu/ccpr/GrCCGeometry.h", + "$_src/gpu/ccpr/GrCCFiller.cpp", + "$_src/gpu/ccpr/GrCCFiller.h", + "$_src/gpu/ccpr/GrCCFillGeometry.cpp", + "$_src/gpu/ccpr/GrCCFillGeometry.h", "$_src/gpu/ccpr/GrCCPathCache.cpp", "$_src/gpu/ccpr/GrCCPathCache.h", - "$_src/gpu/ccpr/GrCCPathParser.cpp", - "$_src/gpu/ccpr/GrCCPathParser.h", "$_src/gpu/ccpr/GrCCPathProcessor.cpp", "$_src/gpu/ccpr/GrCCPathProcessor.h", "$_src/gpu/ccpr/GrCCPerFlushResources.cpp", diff --git a/samplecode/SampleCCPRGeometry.cpp b/samplecode/SampleCCPRGeometry.cpp index f883dbd016..8d34520e96 100644 --- a/samplecode/SampleCCPRGeometry.cpp +++ b/samplecode/SampleCCPRGeometry.cpp @@ -21,7 +21,7 @@ #include "SkPath.h" #include "SkRectPriv.h" #include "ccpr/GrCCCoverageProcessor.h" -#include "ccpr/GrCCGeometry.h" +#include "ccpr/GrCCFillGeometry.h" #include "gl/GrGLGpu.cpp" #include "glsl/GrGLSLFragmentProcessor.h" #include "ops/GrDrawOp.h" @@ -76,7 +76,8 @@ class CCPRGeometryView::DrawCoverageCountOp : public GrDrawOp { public: DrawCoverageCountOp(CCPRGeometryView* view) : INHERITED(ClassID()), fView(view) { - this->setBounds(SkRectPriv::MakeLargest(), GrOp::HasAABloat::kNo, GrOp::IsZeroArea::kNo); + this->setBounds(SkRect::MakeIWH(fView->width(), fView->height()), GrOp::HasAABloat::kNo, + GrOp::IsZeroArea::kNo); } const char* name() const override { @@ -191,9 +192,10 @@ void CCPRGeometryView::onDrawContent(SkCanvas* canvas) { if (GrRenderTargetContext* rtc = canvas->internal_private_accessTopLayerRenderTargetContext()) { // Render coverage count. GrContext* ctx = canvas->getGrContext(); + SkASSERT(ctx); + GrOpMemoryPool* pool = ctx->contextPriv().opMemoryPool(); - SkASSERT(ctx); sk_sp ccbuff = ctx->contextPriv().makeDeferredRenderTargetContext(SkBackingFit::kApprox, this->width(), this->height(), @@ -249,26 +251,27 @@ void CCPRGeometryView::onDrawContent(SkCanvas* canvas) { } void CCPRGeometryView::updateGpuData() { + using Verb = GrCCFillGeometry::Verb; fTriPointInstances.reset(); fQuadPointInstances.reset(); if (PrimitiveType::kCubics == fPrimitiveType) { double t[2], s[2]; fCubicType = GrPathUtils::getCubicKLM(fPoints, &fCubicKLM, t, s); - GrCCGeometry geometry; + GrCCFillGeometry geometry; geometry.beginContour(fPoints[0]); geometry.cubicTo(fPoints, kDebugBloat / 2, kDebugBloat / 2); geometry.endContour(); int ptsIdx = 0; - for (GrCCGeometry::Verb verb : geometry.verbs()) { + for (Verb verb : geometry.verbs()) { switch (verb) { - case GrCCGeometry::Verb::kLineTo: + case Verb::kLineTo: ++ptsIdx; continue; - case GrCCGeometry::Verb::kMonotonicQuadraticTo: + case Verb::kMonotonicQuadraticTo: ptsIdx += 2; continue; - case GrCCGeometry::Verb::kMonotonicCubicTo: + case Verb::kMonotonicCubicTo: fQuadPointInstances.push_back().set(&geometry.points()[ptsIdx], 0, 0); ptsIdx += 3; continue; @@ -278,7 +281,7 @@ void CCPRGeometryView::updateGpuData() { } } else if (PrimitiveType::kTriangles != fPrimitiveType) { SkPoint P3[3] = {fPoints[0], fPoints[1], fPoints[3]}; - GrCCGeometry geometry; + GrCCFillGeometry geometry; geometry.beginContour(P3[0]); if (PrimitiveType::kQuadratics == fPrimitiveType) { geometry.quadraticTo(P3); @@ -288,23 +291,22 @@ void CCPRGeometryView::updateGpuData() { } geometry.endContour(); int ptsIdx = 0, conicWeightIdx = 0; - for (GrCCGeometry::Verb verb : geometry.verbs()) { - if (GrCCGeometry::Verb::kBeginContour == verb || - GrCCGeometry::Verb::kEndOpenContour == verb || - GrCCGeometry::Verb::kEndClosedContour == verb) { + for (Verb verb : geometry.verbs()) { + if (Verb::kBeginContour == verb || + Verb::kEndOpenContour == verb || + Verb::kEndClosedContour == verb) { continue; } - if (GrCCGeometry::Verb::kLineTo == verb) { + if (Verb::kLineTo == verb) { ++ptsIdx; continue; } - SkASSERT(GrCCGeometry::Verb::kMonotonicQuadraticTo == verb || - GrCCGeometry::Verb::kMonotonicConicTo == verb); + SkASSERT(Verb::kMonotonicQuadraticTo == verb || Verb::kMonotonicConicTo == verb); if (PrimitiveType::kQuadratics == fPrimitiveType && - GrCCGeometry::Verb::kMonotonicQuadraticTo == verb) { + Verb::kMonotonicQuadraticTo == verb) { fTriPointInstances.push_back().set(&geometry.points()[ptsIdx], Sk2f(0, 0)); } else if (PrimitiveType::kConics == fPrimitiveType && - GrCCGeometry::Verb::kMonotonicConicTo == verb) { + Verb::kMonotonicConicTo == verb) { fQuadPointInstances.push_back().setW(&geometry.points()[ptsIdx], Sk2f(0, 0), geometry.getConicWeight(conicWeightIdx++)); } diff --git a/src/gpu/ccpr/GrCCGeometry.cpp b/src/gpu/ccpr/GrCCFillGeometry.cpp similarity index 94% rename from src/gpu/ccpr/GrCCGeometry.cpp rename to src/gpu/ccpr/GrCCFillGeometry.cpp index 17a54af560..81692cfb68 100644 --- a/src/gpu/ccpr/GrCCGeometry.cpp +++ b/src/gpu/ccpr/GrCCFillGeometry.cpp @@ -5,7 +5,7 @@ * found in the LICENSE file. */ -#include "GrCCGeometry.h" +#include "GrCCFillGeometry.h" #include "GrTypes.h" #include "SkGeometry.h" @@ -13,19 +13,14 @@ #include #include -// We convert between SkPoint and Sk2f freely throughout this file. -GR_STATIC_ASSERT(SK_SCALAR_IS_FLOAT); -GR_STATIC_ASSERT(2 * sizeof(float) == sizeof(SkPoint)); -GR_STATIC_ASSERT(0 == offsetof(SkPoint, fX)); - static constexpr float kFlatnessThreshold = 1/16.f; // 1/16 of a pixel. -void GrCCGeometry::beginPath() { +void GrCCFillGeometry::beginPath() { SkASSERT(!fBuildingContour); fVerbs.push_back(Verb::kBeginPath); } -void GrCCGeometry::beginContour(const SkPoint& pt) { +void GrCCFillGeometry::beginContour(const SkPoint& pt) { SkASSERT(!fBuildingContour); // Store the current verb count in the fTriangles field for now. When we close the contour we // will use this value to calculate the actual number of triangles in its fan. @@ -38,7 +33,7 @@ void GrCCGeometry::beginContour(const SkPoint& pt) { SkDEBUGCODE(fBuildingContour = true); } -void GrCCGeometry::lineTo(const SkPoint P[2]) { +void GrCCFillGeometry::lineTo(const SkPoint P[2]) { SkASSERT(fBuildingContour); SkASSERT(P[0] == fPoints.back()); Sk2f p0 = Sk2f::Load(P); @@ -46,7 +41,7 @@ void GrCCGeometry::lineTo(const SkPoint P[2]) { this->appendLine(p0, p1); } -inline void GrCCGeometry::appendLine(const Sk2f& p0, const Sk2f& p1) { +inline void GrCCFillGeometry::appendLine(const Sk2f& p0, const Sk2f& p1) { SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1])); if ((p0 == p1).allTrue()) { return; @@ -138,7 +133,7 @@ template static inline SkNx lerp(const SkNx& a, const S return SkNx_fma(t, b - a, a); } -void GrCCGeometry::quadraticTo(const SkPoint P[3]) { +void GrCCFillGeometry::quadraticTo(const SkPoint P[3]) { SkASSERT(fBuildingContour); SkASSERT(P[0] == fPoints.back()); Sk2f p0 = Sk2f::Load(P); @@ -155,7 +150,7 @@ void GrCCGeometry::quadraticTo(const SkPoint P[3]) { this->appendQuadratics(p0, p1, p2); } -inline void GrCCGeometry::appendQuadratics(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2) { +inline void GrCCFillGeometry::appendQuadratics(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2) { Sk2f tan0 = p1 - p0; Sk2f tan1 = p2 - p1; @@ -193,7 +188,8 @@ inline void GrCCGeometry::appendQuadratics(const Sk2f& p0, const Sk2f& p1, const this->appendMonotonicQuadratic(p012, p12, p2); } -inline void GrCCGeometry::appendMonotonicQuadratic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2) { +inline void GrCCFillGeometry::appendMonotonicQuadratic(const Sk2f& p0, const Sk2f& p1, + const Sk2f& p2) { // Don't send curves to the GPU if we know they are nearly flat (or just very small). if (are_collinear(p0, p1, p2)) { this->appendLine(p0, p2); @@ -465,7 +461,7 @@ static inline void find_chops_around_loop_intersection(float padRadius, Sk2f t2, } } -void GrCCGeometry::cubicTo(const SkPoint P[4], float inflectPad, float loopIntersectPad) { +void GrCCFillGeometry::cubicTo(const SkPoint P[4], float inflectPad, float loopIntersectPad) { SkASSERT(fBuildingContour); SkASSERT(P[0] == fPoints.back()); @@ -541,9 +537,9 @@ static inline void chop_cubic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2, co *abcd = lerp(*abc, *bcd, TT); } -void GrCCGeometry::appendCubics(AppendCubicMode mode, const Sk2f& p0, const Sk2f& p1, - const Sk2f& p2, const Sk2f& p3, const float chops[], int numChops, - float localT0, float localT1) { +void GrCCFillGeometry::appendCubics(AppendCubicMode mode, const Sk2f& p0, const Sk2f& p1, + const Sk2f& p2, const Sk2f& p3, const float chops[], + int numChops, float localT0, float localT1) { if (numChops) { SkASSERT(numChops > 0); int midChopIdx = numChops/2; @@ -576,8 +572,8 @@ void GrCCGeometry::appendCubics(AppendCubicMode mode, const Sk2f& p0, const Sk2f this->appendCubics(mode, p0, p1, p2, p3); } -void GrCCGeometry::appendCubics(AppendCubicMode mode, const Sk2f& p0, const Sk2f& p1, - const Sk2f& p2, const Sk2f& p3, int maxSubdivisions) { +void GrCCFillGeometry::appendCubics(AppendCubicMode mode, const Sk2f& p0, const Sk2f& p1, + const Sk2f& p2, const Sk2f& p3, int maxSubdivisions) { if (SkCubicType::kLoop != fCurrCubicType) { // Serpentines and cusps are always monotonic after chopping around inflection points. SkASSERT(!SkCubicIsDegenerate(fCurrCubicType)); @@ -672,11 +668,11 @@ static inline float find_midtangent(const Sk2f& tan0, const Sk2f& tan1, return std::abs(q*q - r) < std::abs(a*c - r) ? q/a : c/q; } -inline void GrCCGeometry::chopAndAppendCubicAtMidTangent(AppendCubicMode mode, const Sk2f& p0, - const Sk2f& p1, const Sk2f& p2, - const Sk2f& p3, const Sk2f& tan0, - const Sk2f& tan1, - int maxFutureSubdivisions) { +inline void GrCCFillGeometry::chopAndAppendCubicAtMidTangent(AppendCubicMode mode, const Sk2f& p0, + const Sk2f& p1, const Sk2f& p2, + const Sk2f& p3, const Sk2f& tan0, + const Sk2f& tan1, + int maxFutureSubdivisions) { float midT = find_midtangent(tan0, tan1, p3 + (p1 - p2)*3 - p0, (p0 - p1*2 + p2)*2, p1 - p0); @@ -694,7 +690,7 @@ inline void GrCCGeometry::chopAndAppendCubicAtMidTangent(AppendCubicMode mode, c this->appendCubics(mode, pT, p11, p12, p3, maxFutureSubdivisions); } -void GrCCGeometry::conicTo(const SkPoint P[3], float w) { +void GrCCFillGeometry::conicTo(const SkPoint P[3], float w) { SkASSERT(fBuildingContour); SkASSERT(P[0] == fPoints.back()); Sk2f p0 = Sk2f::Load(P); @@ -743,7 +739,8 @@ void GrCCGeometry::conicTo(const SkPoint P[3], float w) { this->appendMonotonicConic(p0, p1, p2, w); } -void GrCCGeometry::appendMonotonicConic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2, float w) { +void GrCCFillGeometry::appendMonotonicConic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2, + float w) { SkASSERT(w >= 0); Sk2f base = p2 - p0; @@ -784,7 +781,7 @@ void GrCCGeometry::appendMonotonicConic(const Sk2f& p0, const Sk2f& p1, const Sk ++fCurrContourTallies.fConics; } -GrCCGeometry::PrimitiveTallies GrCCGeometry::endContour() { +GrCCFillGeometry::PrimitiveTallies GrCCFillGeometry::endContour() { SkASSERT(fBuildingContour); SkASSERT(fVerbs.count() >= fCurrContourTallies.fTriangles); diff --git a/src/gpu/ccpr/GrCCGeometry.h b/src/gpu/ccpr/GrCCFillGeometry.h similarity index 83% rename from src/gpu/ccpr/GrCCGeometry.h rename to src/gpu/ccpr/GrCCFillGeometry.h index 5c5d1d2672..df817db10c 100644 --- a/src/gpu/ccpr/GrCCGeometry.h +++ b/src/gpu/ccpr/GrCCFillGeometry.h @@ -5,8 +5,8 @@ * found in the LICENSE file. */ -#ifndef GrGrCCGeometry_DEFINED -#define GrGrCCGeometry_DEFINED +#ifndef GrGrCCFillGeometry_DEFINED +#define GrGrCCFillGeometry_DEFINED #include "SkGeometry.h" #include "SkNx.h" @@ -15,14 +15,14 @@ /** * This class chops device-space contours up into a series of segments that CCPR knows how to - * render. (See GrCCGeometry::Verb.) + * fill. (See GrCCFillGeometry::Verb.) * * NOTE: This must be done in device space, since an affine transformation can change whether a * curve is monotonic. */ -class GrCCGeometry { +class GrCCFillGeometry { public: - // These are the verbs that CCPR knows how to draw. If a path has any segments that don't map to + // These are the verbs that CCPR knows how to fill. If a path has any segments that don't map to // this list, then they are chopped into smaller ones that do. A list of these comprise a // compact representation of what can later be expanded into GPU instance data. enum class Verb : uint8_t { @@ -49,7 +49,7 @@ public: bool operator==(const PrimitiveTallies&); }; - GrCCGeometry(int numSkPoints = 0, int numSkVerbs = 0, int numConicWeights = 0) + GrCCFillGeometry(int numSkPoints = 0, int numSkVerbs = 0, int numConicWeights = 0) : fPoints(numSkPoints * 3) // Reserve for a 3x expansion in points and verbs. , fVerbs(numSkVerbs * 3) , fConicWeights(numConicWeights * 3/2) {} @@ -64,17 +64,6 @@ public: fVerbs.reset(); } - // This is included in case the caller needs to discard previously added contours. It is up to - // the caller to track counts and ensure we don't pop back into the middle of a different - // contour. - void resize_back(int numPoints, int numVerbs) { - SkASSERT(!fBuildingContour); - fPoints.resize_back(numPoints); - fVerbs.resize_back(numVerbs); - SkASSERT(fVerbs.empty() || fVerbs.back() == Verb::kEndOpenContour || - fVerbs.back() == Verb::kEndClosedContour); - } - void beginPath(); void beginContour(const SkPoint&); void lineTo(const SkPoint P[2]); @@ -129,7 +118,7 @@ private: SkSTArray<32, float, true> fConicWeights; }; -inline void GrCCGeometry::PrimitiveTallies::operator+=(const PrimitiveTallies& b) { +inline void GrCCFillGeometry::PrimitiveTallies::operator+=(const PrimitiveTallies& b) { fTriangles += b.fTriangles; fWeightedTriangles += b.fWeightedTriangles; fQuadratics += b.fQuadratics; @@ -137,8 +126,8 @@ inline void GrCCGeometry::PrimitiveTallies::operator+=(const PrimitiveTallies& b fConics += b.fConics; } -GrCCGeometry::PrimitiveTallies -inline GrCCGeometry::PrimitiveTallies::operator-(const PrimitiveTallies& b) const { +GrCCFillGeometry::PrimitiveTallies +inline GrCCFillGeometry::PrimitiveTallies::operator-(const PrimitiveTallies& b) const { return {fTriangles - b.fTriangles, fWeightedTriangles - b.fWeightedTriangles, fQuadratics - b.fQuadratics, @@ -146,7 +135,7 @@ inline GrCCGeometry::PrimitiveTallies::operator-(const PrimitiveTallies& b) cons fConics - b.fConics}; } -inline bool GrCCGeometry::PrimitiveTallies::operator==(const PrimitiveTallies& b) { +inline bool GrCCFillGeometry::PrimitiveTallies::operator==(const PrimitiveTallies& b) { return fTriangles == b.fTriangles && fWeightedTriangles == b.fWeightedTriangles && fQuadratics == b.fQuadratics && fCubics == b.fCubics && fConics == b.fConics; } diff --git a/src/gpu/ccpr/GrCCPathParser.cpp b/src/gpu/ccpr/GrCCFiller.cpp similarity index 64% rename from src/gpu/ccpr/GrCCPathParser.cpp rename to src/gpu/ccpr/GrCCFiller.cpp index 190f1625a8..cdace98705 100644 --- a/src/gpu/ccpr/GrCCPathParser.cpp +++ b/src/gpu/ccpr/GrCCFiller.cpp @@ -5,7 +5,7 @@ * found in the LICENSE file. */ -#include "GrCCPathParser.h" +#include "GrCCFiller.h" #include "GrCaps.h" #include "GrGpuCommandBuffer.h" @@ -15,105 +15,36 @@ #include "SkPath.h" #include "SkPathPriv.h" #include "SkPoint.h" -#include "ccpr/GrCCGeometry.h" #include using TriPointInstance = GrCCCoverageProcessor::TriPointInstance; using QuadPointInstance = GrCCCoverageProcessor::QuadPointInstance; -GrCCPathParser::GrCCPathParser(int numPaths, const PathStats& pathStats) - // Overallocate by one point to accomodate for overflow with Sk4f. (See parsePath.) - : fLocalDevPtsBuffer(pathStats.fMaxPointsPerPath + 1) - , fGeometry(pathStats.fNumTotalSkPoints, pathStats.fNumTotalSkVerbs, +GrCCFiller::GrCCFiller(int numPaths, const PathStats& pathStats) + : fGeometry(pathStats.fNumTotalSkPoints, pathStats.fNumTotalSkVerbs, pathStats.fNumTotalConicWeights) - , fPathsInfo(numPaths) + , fPathInfos(numPaths) , fScissorSubBatches(numPaths) , fTotalPrimitiveCounts{PrimitiveTallies(), PrimitiveTallies()} { // Batches decide what to draw by looking where the previous one ended. Define initial batches // that "end" at the beginning of the data. These will not be drawn, but will only be be read by // the first actual batch. fScissorSubBatches.push_back() = {PrimitiveTallies(), SkIRect::MakeEmpty()}; - fCoverageCountBatches.push_back() = {PrimitiveTallies(), fScissorSubBatches.count(), - PrimitiveTallies()}; + fBatches.push_back() = {PrimitiveTallies(), fScissorSubBatches.count(), PrimitiveTallies()}; } -void GrCCPathParser::parsePath(const SkMatrix& m, const SkPath& path, SkRect* devBounds, - SkRect* devBounds45) { - const SkPoint* pts = SkPathPriv::PointData(path); - int numPts = path.countPoints(); - SkASSERT(numPts + 1 <= fLocalDevPtsBuffer.count()); +void GrCCFiller::parseDeviceSpaceFill(const SkPath& path, const SkPoint* deviceSpacePts, + GrScissorTest scissorTest, const SkIRect& clippedDevIBounds, + const SkIVector& devToAtlasOffset) { + SkASSERT(!fInstanceBuffer); // Can't call after prepareToDraw(). + SkASSERT(!path.isEmpty()); - if (!numPts) { - devBounds->setEmpty(); - devBounds45->setEmpty(); - this->parsePath(path, nullptr); - return; - } - - // m45 transforms path points into "45 degree" device space. A bounding box in this space gives - // the circumscribing octagon's diagonals. We could use SK_ScalarRoot2Over2, but an orthonormal - // transform is not necessary as long as the shader uses the correct inverse. - SkMatrix m45; - m45.setSinCos(1, 1); - m45.preConcat(m); - - // X,Y,T are two parallel view matrices that accumulate two bounding boxes as they map points: - // device-space bounds and "45 degree" device-space bounds (| 1 -1 | * devCoords). - // | 1 1 | - Sk4f X = Sk4f(m.getScaleX(), m.getSkewY(), m45.getScaleX(), m45.getSkewY()); - Sk4f Y = Sk4f(m.getSkewX(), m.getScaleY(), m45.getSkewX(), m45.getScaleY()); - Sk4f T = Sk4f(m.getTranslateX(), m.getTranslateY(), m45.getTranslateX(), m45.getTranslateY()); - - // Map the path's points to device space and accumulate bounding boxes. - Sk4f devPt = SkNx_fma(Y, Sk4f(pts[0].y()), T); - devPt = SkNx_fma(X, Sk4f(pts[0].x()), devPt); - Sk4f topLeft = devPt; - Sk4f bottomRight = devPt; - - // Store all 4 values [dev.x, dev.y, dev45.x, dev45.y]. We are only interested in the first two, - // and will overwrite [dev45.x, dev45.y] with the next point. This is why the dst buffer must - // be at least one larger than the number of points. - devPt.store(&fLocalDevPtsBuffer[0]); - - for (int i = 1; i < numPts; ++i) { - devPt = SkNx_fma(Y, Sk4f(pts[i].y()), T); - devPt = SkNx_fma(X, Sk4f(pts[i].x()), devPt); - topLeft = Sk4f::Min(topLeft, devPt); - bottomRight = Sk4f::Max(bottomRight, devPt); - devPt.store(&fLocalDevPtsBuffer[i]); - } - - SkPoint topLeftPts[2], bottomRightPts[2]; - topLeft.store(topLeftPts); - bottomRight.store(bottomRightPts); - devBounds->setLTRB(topLeftPts[0].x(), topLeftPts[0].y(), bottomRightPts[0].x(), - bottomRightPts[0].y()); - devBounds45->setLTRB(topLeftPts[1].x(), topLeftPts[1].y(), bottomRightPts[1].x(), - bottomRightPts[1].y()); - - this->parsePath(path, fLocalDevPtsBuffer.get()); -} - -void GrCCPathParser::parseDeviceSpacePath(const SkPath& deviceSpacePath) { - this->parsePath(deviceSpacePath, SkPathPriv::PointData(deviceSpacePath)); -} - -void GrCCPathParser::parsePath(const SkPath& path, const SkPoint* deviceSpacePts) { - SkASSERT(!fInstanceBuffer); // Can't call after finalize(). - SkASSERT(!fParsingPath); // Call saveParsedPath() or discardParsedPath() for the last one first. - SkDEBUGCODE(fParsingPath = true); - SkASSERT(path.isEmpty() || deviceSpacePts); - - fCurrPathPointsIdx = fGeometry.points().count(); - fCurrPathVerbsIdx = fGeometry.verbs().count(); - fCurrPathPrimitiveCounts = PrimitiveTallies(); + int currPathPointsIdx = fGeometry.points().count(); + int currPathVerbsIdx = fGeometry.verbs().count(); + PrimitiveTallies currPathPrimitiveCounts = PrimitiveTallies(); fGeometry.beginPath(); - if (path.isEmpty()) { - return; - } - const float* conicWeights = SkPathPriv::ConicWeightData(path); int ptsIdx = 0; int conicWeightsIdx = 0; @@ -122,13 +53,17 @@ void GrCCPathParser::parsePath(const SkPath& path, const SkPoint* deviceSpacePts for (SkPath::Verb verb : SkPathPriv::Verbs(path)) { switch (verb) { case SkPath::kMove_Verb: - this->endContourIfNeeded(insideContour); + if (insideContour) { + currPathPrimitiveCounts += fGeometry.endContour(); + } fGeometry.beginContour(deviceSpacePts[ptsIdx]); ++ptsIdx; insideContour = true; continue; case SkPath::kClose_Verb: - this->endContourIfNeeded(insideContour); + if (insideContour) { + currPathPrimitiveCounts += fGeometry.endContour(); + } insideContour = false; continue; case SkPath::kLine_Verb: @@ -155,123 +90,124 @@ void GrCCPathParser::parsePath(const SkPath& path, const SkPoint* deviceSpacePts SkASSERT(ptsIdx == path.countPoints()); SkASSERT(conicWeightsIdx == SkPathPriv::ConicWeightCnt(path)); - this->endContourIfNeeded(insideContour); -} - -void GrCCPathParser::endContourIfNeeded(bool insideContour) { if (insideContour) { - fCurrPathPrimitiveCounts += fGeometry.endContour(); + currPathPrimitiveCounts += fGeometry.endContour(); } -} -void GrCCPathParser::saveParsedPath(GrScissorTest scissorTest, const SkIRect& clippedDevIBounds, - const SkIVector& devToAtlasOffset) { - SkASSERT(fParsingPath); - - fPathsInfo.emplace_back(scissorTest, devToAtlasOffset); + fPathInfos.emplace_back(scissorTest, devToAtlasOffset); // Tessellate fans from very large and/or simple paths, in order to reduce overdraw. - int numVerbs = fGeometry.verbs().count() - fCurrPathVerbsIdx - 1; + int numVerbs = fGeometry.verbs().count() - currPathVerbsIdx - 1; int64_t tessellationWork = (int64_t)numVerbs * (32 - SkCLZ(numVerbs)); // N log N. int64_t fanningWork = (int64_t)clippedDevIBounds.height() * clippedDevIBounds.width(); if (tessellationWork * (50*50) + (100*100) < fanningWork) { // Don't tessellate under 100x100. - fCurrPathPrimitiveCounts.fTriangles = - fCurrPathPrimitiveCounts.fWeightedTriangles = 0; - - const SkTArray& verbs = fGeometry.verbs(); - const SkTArray& pts = fGeometry.points(); - int ptsIdx = fCurrPathPointsIdx; - - // Build an SkPath of the Redbook fan. We use "winding" fill type right now because we are - // producing a coverage count, and must fill in every region that has non-zero wind. The - // path processor will convert coverage count to the appropriate fill type later. - SkPath fan; - fan.setFillType(SkPath::kWinding_FillType); - SkASSERT(GrCCGeometry::Verb::kBeginPath == verbs[fCurrPathVerbsIdx]); - for (int i = fCurrPathVerbsIdx + 1; i < fGeometry.verbs().count(); ++i) { - switch (verbs[i]) { - case GrCCGeometry::Verb::kBeginPath: - SK_ABORT("Invalid GrCCGeometry"); - continue; - - case GrCCGeometry::Verb::kBeginContour: - fan.moveTo(pts[ptsIdx++]); - continue; - - case GrCCGeometry::Verb::kLineTo: - fan.lineTo(pts[ptsIdx++]); - continue; - - case GrCCGeometry::Verb::kMonotonicQuadraticTo: - case GrCCGeometry::Verb::kMonotonicConicTo: - fan.lineTo(pts[ptsIdx + 1]); - ptsIdx += 2; - continue; - - case GrCCGeometry::Verb::kMonotonicCubicTo: - fan.lineTo(pts[ptsIdx + 2]); - ptsIdx += 3; - continue; - - case GrCCGeometry::Verb::kEndClosedContour: - case GrCCGeometry::Verb::kEndOpenContour: - fan.close(); - continue; - } - } - GrTessellator::WindingVertex* vertices = nullptr; - int count = GrTessellator::PathToVertices(fan, std::numeric_limits::infinity(), - SkRect::Make(clippedDevIBounds), &vertices); - SkASSERT(0 == count % 3); - for (int i = 0; i < count; i += 3) { - int tessWinding = vertices[i].fWinding; - SkASSERT(tessWinding == vertices[i + 1].fWinding); - SkASSERT(tessWinding == vertices[i + 2].fWinding); - - // Ensure this triangle's points actually wind in the same direction as tessWinding. - // CCPR shaders use the sign of wind to determine which direction to bloat, so even for - // "wound" triangles the winding sign and point ordering need to agree. - float ax = vertices[i].fPos.fX - vertices[i + 1].fPos.fX; - float ay = vertices[i].fPos.fY - vertices[i + 1].fPos.fY; - float bx = vertices[i].fPos.fX - vertices[i + 2].fPos.fX; - float by = vertices[i].fPos.fY - vertices[i + 2].fPos.fY; - float wind = ax*by - ay*bx; - if ((wind > 0) != (-tessWinding > 0)) { // Tessellator has opposite winding sense. - std::swap(vertices[i + 1].fPos, vertices[i + 2].fPos); - } - - if (1 == abs(tessWinding)) { - ++fCurrPathPrimitiveCounts.fTriangles; - } else { - ++fCurrPathPrimitiveCounts.fWeightedTriangles; - } - } - - fPathsInfo.back().adoptFanTessellation(vertices, count); + fPathInfos.back().tessellateFan(fGeometry, currPathVerbsIdx, currPathPointsIdx, + clippedDevIBounds, &currPathPrimitiveCounts); } - fTotalPrimitiveCounts[(int)scissorTest] += fCurrPathPrimitiveCounts; + fTotalPrimitiveCounts[(int)scissorTest] += currPathPrimitiveCounts; if (GrScissorTest::kEnabled == scissorTest) { fScissorSubBatches.push_back() = {fTotalPrimitiveCounts[(int)GrScissorTest::kEnabled], clippedDevIBounds.makeOffset(devToAtlasOffset.fX, devToAtlasOffset.fY)}; } - - SkDEBUGCODE(fParsingPath = false); } -void GrCCPathParser::discardParsedPath() { - SkASSERT(fParsingPath); - fGeometry.resize_back(fCurrPathPointsIdx, fCurrPathVerbsIdx); - SkDEBUGCODE(fParsingPath = false); +void GrCCFiller::PathInfo::tessellateFan(const GrCCFillGeometry& geometry, int verbsIdx, + int ptsIdx, const SkIRect& clippedDevIBounds, + PrimitiveTallies* newTriangleCounts) { + using Verb = GrCCFillGeometry::Verb; + SkASSERT(-1 == fFanTessellationCount); + SkASSERT(!fFanTessellation); + + const SkTArray& verbs = geometry.verbs(); + const SkTArray& pts = geometry.points(); + + newTriangleCounts->fTriangles = + newTriangleCounts->fWeightedTriangles = 0; + + // Build an SkPath of the Redbook fan. We use "winding" fill type right now because we are + // producing a coverage count, and must fill in every region that has non-zero wind. The + // path processor will convert coverage count to the appropriate fill type later. + SkPath fan; + fan.setFillType(SkPath::kWinding_FillType); + SkASSERT(Verb::kBeginPath == verbs[verbsIdx]); + for (int i = verbsIdx + 1; i < verbs.count(); ++i) { + switch (verbs[i]) { + case Verb::kBeginPath: + SK_ABORT("Invalid GrCCFillGeometry"); + continue; + + case Verb::kBeginContour: + fan.moveTo(pts[ptsIdx++]); + continue; + + case Verb::kLineTo: + fan.lineTo(pts[ptsIdx++]); + continue; + + case Verb::kMonotonicQuadraticTo: + case Verb::kMonotonicConicTo: + fan.lineTo(pts[ptsIdx + 1]); + ptsIdx += 2; + continue; + + case Verb::kMonotonicCubicTo: + fan.lineTo(pts[ptsIdx + 2]); + ptsIdx += 3; + continue; + + case Verb::kEndClosedContour: + case Verb::kEndOpenContour: + fan.close(); + continue; + } + } + + GrTessellator::WindingVertex* vertices = nullptr; + fFanTessellationCount = + GrTessellator::PathToVertices(fan, std::numeric_limits::infinity(), + SkRect::Make(clippedDevIBounds), &vertices); + if (fFanTessellationCount <= 0) { + SkASSERT(0 == fFanTessellationCount); + SkASSERT(nullptr == vertices); + return; + } + + SkASSERT(0 == fFanTessellationCount % 3); + for (int i = 0; i < fFanTessellationCount; i += 3) { + int tessWinding = vertices[i].fWinding; + SkASSERT(tessWinding == vertices[i + 1].fWinding); + SkASSERT(tessWinding == vertices[i + 2].fWinding); + + // Ensure this triangle's points actually wind in the same direction as tessWinding. + // CCPR shaders use the sign of wind to determine which direction to bloat, so even for + // "wound" triangles the winding sign and point ordering need to agree. + float ax = vertices[i].fPos.fX - vertices[i + 1].fPos.fX; + float ay = vertices[i].fPos.fY - vertices[i + 1].fPos.fY; + float bx = vertices[i].fPos.fX - vertices[i + 2].fPos.fX; + float by = vertices[i].fPos.fY - vertices[i + 2].fPos.fY; + float wind = ax*by - ay*bx; + if ((wind > 0) != (-tessWinding > 0)) { // Tessellator has opposite winding sense. + std::swap(vertices[i + 1].fPos, vertices[i + 2].fPos); + } + + if (1 == abs(tessWinding)) { + ++newTriangleCounts->fTriangles; + } else { + ++newTriangleCounts->fWeightedTriangles; + } + } + + fFanTessellation.reset(vertices); } -GrCCPathParser::CoverageCountBatchID GrCCPathParser::closeCurrentBatch() { +GrCCFiller::BatchID GrCCFiller::closeCurrentBatch() { SkASSERT(!fInstanceBuffer); - SkASSERT(!fCoverageCountBatches.empty()); + SkASSERT(!fBatches.empty()); - const auto& lastBatch = fCoverageCountBatches.back(); + const auto& lastBatch = fBatches.back(); int maxMeshes = 1 + fScissorSubBatches.count() - lastBatch.fEndScissorSubBatchIdx; fMaxMeshesPerDraw = SkTMax(fMaxMeshesPerDraw, maxMeshes); @@ -282,12 +218,12 @@ GrCCPathParser::CoverageCountBatchID GrCCPathParser::closeCurrentBatch() { lastScissorSubBatch.fEndPrimitiveIndices; // This will invalidate lastBatch. - fCoverageCountBatches.push_back() = { + fBatches.push_back() = { fTotalPrimitiveCounts[(int)GrScissorTest::kDisabled], fScissorSubBatches.count(), batchTotalCounts }; - return fCoverageCountBatches.count() - 1; + return fBatches.count() - 1; } // Emits a contour's triangle fan. @@ -334,7 +270,7 @@ static void emit_tessellated_fan(const GrTessellator::WindingVertex* vertices, i const Sk2f& devToAtlasOffset, TriPointInstance* triPointInstanceData, QuadPointInstance* quadPointInstanceData, - GrCCGeometry::PrimitiveTallies* indices) { + GrCCFillGeometry::PrimitiveTallies* indices) { for (int i = 0; i < numVertices; i += 3) { if (1 == abs(vertices[i].fWinding)) { triPointInstanceData[indices->fTriangles++].set(vertices[i].fPos, vertices[i + 1].fPos, @@ -347,11 +283,12 @@ static void emit_tessellated_fan(const GrTessellator::WindingVertex* vertices, i } } -bool GrCCPathParser::finalize(GrOnFlushResourceProvider* onFlushRP) { - SkASSERT(!fParsingPath); // Call saveParsedPath() or discardParsedPath(). - SkASSERT(fCoverageCountBatches.back().fEndNonScissorIndices == // Call closeCurrentBatch(). +bool GrCCFiller::prepareToDraw(GrOnFlushResourceProvider* onFlushRP) { + using Verb = GrCCFillGeometry::Verb; + SkASSERT(!fInstanceBuffer); + SkASSERT(fBatches.back().fEndNonScissorIndices == // Call closeCurrentBatch(). fTotalPrimitiveCounts[(int)GrScissorTest::kDisabled]); - SkASSERT(fCoverageCountBatches.back().fEndScissorSubBatchIdx == fScissorSubBatches.count()); + SkASSERT(fBatches.back().fEndScissorSubBatchIdx == fScissorSubBatches.count()); // Here we build a single instance buffer to share with every internal batch. // @@ -393,6 +330,7 @@ bool GrCCPathParser::finalize(GrOnFlushResourceProvider* onFlushRP) { fInstanceBuffer = onFlushRP->makeBuffer(kVertex_GrBufferType, quadEndIdx * sizeof(QuadPointInstance)); if (!fInstanceBuffer) { + SkDebugf("WARNING: failed to allocate CCPR fill instance buffer.\n"); return false; } @@ -401,7 +339,7 @@ bool GrCCPathParser::finalize(GrOnFlushResourceProvider* onFlushRP) { reinterpret_cast(triPointInstanceData); SkASSERT(quadPointInstanceData); - PathInfo* nextPathInfo = fPathsInfo.begin(); + PathInfo* nextPathInfo = fPathInfos.begin(); Sk2f devToAtlasOffset; PrimitiveTallies instanceIndices[2] = {fBaseInstances[0], fBaseInstances[1]}; PrimitiveTallies* currIndices = nullptr; @@ -413,9 +351,9 @@ bool GrCCPathParser::finalize(GrOnFlushResourceProvider* onFlushRP) { int nextConicWeightIdx = 0; // Expand the ccpr verbs into GPU instance buffers. - for (GrCCGeometry::Verb verb : fGeometry.verbs()) { + for (Verb verb : fGeometry.verbs()) { switch (verb) { - case GrCCGeometry::Verb::kBeginPath: + case Verb::kBeginPath: SkASSERT(currFan.empty()); currIndices = &instanceIndices[(int)nextPathInfo->scissorTest()]; devToAtlasOffset = Sk2f(static_cast(nextPathInfo->devToAtlasOffset().fX), @@ -429,7 +367,7 @@ bool GrCCPathParser::finalize(GrOnFlushResourceProvider* onFlushRP) { ++nextPathInfo; continue; - case GrCCGeometry::Verb::kBeginContour: + case Verb::kBeginContour: SkASSERT(currFan.empty()); ++ptsIdx; if (!currFanIsTessellated) { @@ -437,7 +375,7 @@ bool GrCCPathParser::finalize(GrOnFlushResourceProvider* onFlushRP) { } continue; - case GrCCGeometry::Verb::kLineTo: + case Verb::kLineTo: ++ptsIdx; if (!currFanIsTessellated) { SkASSERT(!currFan.empty()); @@ -445,7 +383,7 @@ bool GrCCPathParser::finalize(GrOnFlushResourceProvider* onFlushRP) { } continue; - case GrCCGeometry::Verb::kMonotonicQuadraticTo: + case Verb::kMonotonicQuadraticTo: triPointInstanceData[currIndices->fQuadratics++].set(&pts[ptsIdx], devToAtlasOffset); ptsIdx += 2; @@ -455,7 +393,7 @@ bool GrCCPathParser::finalize(GrOnFlushResourceProvider* onFlushRP) { } continue; - case GrCCGeometry::Verb::kMonotonicCubicTo: + case Verb::kMonotonicCubicTo: quadPointInstanceData[currIndices->fCubics++].set(&pts[ptsIdx], devToAtlasOffset[0], devToAtlasOffset[1]); ptsIdx += 3; @@ -465,7 +403,7 @@ bool GrCCPathParser::finalize(GrOnFlushResourceProvider* onFlushRP) { } continue; - case GrCCGeometry::Verb::kMonotonicConicTo: + case Verb::kMonotonicConicTo: quadPointInstanceData[currIndices->fConics++].setW( &pts[ptsIdx], devToAtlasOffset, fGeometry.getConicWeight(nextConicWeightIdx)); @@ -477,13 +415,13 @@ bool GrCCPathParser::finalize(GrOnFlushResourceProvider* onFlushRP) { } continue; - case GrCCGeometry::Verb::kEndClosedContour: // endPt == startPt. + case Verb::kEndClosedContour: // endPt == startPt. if (!currFanIsTessellated) { SkASSERT(!currFan.empty()); currFan.pop_back(); } // fallthru. - case GrCCGeometry::Verb::kEndOpenContour: // endPt != startPt. + case Verb::kEndOpenContour: // endPt != startPt. SkASSERT(!currFanIsTessellated || currFan.empty()); if (!currFanIsTessellated && currFan.count() >= 3) { int fanSize = currFan.count(); @@ -503,7 +441,7 @@ bool GrCCPathParser::finalize(GrOnFlushResourceProvider* onFlushRP) { fInstanceBuffer->unmap(); - SkASSERT(nextPathInfo == fPathsInfo.end()); + SkASSERT(nextPathInfo == fPathInfos.end()); SkASSERT(ptsIdx == pts.count() - 1); SkASSERT(instanceIndices[0].fTriangles == fBaseInstances[1].fTriangles); SkASSERT(instanceIndices[1].fTriangles == fBaseInstances[0].fQuadratics); @@ -522,13 +460,13 @@ bool GrCCPathParser::finalize(GrOnFlushResourceProvider* onFlushRP) { return true; } -void GrCCPathParser::drawCoverageCount(GrOpFlushState* flushState, CoverageCountBatchID batchID, - const SkIRect& drawBounds) const { +void GrCCFiller::drawFills(GrOpFlushState* flushState, BatchID batchID, + const SkIRect& drawBounds) const { using PrimitiveType = GrCCCoverageProcessor::PrimitiveType; SkASSERT(fInstanceBuffer); - const PrimitiveTallies& batchTotalCounts = fCoverageCountBatches[batchID].fTotalPrimitiveCounts; + const PrimitiveTallies& batchTotalCounts = fBatches[batchID].fTotalPrimitiveCounts; GrPipeline pipeline(flushState->drawOpArgs().fProxy, GrScissorTest::kEnabled, SkBlendMode::kPlus); @@ -559,11 +497,10 @@ void GrCCPathParser::drawCoverageCount(GrOpFlushState* flushState, CoverageCount } } -void GrCCPathParser::drawPrimitives(GrOpFlushState* flushState, const GrPipeline& pipeline, - CoverageCountBatchID batchID, - GrCCCoverageProcessor::PrimitiveType primitiveType, - int PrimitiveTallies::*instanceType, - const SkIRect& drawBounds) const { +void GrCCFiller::drawPrimitives(GrOpFlushState* flushState, const GrPipeline& pipeline, + BatchID batchID, GrCCCoverageProcessor::PrimitiveType primitiveType, + int PrimitiveTallies::*instanceType, + const SkIRect& drawBounds) const { SkASSERT(pipeline.isScissorEnabled()); // Don't call reset(), as that also resets the reserve count. @@ -573,9 +510,9 @@ void GrCCPathParser::drawPrimitives(GrOpFlushState* flushState, const GrPipeline GrCCCoverageProcessor proc(flushState->resourceProvider(), primitiveType); SkASSERT(batchID > 0); - SkASSERT(batchID < fCoverageCountBatches.count()); - const CoverageCountBatch& previousBatch = fCoverageCountBatches[batchID - 1]; - const CoverageCountBatch& batch = fCoverageCountBatches[batchID]; + SkASSERT(batchID < fBatches.count()); + const Batch& previousBatch = fBatches[batchID - 1]; + const Batch& batch = fBatches[batchID]; SkDEBUGCODE(int totalInstanceCount = 0); if (int instanceCount = batch.fEndNonScissorIndices.*instanceType - diff --git a/src/gpu/ccpr/GrCCFiller.h b/src/gpu/ccpr/GrCCFiller.h new file mode 100644 index 0000000000..40dc6577cf --- /dev/null +++ b/src/gpu/ccpr/GrCCFiller.h @@ -0,0 +1,132 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCPathParser_DEFINED +#define GrCCPathParser_DEFINED + +#include "GrMesh.h" +#include "SkPath.h" +#include "SkPathPriv.h" +#include "SkRect.h" +#include "SkRefCnt.h" +#include "GrTessellator.h" +#include "ccpr/GrCCCoverageProcessor.h" +#include "ccpr/GrCCFillGeometry.h" +#include "ops/GrDrawOp.h" + +class GrOnFlushResourceProvider; +class SkMatrix; +class SkPath; + +/** + * This class parses SkPaths into CCPR primitives in GPU buffers, then issues calls to draw their + * coverage counts. + */ +class GrCCFiller { +public: + 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. + void parseDeviceSpaceFill(const SkPath&, const SkPoint* deviceSpacePts, 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 fills in the future. + BatchID closeCurrentBatch(); + + // Builds internal GPU buffers and prepares for calls to drawFills(). 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 fills. + void drawFills(GrOpFlushState*, BatchID, const SkIRect& drawBounds) const; + +private: + static constexpr int kNumScissorModes = 2; + using PrimitiveTallies = GrCCFillGeometry::PrimitiveTallies; + + // Every kBeginPath verb has a corresponding PathInfo entry. + class PathInfo { + public: + PathInfo(GrScissorTest scissorTest, const SkIVector& devToAtlasOffset) + : fScissorTest(scissorTest), fDevToAtlasOffset(devToAtlasOffset) {} + + GrScissorTest scissorTest() const { return fScissorTest; } + const SkIVector& devToAtlasOffset() const { return fDevToAtlasOffset; } + + // An empty tessellation fan is also valid; we use negative count to denote not tessellated. + bool hasFanTessellation() const { return fFanTessellationCount >= 0; } + int fanTessellationCount() const { + SkASSERT(this->hasFanTessellation()); + return fFanTessellationCount; + } + const GrTessellator::WindingVertex* fanTessellation() const { + SkASSERT(this->hasFanTessellation()); + return fFanTessellation.get(); + } + void tessellateFan(const GrCCFillGeometry&, int verbsIdx, int ptsIdx, + const SkIRect& clippedDevIBounds, PrimitiveTallies* newTriangleCounts); + + private: + GrScissorTest fScissorTest; + SkIVector fDevToAtlasOffset; // Translation from device space to location in atlas. + int fFanTessellationCount = -1; + std::unique_ptr fFanTessellation; + }; + + // Defines a batch of CCPR primitives. Start indices are deduced by looking at the previous + // Batch in the list. + struct Batch { + PrimitiveTallies fEndNonScissorIndices; + int fEndScissorSubBatchIdx; + PrimitiveTallies fTotalPrimitiveCounts; + }; + + // Defines a sub-batch that will be drawn with the given scissor rect. Start indices are deduced + // by looking at the previous ScissorSubBatch in the list. + struct ScissorSubBatch { + PrimitiveTallies fEndPrimitiveIndices; + SkIRect fScissor; + }; + + void drawPrimitives(GrOpFlushState*, const GrPipeline&, BatchID, + GrCCCoverageProcessor::PrimitiveType, int PrimitiveTallies::*instanceType, + const SkIRect& drawBounds) const; + + GrCCFillGeometry fGeometry; + SkSTArray<32, PathInfo, true> fPathInfos; + SkSTArray<32, Batch, true> fBatches; + SkSTArray<32, ScissorSubBatch, true> fScissorSubBatches; + PrimitiveTallies fTotalPrimitiveCounts[kNumScissorModes]; + int fMaxMeshesPerDraw = 0; + + sk_sp fInstanceBuffer; + PrimitiveTallies fBaseInstances[kNumScissorModes]; + mutable SkSTArray<32, GrMesh> fMeshesScratchBuffer; + 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 7a00d35fd5..01781b08db 100644 --- a/src/gpu/ccpr/GrCCPathCache.cpp +++ b/src/gpu/ccpr/GrCCPathCache.cpp @@ -9,7 +9,6 @@ #include "GrShape.h" #include "SkNx.h" -#include "ccpr/GrCCPathParser.h" // The maximum number of cache entries we allow in our own cache. static constexpr int kMaxCacheCount = 1 << 16; diff --git a/src/gpu/ccpr/GrCCPathParser.h b/src/gpu/ccpr/GrCCPathParser.h deleted file mode 100644 index 9ec2a3c64a..0000000000 --- a/src/gpu/ccpr/GrCCPathParser.h +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#ifndef GrCCPathParser_DEFINED -#define GrCCPathParser_DEFINED - -#include "GrMesh.h" -#include "SkPath.h" -#include "SkPathPriv.h" -#include "SkRect.h" -#include "SkRefCnt.h" -#include "GrTessellator.h" -#include "ccpr/GrCCCoverageProcessor.h" -#include "ccpr/GrCCGeometry.h" -#include "ops/GrDrawOp.h" - -class GrOnFlushResourceProvider; -class SkMatrix; -class SkPath; - -/** - * This class parses SkPaths into CCPR primitives in GPU buffers, then issues calls to draw their - * coverage counts. - */ -class GrCCPathParser { -public: - struct PathStats { - int fMaxPointsPerPath = 0; - int fNumTotalSkPoints = 0; - int fNumTotalSkVerbs = 0; - int fNumTotalConicWeights = 0; - - void statPath(const SkPath&); - }; - - GrCCPathParser(int numPaths, const PathStats&); - - ~GrCCPathParser() { - // Enforce the contract that the client always calls saveParsedPath or discardParsedPath. - SkASSERT(!fParsingPath); - } - - using CoverageCountBatchID = int; - - // Parses an SkPath into a temporary staging area. The path will not be included in the current - // batch until there is a matching call to saveParsedPath. The user must complement this with a - // following call to either saveParsedPath or discardParsedPath. - // - // Returns two tight bounding boxes: device space and "45 degree" (| 1 -1 | * devCoords) space. - // | 1 1 | - void parsePath(const SkMatrix&, const SkPath&, SkRect* devBounds, SkRect* devBounds45); - - // Parses a device-space SkPath into a temporary staging area. The path will not be included in - // the current batch until there is a matching call to saveParsedPath. The user must complement - // this with a following call to either saveParsedPath or discardParsedPath. - void parseDeviceSpacePath(const SkPath&); - - // Commits the currently-parsed path from staging to the current batch, and specifies whether - // the mask should be rendered with a scissor in effect. Accepts an optional post-device-space - // translate for placement in an atlas. - void saveParsedPath(GrScissorTest, const SkIRect& clippedDevIBounds, - const SkIVector& devToAtlasOffset); - void discardParsedPath(); - - // Compiles the outstanding saved paths into a batch, and returns an ID that can be used to draw - // their coverage counts in the future. - CoverageCountBatchID closeCurrentBatch(); - - // Builds internal GPU buffers and prepares for calls to drawCoverageCount. Caller must close - // the current batch before calling this method, and cannot parse new paths afer. - bool finalize(GrOnFlushResourceProvider*); - - // Called after finalize. Draws the given batch of parsed paths. - void drawCoverageCount(GrOpFlushState*, CoverageCountBatchID, const SkIRect& drawBounds) const; - -private: - static constexpr int kNumScissorModes = 2; - using PrimitiveTallies = GrCCGeometry::PrimitiveTallies; - - // Every kBeginPath verb has a corresponding PathInfo entry. - class PathInfo { - public: - PathInfo(GrScissorTest scissorTest, const SkIVector& devToAtlasOffset) - : fScissorTest(scissorTest), fDevToAtlasOffset(devToAtlasOffset) {} - - GrScissorTest scissorTest() const { return fScissorTest; } - const SkIVector& devToAtlasOffset() const { return fDevToAtlasOffset; } - - // An empty tessellation fan is also valid; we use negative count to denote not tessellated. - bool hasFanTessellation() const { return fFanTessellationCount >= 0; } - int fanTessellationCount() const { - SkASSERT(this->hasFanTessellation()); - return fFanTessellationCount; - } - const GrTessellator::WindingVertex* fanTessellation() const { - SkASSERT(this->hasFanTessellation()); - return fFanTessellation.get(); - } - - void adoptFanTessellation(const GrTessellator::WindingVertex* vertices, int count) { - SkASSERT(count >= 0); - fFanTessellation.reset(vertices); - fFanTessellationCount = count; - } - - private: - GrScissorTest fScissorTest; - SkIVector fDevToAtlasOffset; // Translation from device space to location in atlas. - int fFanTessellationCount = -1; - std::unique_ptr fFanTessellation; - }; - - // Defines a batch of CCPR primitives. Start indices are deduced by looking at the previous - // CoverageCountBatch in the list. - struct CoverageCountBatch { - PrimitiveTallies fEndNonScissorIndices; - int fEndScissorSubBatchIdx; - PrimitiveTallies fTotalPrimitiveCounts; - }; - - // Defines a sub-batch from CoverageCountBatch that will be drawn with the given scissor rect. - // Start indices are deduced by looking at the previous ScissorSubBatch in the list. - struct ScissorSubBatch { - PrimitiveTallies fEndPrimitiveIndices; - SkIRect fScissor; - }; - - void parsePath(const SkPath&, const SkPoint* deviceSpacePts); - void endContourIfNeeded(bool insideContour); - - void drawPrimitives(GrOpFlushState*, const GrPipeline&, CoverageCountBatchID, - GrCCCoverageProcessor::PrimitiveType, int PrimitiveTallies::*instanceType, - const SkIRect& drawBounds) const; - - // Staging area for the path being parsed. - SkDEBUGCODE(int fParsingPath = false); - const SkAutoSTArray<32, SkPoint> fLocalDevPtsBuffer; - int fCurrPathPointsIdx; - int fCurrPathVerbsIdx; - PrimitiveTallies fCurrPathPrimitiveCounts; - - GrCCGeometry fGeometry; - SkSTArray<32, PathInfo, true> fPathsInfo; - SkSTArray<32, CoverageCountBatch, true> fCoverageCountBatches; - SkSTArray<32, ScissorSubBatch, true> fScissorSubBatches; - PrimitiveTallies fTotalPrimitiveCounts[kNumScissorModes]; - int fMaxMeshesPerDraw = 0; - - sk_sp fInstanceBuffer; - PrimitiveTallies fBaseInstances[kNumScissorModes]; - mutable SkSTArray<32, GrMesh> fMeshesScratchBuffer; - mutable SkSTArray<32, SkIRect> fScissorRectScratchBuffer; -}; - -inline void GrCCPathParser::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/GrCCPerFlushResources.cpp b/src/gpu/ccpr/GrCCPerFlushResources.cpp index 886d679d87..0ab4e5deeb 100644 --- a/src/gpu/ccpr/GrCCPerFlushResources.cpp +++ b/src/gpu/ccpr/GrCCPerFlushResources.cpp @@ -15,7 +15,7 @@ #include "SkMakeUnique.h" #include "ccpr/GrCCPathCache.h" -using CoverageCountBatchID = GrCCPathParser::CoverageCountBatchID; +using FillBatchID = GrCCFiller::BatchID; using PathInstance = GrCCPathProcessor::Instance; namespace { @@ -101,7 +101,7 @@ public: static std::unique_ptr Make(GrContext* context, sk_sp resources, - CoverageCountBatchID batchID, const SkISize& drawBounds) { + FillBatchID batchID, const SkISize& drawBounds) { GrOpMemoryPool* pool = context->contextPriv().opMemoryPool(); return pool->allocate(std::move(resources), batchID, drawBounds); @@ -111,20 +111,20 @@ public: const char* name() const override { return "RenderAtlasOp (CCPR)"; } void onExecute(GrOpFlushState* flushState) override { - fResources->pathParser().drawCoverageCount(flushState, fBatchID, fDrawBounds); + fResources->filler().drawFills(flushState, fBatchID, fDrawBounds); } private: friend class ::GrOpMemoryPool; // for ctor - RenderAtlasOp(sk_sp resources, CoverageCountBatchID batchID, + RenderAtlasOp(sk_sp resources, FillBatchID batchID, const SkISize& drawBounds) : AtlasOp(ClassID(), std::move(resources), drawBounds) , fBatchID(batchID) , fDrawBounds(SkIRect::MakeWH(drawBounds.width(), drawBounds.height())) { } - const CoverageCountBatchID fBatchID; + const FillBatchID fBatchID; const SkIRect fDrawBounds; }; @@ -138,7 +138,11 @@ static int inst_buffer_count(const GrCCPerFlushResourceSpecs& specs) { GrCCPerFlushResources::GrCCPerFlushResources(GrOnFlushResourceProvider* onFlushRP, const GrCCPerFlushResourceSpecs& specs) - : fPathParser(specs.fNumRenderedPaths + specs.fNumClipPaths, specs.fRenderedPathStats) + // 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(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()) @@ -183,6 +187,56 @@ GrCCAtlas* GrCCPerFlushResources::copyPathToCachedAtlas(const GrCCPathCacheEntry return &fCopyAtlasStack.current(); } +static void transform_path_pts(const SkMatrix& m, const SkPath& path, + const SkAutoSTArray<32, SkPoint>& outDevPts, SkRect* devBounds, + SkRect* devBounds45) { + const SkPoint* pts = SkPathPriv::PointData(path); + int numPts = path.countPoints(); + SkASSERT(numPts + 1 <= outDevPts.count()); + SkASSERT(numPts); + + // m45 transforms path points into "45 degree" device space. A bounding box in this space gives + // the circumscribing octagon's diagonals. We could use SK_ScalarRoot2Over2, but an orthonormal + // transform is not necessary as long as the shader uses the correct inverse. + SkMatrix m45; + m45.setSinCos(1, 1); + m45.preConcat(m); + + // X,Y,T are two parallel view matrices that accumulate two bounding boxes as they map points: + // device-space bounds and "45 degree" device-space bounds (| 1 -1 | * devCoords). + // | 1 1 | + Sk4f X = Sk4f(m.getScaleX(), m.getSkewY(), m45.getScaleX(), m45.getSkewY()); + Sk4f Y = Sk4f(m.getSkewX(), m.getScaleY(), m45.getSkewX(), m45.getScaleY()); + Sk4f T = Sk4f(m.getTranslateX(), m.getTranslateY(), m45.getTranslateX(), m45.getTranslateY()); + + // Map the path's points to device space and accumulate bounding boxes. + Sk4f devPt = SkNx_fma(Y, Sk4f(pts[0].y()), T); + devPt = SkNx_fma(X, Sk4f(pts[0].x()), devPt); + Sk4f topLeft = devPt; + Sk4f bottomRight = devPt; + + // Store all 4 values [dev.x, dev.y, dev45.x, dev45.y]. We are only interested in the first two, + // and will overwrite [dev45.x, dev45.y] with the next point. This is why the dst buffer must + // be at least one larger than the number of points. + devPt.store(&outDevPts[0]); + + for (int i = 1; i < numPts; ++i) { + devPt = SkNx_fma(Y, Sk4f(pts[i].y()), T); + devPt = SkNx_fma(X, Sk4f(pts[i].x()), devPt); + topLeft = Sk4f::Min(topLeft, devPt); + bottomRight = Sk4f::Max(bottomRight, devPt); + devPt.store(&outDevPts[i]); + } + + SkPoint topLeftPts[2], bottomRightPts[2]; + topLeft.store(topLeftPts); + bottomRight.store(bottomRightPts); + devBounds->setLTRB(topLeftPts[0].x(), topLeftPts[0].y(), bottomRightPts[0].x(), + bottomRightPts[0].y()); + devBounds45->setLTRB(topLeftPts[1].x(), topLeftPts[1].y(), bottomRightPts[1].x(), + bottomRightPts[1].y()); +} + const GrCCAtlas* GrCCPerFlushResources::renderPathInAtlas(const SkIRect& clipIBounds, const SkMatrix& m, const SkPath& path, SkRect* devBounds, SkRect* devBounds45, @@ -191,13 +245,24 @@ const GrCCAtlas* GrCCPerFlushResources::renderPathInAtlas(const SkIRect& clipIBo SkASSERT(this->isMapped()); SkASSERT(fNextPathInstanceIdx < fEndPathInstance); - fPathParser.parsePath(m, path, devBounds, devBounds45); + if (path.isEmpty()) { + SkDEBUGCODE(--fEndPathInstance); + return nullptr; + } + + transform_path_pts(m, path, fLocalDevPtsBuffer, devBounds, devBounds45); devBounds->roundOut(devIBounds); - if (!this->placeParsedPathInAtlas(clipIBounds, *devIBounds, devToAtlasOffset)) { + GrScissorTest scissorTest; + SkIRect clippedPathIBounds; + if (!this->placeRenderedPathInAtlas(clipIBounds, *devIBounds, &scissorTest, &clippedPathIBounds, + devToAtlasOffset)) { SkDEBUGCODE(--fEndPathInstance); return nullptr; // Path was degenerate or clipped away. } + + fFiller.parseDeviceSpaceFill(path, fLocalDevPtsBuffer.begin(), scissorTest, clippedPathIBounds, + *devToAtlasOffset); return &fRenderedAtlasStack.current(); } @@ -205,37 +270,45 @@ const GrCCAtlas* GrCCPerFlushResources::renderDeviceSpacePathInAtlas( const SkIRect& clipIBounds, const SkPath& devPath, const SkIRect& devPathIBounds, SkIVector* devToAtlasOffset) { SkASSERT(this->isMapped()); - fPathParser.parseDeviceSpacePath(devPath); - if (!this->placeParsedPathInAtlas(clipIBounds, devPathIBounds, devToAtlasOffset)) { + + if (devPath.isEmpty()) { return nullptr; } + + GrScissorTest scissorTest; + SkIRect clippedPathIBounds; + if (!this->placeRenderedPathInAtlas(clipIBounds, devPathIBounds, &scissorTest, + &clippedPathIBounds, devToAtlasOffset)) { + return nullptr; + } + + fFiller.parseDeviceSpaceFill(devPath, SkPathPriv::PointData(devPath), scissorTest, + clippedPathIBounds, *devToAtlasOffset); return &fRenderedAtlasStack.current(); } -bool GrCCPerFlushResources::placeParsedPathInAtlas(const SkIRect& clipIBounds, - const SkIRect& pathIBounds, - SkIVector* devToAtlasOffset) { - GrScissorTest scissorTest; - SkIRect clippedPathIBounds; +bool GrCCPerFlushResources::placeRenderedPathInAtlas(const SkIRect& clipIBounds, + const SkIRect& pathIBounds, + GrScissorTest* scissorTest, + SkIRect* clippedPathIBounds, + SkIVector* devToAtlasOffset) { if (clipIBounds.contains(pathIBounds)) { - clippedPathIBounds = pathIBounds; - scissorTest = GrScissorTest::kDisabled; - } else if (clippedPathIBounds.intersect(clipIBounds, pathIBounds)) { - scissorTest = GrScissorTest::kEnabled; + *clippedPathIBounds = pathIBounds; + *scissorTest = GrScissorTest::kDisabled; + } else if (clippedPathIBounds->intersect(clipIBounds, pathIBounds)) { + *scissorTest = GrScissorTest::kEnabled; } else { - fPathParser.discardParsedPath(); return false; } if (GrCCAtlas* retiredAtlas = - fRenderedAtlasStack.addRect(clippedPathIBounds, devToAtlasOffset)) { + fRenderedAtlasStack.addRect(*clippedPathIBounds, devToAtlasOffset)) { // 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(). - CoverageCountBatchID batchID = fPathParser.closeCurrentBatch(); + FillBatchID batchID = fFiller.closeCurrentBatch(); retiredAtlas->setUserBatchID(batchID); } - fPathParser.saveParsedPath(scissorTest, clippedPathIBounds, *devToAtlasOffset); return true; } @@ -253,14 +326,13 @@ bool GrCCPerFlushResources::finalize(GrOnFlushResourceProvider* onFlushRP, fCopyAtlasStack.current().setUserBatchID(fNextCopyInstanceIdx); } if (!fRenderedAtlasStack.empty()) { - CoverageCountBatchID batchID = fPathParser.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 call to fPathParser.closeCurrentBatch().) - if (!fPathParser.finalize(onFlushRP)) { - SkDebugf("WARNING: failed to allocate GPU buffers for CCPR. No paths will be drawn.\n"); + if (!fFiller.prepareToDraw(onFlushRP)) { return false; } @@ -320,5 +392,5 @@ void GrCCPerFlushResourceSpecs::convertCopiesToRenders() { fRenderedPathStats.fNumTotalSkPoints += fCopyPathStats.fNumTotalSkPoints; fRenderedPathStats.fNumTotalSkVerbs += fCopyPathStats.fNumTotalSkVerbs; fRenderedPathStats.fNumTotalConicWeights += fCopyPathStats.fNumTotalConicWeights; - fCopyPathStats = GrCCPathParser::PathStats(); + fCopyPathStats = GrCCFiller::PathStats(); } diff --git a/src/gpu/ccpr/GrCCPerFlushResources.h b/src/gpu/ccpr/GrCCPerFlushResources.h index cdc89f8312..3fa392eed9 100644 --- a/src/gpu/ccpr/GrCCPerFlushResources.h +++ b/src/gpu/ccpr/GrCCPerFlushResources.h @@ -10,7 +10,7 @@ #include "GrNonAtomicRef.h" #include "ccpr/GrCCAtlas.h" -#include "ccpr/GrCCPathParser.h" +#include "ccpr/GrCCFiller.h" #include "ccpr/GrCCPathProcessor.h" class GrCCPathCacheEntry; @@ -24,12 +24,12 @@ struct GrCCPerFlushResourceSpecs { int fNumCachedPaths = 0; int fNumCopiedPaths = 0; - GrCCPathParser::PathStats fCopyPathStats; + GrCCFiller::PathStats fCopyPathStats; GrCCAtlas::Specs fCopyAtlasSpecs; int fNumRenderedPaths = 0; int fNumClipPaths = 0; - GrCCPathParser::PathStats fRenderedPathStats; + GrCCFiller::PathStats fRenderedPathStats; GrCCAtlas::Specs fRenderedAtlasSpecs; bool isEmpty() const { @@ -85,7 +85,7 @@ public: SkTArray>* out); // Accessors used by draw calls, once the resources have been finalized. - const GrCCPathParser& pathParser() const { SkASSERT(!this->isMapped()); return fPathParser; } + const GrCCFiller& filler() const { SkASSERT(!this->isMapped()); return fFiller; } 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(); } @@ -107,10 +107,12 @@ public: } private: - bool placeParsedPathInAtlas(const SkIRect& clipIBounds, const SkIRect& pathIBounds, - SkIVector* devToAtlasOffset); + bool placeRenderedPathInAtlas(const SkIRect& clipIBounds, const SkIRect& pathIBounds, + GrScissorTest*, SkIRect* clippedPathIBounds, + SkIVector* devToAtlasOffset); - GrCCPathParser fPathParser; + const SkAutoSTArray<32, SkPoint> fLocalDevPtsBuffer; + GrCCFiller fFiller; GrCCAtlasStack fCopyAtlasStack; GrCCAtlasStack fRenderedAtlasStack; diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp index d68d4c2d98..f783259457 100644 --- a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp +++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp @@ -15,7 +15,6 @@ #include "ccpr/GrCCClipProcessor.h" #include "ccpr/GrCCDrawPathsOp.h" #include "ccpr/GrCCPathCache.h" -#include "ccpr/GrCCPathParser.h" using PathInstance = GrCCPathProcessor::Instance;