ccpr: Rename GrCCPathParser to GrCCFiller

Various renames and other refactorings that will allow us to add new
stroking classes alongside the existing code for fills.

Bug: skia:
Change-Id: Ib477f9e1d87f9d4c1604719f9af0695a53614081
Reviewed-on: https://skia-review.googlesource.com/147503
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
This commit is contained in:
Chris Dalton 2018-08-20 14:00:30 -06:00 committed by Skia Commit-Bot
parent 9453731470
commit e1639695bf
13 changed files with 446 additions and 483 deletions

View File

@ -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;
};

View File

@ -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",

View File

@ -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",

View File

@ -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<GrRenderTargetContext> 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++));
}

View File

@ -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 <cmath>
#include <cstdlib>
// 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<int N> static inline SkNx<N,float> lerp(const SkNx<N,float>& 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);

View File

@ -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;
}

View File

@ -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 <stdlib.h>
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<GrCCGeometry::Verb, true>& verbs = fGeometry.verbs();
const SkTArray<SkPoint, true>& 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<float>::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<Verb, true>& verbs = geometry.verbs();
const SkTArray<SkPoint, true>& 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<float>::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<QuadPointInstance*>(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<float>(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 -

132
src/gpu/ccpr/GrCCFiller.h Normal file
View File

@ -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<const GrTessellator::WindingVertex[]> 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<GrBuffer> 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

View File

@ -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;

View File

@ -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<const GrTessellator::WindingVertex[]> 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<GrBuffer> 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

View File

@ -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<GrDrawOp> Make(GrContext* context,
sk_sp<const GrCCPerFlushResources> resources,
CoverageCountBatchID batchID, const SkISize& drawBounds) {
FillBatchID batchID, const SkISize& drawBounds) {
GrOpMemoryPool* pool = context->contextPriv().opMemoryPool();
return pool->allocate<RenderAtlasOp>(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<const GrCCPerFlushResources> resources, CoverageCountBatchID batchID,
RenderAtlasOp(sk_sp<const GrCCPerFlushResources> 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();
}

View File

@ -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<sk_sp<GrRenderTargetContext>>* 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;

View File

@ -15,7 +15,6 @@
#include "ccpr/GrCCClipProcessor.h"
#include "ccpr/GrCCDrawPathsOp.h"
#include "ccpr/GrCCPathCache.h"
#include "ccpr/GrCCPathParser.h"
using PathInstance = GrCCPathProcessor::Instance;