blob: e73f7f5722a64f3ada618d136d511fb18e7ca3d2 [file] [log] [blame]
/*
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "GrPLSPathRenderer.h"
#include "SkChunkAlloc.h"
#include "SkGeometry.h"
#include "SkPathPriv.h"
#include "SkString.h"
#include "SkTSort.h"
#include "SkTraceEvent.h"
#include "GrBatchFlushState.h"
#include "GrBatchTest.h"
#include "GrCaps.h"
#include "GrContext.h"
#include "GrDefaultGeoProcFactory.h"
#include "GrPLSGeometryProcessor.h"
#include "GrInvariantOutput.h"
#include "GrPathUtils.h"
#include "GrProcessor.h"
#include "GrPipelineBuilder.h"
#include "GrStyle.h"
#include "GrTessellator.h"
#include "batches/GrVertexBatch.h"
#include "glsl/GrGLSLGeometryProcessor.h"
#include "gl/builders/GrGLProgramBuilder.h"
#include "glsl/GrGLSLPLSPathRendering.h"
GrPLSPathRenderer::GrPLSPathRenderer() {
}
struct PLSVertex {
SkPoint fPos;
// for triangles, these are the three triangle vertices
// for quads, vert1 is the texture UV coords, and vert2 and vert3 are the line segment
// comprising the flat edge of the quad
SkPoint fVert1;
SkPoint fVert2;
SkPoint fVert3;
int fWinding;
};
typedef SkTArray<PLSVertex, true> PLSVertices;
typedef SkTArray<SkPoint, true> FinishVertices;
static const float kCubicTolerance = 0.5f;
static const float kConicTolerance = 0.5f;
static const float kBloatSize = 1.0f;
static const float kBloatLimit = 640000.0f;
#define kQuadNumVertices 5
static void add_quad(SkPoint pts[3], PLSVertices& vertices) {
SkPoint normal = SkPoint::Make(pts[0].fY - pts[2].fY,
pts[2].fX - pts[0].fX);
normal.setLength(kBloatSize);
SkScalar cross = (pts[1] - pts[0]).cross(pts[2] - pts[0]);
if (cross < 0) {
normal = -normal;
}
PLSVertex quad[kQuadNumVertices];
quad[0].fPos = pts[0] + normal;
quad[1].fPos = pts[0] - normal;
quad[2].fPos = pts[1] - normal;
quad[3].fPos = pts[2] - normal;
quad[4].fPos = pts[2] + normal;
for (int i = 0; i < kQuadNumVertices; i++) {
quad[i].fWinding = cross < 0 ? 1 : -1;
if (cross > 0.0) {
quad[i].fVert2 = pts[0];
quad[i].fVert3 = pts[2];
}
else {
quad[i].fVert2 = pts[2];
quad[i].fVert3 = pts[0];
}
}
GrPathUtils::QuadUVMatrix DevToUV(pts);
DevToUV.apply<kQuadNumVertices, sizeof(PLSVertex), sizeof(SkPoint)>(quad);
for (int i = 2; i < kQuadNumVertices; i++) {
vertices.push_back(quad[0]);
vertices.push_back(quad[i - 1]);
vertices.push_back(quad[i]);
}
}
/* Used by bloat_tri; outsets a single point. */
static bool outset(SkPoint* p1, SkPoint line1, SkPoint line2) {
// rotate the two line vectors 90 degrees to form the normals, and compute
// the dot product of the normals
SkScalar dotProd = line1.fY * line2.fY + line1.fX * line2.fX;
SkScalar lengthSq = 1.0f / ((1.0f - dotProd) / 2.0f);
if (lengthSq > kBloatLimit) {
return false;
}
SkPoint bisector = line1 + line2;
bisector.setLength(SkScalarSqrt(lengthSq) * kBloatSize);
*p1 += bisector;
return true;
}
/* Bloats a triangle so as to create a border kBloatSize pixels wide all around it. */
static bool bloat_tri(SkPoint pts[3]) {
SkPoint line1 = pts[0] - pts[1];
line1.normalize();
SkPoint line2 = pts[0] - pts[2];
line2.normalize();
SkPoint line3 = pts[1] - pts[2];
line3.normalize();
SkPoint result[3];
result[0] = pts[0];
if (!outset(&result[0], line1, line2)) {
return false;
}
result[1] = pts[1];
if (!outset(&result[1], -line1, line3)) {
return false;
}
result[2] = pts[2];
if (!outset(&result[2], -line3, -line2)) {
return false;
}
pts[0] = result[0];
pts[1] = result[1];
pts[2] = result[2];
return true;
}
static bool get_geometry(const SkPath& path, const SkMatrix& m, PLSVertices& triVertices,
PLSVertices& quadVertices, GrResourceProvider* resourceProvider,
SkRect bounds) {
SkScalar screenSpaceTol = GrPathUtils::kDefaultTolerance;
SkScalar tol = GrPathUtils::scaleToleranceToSrc(screenSpaceTol, m, bounds);
int contourCnt;
int maxPts = GrPathUtils::worstCasePointCount(path, &contourCnt, tol);
if (maxPts <= 0) {
return 0;
}
SkPath linesOnlyPath;
linesOnlyPath.setFillType(path.getFillType());
SkSTArray<15, SkPoint, true> quadPoints;
SkPath::Iter iter(path, true);
bool done = false;
while (!done) {
SkPoint pts[4];
SkPath::Verb verb = iter.next(pts);
switch (verb) {
case SkPath::kMove_Verb:
SkASSERT(quadPoints.count() % 3 == 0);
for (int i = 0; i < quadPoints.count(); i += 3) {
add_quad(&quadPoints[i], quadVertices);
}
quadPoints.reset();
m.mapPoints(&pts[0], 1);
linesOnlyPath.moveTo(pts[0]);
break;
case SkPath::kLine_Verb:
m.mapPoints(&pts[1], 1);
linesOnlyPath.lineTo(pts[1]);
break;
case SkPath::kQuad_Verb:
m.mapPoints(pts, 3);
linesOnlyPath.lineTo(pts[2]);
quadPoints.push_back(pts[0]);
quadPoints.push_back(pts[1]);
quadPoints.push_back(pts[2]);
break;
case SkPath::kCubic_Verb: {
m.mapPoints(pts, 4);
SkSTArray<15, SkPoint, true> quads;
GrPathUtils::convertCubicToQuads(pts, kCubicTolerance, &quads);
int count = quads.count();
for (int q = 0; q < count; q += 3) {
linesOnlyPath.lineTo(quads[q + 2]);
quadPoints.push_back(quads[q]);
quadPoints.push_back(quads[q + 1]);
quadPoints.push_back(quads[q + 2]);
}
break;
}
case SkPath::kConic_Verb: {
m.mapPoints(pts, 3);
SkScalar weight = iter.conicWeight();
SkAutoConicToQuads converter;
const SkPoint* quads = converter.computeQuads(pts, weight, kConicTolerance);
int count = converter.countQuads();
for (int i = 0; i < count; ++i) {
linesOnlyPath.lineTo(quads[2 * i + 2]);
quadPoints.push_back(quads[2 * i]);
quadPoints.push_back(quads[2 * i + 1]);
quadPoints.push_back(quads[2 * i + 2]);
}
break;
}
case SkPath::kClose_Verb:
linesOnlyPath.close();
break;
case SkPath::kDone_Verb:
done = true;
break;
default: SkASSERT(false);
}
}
SkASSERT(quadPoints.count() % 3 == 0);
for (int i = 0; i < quadPoints.count(); i += 3) {
add_quad(&quadPoints[i], quadVertices);
}
static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
GrUniqueKey key;
GrUniqueKey::Builder builder(&key, kDomain, 2);
builder[0] = path.getGenerationID();
builder[1] = path.getFillType();
builder.finish();
GrTessellator::WindingVertex* windingVertices;
int triVertexCount = GrTessellator::PathToVertices(linesOnlyPath, 0, bounds, &windingVertices);
if (triVertexCount > 0) {
for (int i = 0; i < triVertexCount; i += 3) {
SkPoint p1 = windingVertices[i].fPos;
SkPoint p2 = windingVertices[i + 1].fPos;
SkPoint p3 = windingVertices[i + 2].fPos;
int winding = windingVertices[i].fWinding;
SkASSERT(windingVertices[i + 1].fWinding == winding);
SkASSERT(windingVertices[i + 2].fWinding == winding);
SkScalar cross = (p2 - p1).cross(p3 - p1);
SkPoint bloated[3] = { p1, p2, p3 };
if (cross < 0.0f) {
SkTSwap(p1, p3);
}
if (bloat_tri(bloated)) {
triVertices.push_back({ bloated[0], p1, p2, p3, winding });
triVertices.push_back({ bloated[1], p1, p2, p3, winding });
triVertices.push_back({ bloated[2], p1, p2, p3, winding });
}
else {
SkScalar minX = SkTMin(p1.fX, SkTMin(p2.fX, p3.fX)) - 1.0f;
SkScalar minY = SkTMin(p1.fY, SkTMin(p2.fY, p3.fY)) - 1.0f;
SkScalar maxX = SkTMax(p1.fX, SkTMax(p2.fX, p3.fX)) + 1.0f;
SkScalar maxY = SkTMax(p1.fY, SkTMax(p2.fY, p3.fY)) + 1.0f;
triVertices.push_back({ { minX, minY }, p1, p2, p3, winding });
triVertices.push_back({ { maxX, minY }, p1, p2, p3, winding });
triVertices.push_back({ { minX, maxY }, p1, p2, p3, winding });
triVertices.push_back({ { maxX, minY }, p1, p2, p3, winding });
triVertices.push_back({ { maxX, maxY }, p1, p2, p3, winding });
triVertices.push_back({ { minX, maxY }, p1, p2, p3, winding });
}
}
delete[] windingVertices;
}
return triVertexCount > 0 || quadVertices.count() > 0;
}
class PLSAATriangleEffect : public GrPLSGeometryProcessor {
public:
static GrPLSGeometryProcessor* Create(const SkMatrix& localMatrix,
bool usesLocalCoords) {
return new PLSAATriangleEffect(localMatrix, usesLocalCoords);
}
virtual ~PLSAATriangleEffect() {}
const char* name() const override { return "PLSAATriangle"; }
const Attribute* inPosition() const { return fInPosition; }
const Attribute* inVertex1() const { return fInVertex1; }
const Attribute* inVertex2() const { return fInVertex2; }
const Attribute* inVertex3() const { return fInVertex3; }
const Attribute* inWindings() const { return fInWindings; }
const SkMatrix& localMatrix() const { return fLocalMatrix; }
bool usesLocalCoords() const { return fUsesLocalCoords; }
class GLSLProcessor : public GrGLSLGeometryProcessor {
public:
GLSLProcessor(const GrGeometryProcessor&) {}
void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
const PLSAATriangleEffect& te = args.fGP.cast<PLSAATriangleEffect>();
GrGLSLVertexBuilder* vsBuilder = args.fVertBuilder;
GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
varyingHandler->emitAttributes(te);
this->setupPosition(vsBuilder, gpArgs, te.inPosition()->fName);
GrGLSLVertToFrag v1(kVec2f_GrSLType);
varyingHandler->addVarying("Vertex1", &v1, kHigh_GrSLPrecision);
vsBuilder->codeAppendf("%s = vec2(%s.x, %s.y);",
v1.vsOut(),
te.inVertex1()->fName,
te.inVertex1()->fName);
GrGLSLVertToFrag v2(kVec2f_GrSLType);
varyingHandler->addVarying("Vertex2", &v2, kHigh_GrSLPrecision);
vsBuilder->codeAppendf("%s = vec2(%s.x, %s.y);",
v2.vsOut(),
te.inVertex2()->fName,
te.inVertex2()->fName);
GrGLSLVertToFrag v3(kVec2f_GrSLType);
varyingHandler->addVarying("Vertex3", &v3, kHigh_GrSLPrecision);
vsBuilder->codeAppendf("%s = vec2(%s.x, %s.y);",
v3.vsOut(),
te.inVertex3()->fName,
te.inVertex3()->fName);
GrGLSLVertToFrag delta1(kVec2f_GrSLType);
varyingHandler->addVarying("delta1", &delta1, kHigh_GrSLPrecision);
vsBuilder->codeAppendf("%s = vec2(%s.x - %s.x, %s.y - %s.y) * 0.5;",
delta1.vsOut(), v1.vsOut(), v2.vsOut(), v2.vsOut(), v1.vsOut());
GrGLSLVertToFrag delta2(kVec2f_GrSLType);
varyingHandler->addVarying("delta2", &delta2, kHigh_GrSLPrecision);
vsBuilder->codeAppendf("%s = vec2(%s.x - %s.x, %s.y - %s.y) * 0.5;",
delta2.vsOut(), v2.vsOut(), v3.vsOut(), v3.vsOut(), v2.vsOut());
GrGLSLVertToFrag delta3(kVec2f_GrSLType);
varyingHandler->addVarying("delta3", &delta3, kHigh_GrSLPrecision);
vsBuilder->codeAppendf("%s = vec2(%s.x - %s.x, %s.y - %s.y) * 0.5;",
delta3.vsOut(), v3.vsOut(), v1.vsOut(), v1.vsOut(), v3.vsOut());
GrGLSLVertToFrag windings(kInt_GrSLType);
varyingHandler->addFlatVarying("windings", &windings, kLow_GrSLPrecision);
vsBuilder->codeAppendf("%s = %s;",
windings.vsOut(), te.inWindings()->fName);
// emit transforms
this->emitTransforms(vsBuilder, varyingHandler, uniformHandler, gpArgs->fPositionVar,
te.inPosition()->fName, te.localMatrix(),
args.fFPCoordTransformHandler);
GrGLSLPPFragmentBuilder* fsBuilder = args.fFragBuilder;
SkAssertResult(fsBuilder->enableFeature(
GrGLSLFragmentShaderBuilder::kPixelLocalStorage_GLSLFeature));
fsBuilder->declAppendf(GR_GL_PLS_PATH_DATA_DECL);
// Compute four subsamples, each shifted a quarter pixel along x and y from
// gl_FragCoord. The oriented box positioning of the subsamples is of course not
// optimal, but it greatly simplifies the math and this simplification is necessary for
// performance reasons.
fsBuilder->codeAppendf("highp vec2 firstSample = %s.xy - vec2(0.25);",
fsBuilder->fragmentPosition());
fsBuilder->codeAppendf("highp vec2 delta1 = %s;", delta1.fsIn());
fsBuilder->codeAppendf("highp vec2 delta2 = %s;", delta2.fsIn());
fsBuilder->codeAppendf("highp vec2 delta3 = %s;", delta3.fsIn());
// Check whether first sample is inside the triangle by computing three dot products. If
// all are < 0, we're inside. The first vector in each case is half of what it is
// "supposed" to be, because we re-use them later as adjustment factors for which half
// is the correct value, so we multiply the dots by two to compensate.
fsBuilder->codeAppendf("highp float d1 = dot(delta1, (firstSample - %s).yx) * 2.0;",
v1.fsIn());
fsBuilder->codeAppendf("highp float d2 = dot(delta2, (firstSample - %s).yx) * 2.0;",
v2.fsIn());
fsBuilder->codeAppendf("highp float d3 = dot(delta3, (firstSample - %s).yx) * 2.0;",
v3.fsIn());
fsBuilder->codeAppend("highp float dmax = max(d1, max(d2, d3));");
fsBuilder->codeAppendf("pls.windings[0] += (dmax <= 0.0) ? %s : 0;", windings.fsIn());
// for subsequent samples, we don't recalculate the entire dot product -- just adjust it
// to the value it would have if we did recompute it.
fsBuilder->codeAppend("d1 += delta1.x;");
fsBuilder->codeAppend("d2 += delta2.x;");
fsBuilder->codeAppend("d3 += delta3.x;");
fsBuilder->codeAppend("dmax = max(d1, max(d2, d3));");
fsBuilder->codeAppendf("pls.windings[1] += (dmax <= 0.0) ? %s : 0;", windings.fsIn());
fsBuilder->codeAppend("d1 += delta1.y;");
fsBuilder->codeAppend("d2 += delta2.y;");
fsBuilder->codeAppend("d3 += delta3.y;");
fsBuilder->codeAppend("dmax = max(d1, max(d2, d3));");
fsBuilder->codeAppendf("pls.windings[2] += (dmax <= 0.0) ? %s : 0;", windings.fsIn());
fsBuilder->codeAppend("d1 -= delta1.x;");
fsBuilder->codeAppend("d2 -= delta2.x;");
fsBuilder->codeAppend("d3 -= delta3.x;");
fsBuilder->codeAppend("dmax = max(d1, max(d2, d3));");
fsBuilder->codeAppendf("pls.windings[3] += (dmax <= 0.0) ? %s : 0;", windings.fsIn());
}
static inline void GenKey(const GrGeometryProcessor& gp,
const GrGLSLCaps&,
GrProcessorKeyBuilder* b) {
const PLSAATriangleEffect& te = gp.cast<PLSAATriangleEffect>();
uint32_t key = 0;
key |= te.localMatrix().hasPerspective() ? 0x1 : 0x0;
b->add32(key);
}
void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& gp,
FPCoordTransformIter&& transformIter) override {
this->setTransformDataHelper(gp.cast<PLSAATriangleEffect>().fLocalMatrix, pdman,
&transformIter);
}
private:
typedef GrGLSLGeometryProcessor INHERITED;
};
virtual void getGLSLProcessorKey(const GrGLSLCaps& caps,
GrProcessorKeyBuilder* b) const override {
GLSLProcessor::GenKey(*this, caps, b);
}
virtual GrGLSLPrimitiveProcessor* createGLSLInstance(const GrGLSLCaps&) const override {
return new GLSLProcessor(*this);
}
private:
PLSAATriangleEffect(const SkMatrix& localMatrix, bool usesLocalCoords)
: fLocalMatrix(localMatrix)
, fUsesLocalCoords(usesLocalCoords) {
this->initClassID<PLSAATriangleEffect>();
fInPosition = &this->addVertexAttrib("inPosition", kVec2f_GrVertexAttribType,
kHigh_GrSLPrecision);
fInVertex1 = &this->addVertexAttrib("inVertex1", kVec2f_GrVertexAttribType,
kHigh_GrSLPrecision);
fInVertex2 = &this->addVertexAttrib("inVertex2", kVec2f_GrVertexAttribType,
kHigh_GrSLPrecision);
fInVertex3 = &this->addVertexAttrib("inVertex3", kVec2f_GrVertexAttribType,
kHigh_GrSLPrecision);
fInWindings = &this->addVertexAttrib("inWindings", kInt_GrVertexAttribType,
kLow_GrSLPrecision);
this->setWillReadFragmentPosition();
}
const Attribute* fInPosition;
const Attribute* fInVertex1;
const Attribute* fInVertex2;
const Attribute* fInVertex3;
const Attribute* fInWindings;
SkMatrix fLocalMatrix;
bool fUsesLocalCoords;
GR_DECLARE_GEOMETRY_PROCESSOR_TEST;
typedef GrGeometryProcessor INHERITED;
};
///////////////////////////////////////////////////////////////////////////////
/*
* Quadratic specified by 0=u^2-v canonical coords. u and v are the first
* two components of the vertex attribute. Coverage is based on signed
* distance with negative being inside, positive outside. The edge is specified in
* window space (y-down). If either the third or fourth component of the interpolated
* vertex coord is > 0 then the pixel is considered outside the edge. This is used to
* attempt to trim to a portion of the infinite quad.
* Requires shader derivative instruction support.
*/
class PLSQuadEdgeEffect : public GrPLSGeometryProcessor {
public:
static GrPLSGeometryProcessor* Create(const SkMatrix& localMatrix,
bool usesLocalCoords) {
return new PLSQuadEdgeEffect(localMatrix, usesLocalCoords);
}
virtual ~PLSQuadEdgeEffect() {}
const char* name() const override { return "PLSQuadEdge"; }
const Attribute* inPosition() const { return fInPosition; }
const Attribute* inUV() const { return fInUV; }
const Attribute* inEndpoint1() const { return fInEndpoint1; }
const Attribute* inEndpoint2() const { return fInEndpoint2; }
const Attribute* inWindings() const { return fInWindings; }
const SkMatrix& localMatrix() const { return fLocalMatrix; }
bool usesLocalCoords() const { return fUsesLocalCoords; }
class GLSLProcessor : public GrGLSLGeometryProcessor {
public:
GLSLProcessor(const GrGeometryProcessor&) {}
void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
const PLSQuadEdgeEffect& qe = args.fGP.cast<PLSQuadEdgeEffect>();
GrGLSLVertexBuilder* vsBuilder = args.fVertBuilder;
GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
// emit attributes
varyingHandler->emitAttributes(qe);
GrGLSLVertToFrag uv(kVec2f_GrSLType);
varyingHandler->addVarying("uv", &uv, kHigh_GrSLPrecision);
vsBuilder->codeAppendf("%s = %s;", uv.vsOut(), qe.inUV()->fName);
GrGLSLVertToFrag ep1(kVec2f_GrSLType);
varyingHandler->addVarying("endpoint1", &ep1, kHigh_GrSLPrecision);
vsBuilder->codeAppendf("%s = vec2(%s.x, %s.y);", ep1.vsOut(),
qe.inEndpoint1()->fName, qe.inEndpoint1()->fName);
GrGLSLVertToFrag ep2(kVec2f_GrSLType);
varyingHandler->addVarying("endpoint2", &ep2, kHigh_GrSLPrecision);
vsBuilder->codeAppendf("%s = vec2(%s.x, %s.y);", ep2.vsOut(),
qe.inEndpoint2()->fName, qe.inEndpoint2()->fName);
GrGLSLVertToFrag delta(kVec2f_GrSLType);
varyingHandler->addVarying("delta", &delta, kHigh_GrSLPrecision);
vsBuilder->codeAppendf("%s = vec2(%s.x - %s.x, %s.y - %s.y) * 0.5;",
delta.vsOut(), ep1.vsOut(), ep2.vsOut(), ep2.vsOut(),
ep1.vsOut());
GrGLSLVertToFrag windings(kInt_GrSLType);
varyingHandler->addFlatVarying("windings", &windings, kLow_GrSLPrecision);
vsBuilder->codeAppendf("%s = %s;",
windings.vsOut(), qe.inWindings()->fName);
// Setup position
this->setupPosition(vsBuilder, gpArgs, qe.inPosition()->fName);
// emit transforms
this->emitTransforms(vsBuilder, varyingHandler, uniformHandler, gpArgs->fPositionVar,
qe.inPosition()->fName, qe.localMatrix(),
args.fFPCoordTransformHandler);
GrGLSLPPFragmentBuilder* fsBuilder = args.fFragBuilder;
SkAssertResult(fsBuilder->enableFeature(
GrGLSLFragmentShaderBuilder::kPixelLocalStorage_GLSLFeature));
static const int QUAD_ARGS = 2;
GrGLSLShaderVar inQuadArgs[QUAD_ARGS] = {
GrGLSLShaderVar("dot", kFloat_GrSLType, 0, kHigh_GrSLPrecision),
GrGLSLShaderVar("uv", kVec2f_GrSLType, 0, kHigh_GrSLPrecision)
};
SkString inQuadName;
const char* inQuadCode = "if (uv.x * uv.x <= uv.y) {"
"return dot >= 0.0;"
"} else {"
"return false;"
"}";
fsBuilder->emitFunction(kBool_GrSLType, "in_quad", QUAD_ARGS, inQuadArgs, inQuadCode,
&inQuadName);
fsBuilder->declAppendf(GR_GL_PLS_PATH_DATA_DECL);
// keep the derivative instructions outside the conditional
fsBuilder->codeAppendf("highp vec2 uvdX = dFdx(%s);", uv.fsIn());
fsBuilder->codeAppendf("highp vec2 uvdY = dFdy(%s);", uv.fsIn());
fsBuilder->codeAppend("highp vec2 uvIncX = uvdX * 0.45 + uvdY * -0.1;");
fsBuilder->codeAppend("highp vec2 uvIncY = uvdX * 0.1 + uvdY * 0.55;");
fsBuilder->codeAppendf("highp vec2 uv = %s.xy - uvdX * 0.35 - uvdY * 0.25;",
uv.fsIn());
fsBuilder->codeAppendf("highp vec2 firstSample = %s.xy - vec2(0.25);",
fsBuilder->fragmentPosition());
fsBuilder->codeAppendf("highp float d = dot(%s, (firstSample - %s).yx) * 2.0;",
delta.fsIn(), ep1.fsIn());
fsBuilder->codeAppendf("pls.windings[0] += %s(d, uv) ? %s : 0;", inQuadName.c_str(),
windings.fsIn());
fsBuilder->codeAppend("uv += uvIncX;");
fsBuilder->codeAppendf("d += %s.x;", delta.fsIn());
fsBuilder->codeAppendf("pls.windings[1] += %s(d, uv) ? %s : 0;", inQuadName.c_str(),
windings.fsIn());
fsBuilder->codeAppend("uv += uvIncY;");
fsBuilder->codeAppendf("d += %s.y;", delta.fsIn());
fsBuilder->codeAppendf("pls.windings[2] += %s(d, uv) ? %s : 0;", inQuadName.c_str(),
windings.fsIn());
fsBuilder->codeAppend("uv -= uvIncX;");
fsBuilder->codeAppendf("d -= %s.x;", delta.fsIn());
fsBuilder->codeAppendf("pls.windings[3] += %s(d, uv) ? %s : 0;", inQuadName.c_str(),
windings.fsIn());
}
static inline void GenKey(const GrGeometryProcessor& gp,
const GrGLSLCaps&,
GrProcessorKeyBuilder* b) {
const PLSQuadEdgeEffect& qee = gp.cast<PLSQuadEdgeEffect>();
uint32_t key = 0;
key |= qee.usesLocalCoords() && qee.localMatrix().hasPerspective() ? 0x1 : 0x0;
b->add32(key);
}
void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& gp,
FPCoordTransformIter&& transformIter) override {
this->setTransformDataHelper(gp.cast<PLSQuadEdgeEffect>().fLocalMatrix, pdman,
&transformIter);
}
private:
typedef GrGLSLGeometryProcessor INHERITED;
};
virtual void getGLSLProcessorKey(const GrGLSLCaps& caps,
GrProcessorKeyBuilder* b) const override {
GLSLProcessor::GenKey(*this, caps, b);
}
virtual GrGLSLPrimitiveProcessor* createGLSLInstance(const GrGLSLCaps&) const override {
return new GLSLProcessor(*this);
}
private:
PLSQuadEdgeEffect(const SkMatrix& localMatrix, bool usesLocalCoords)
: fLocalMatrix(localMatrix)
, fUsesLocalCoords(usesLocalCoords) {
this->initClassID<PLSQuadEdgeEffect>();
fInPosition = &this->addVertexAttrib("inPosition", kVec2f_GrVertexAttribType,
kHigh_GrSLPrecision);
fInUV = &this->addVertexAttrib("inUV", kVec2f_GrVertexAttribType, kHigh_GrSLPrecision);
fInEndpoint1 = &this->addVertexAttrib("inEndpoint1", kVec2f_GrVertexAttribType,
kHigh_GrSLPrecision);
fInEndpoint2 = &this->addVertexAttrib("inEndpoint2", kVec2f_GrVertexAttribType,
kHigh_GrSLPrecision);
fInWindings = &this->addVertexAttrib("inWindings", kInt_GrVertexAttribType,
kLow_GrSLPrecision);
this->setWillReadFragmentPosition();
}
const Attribute* fInPosition;
const Attribute* fInUV;
const Attribute* fInEndpoint1;
const Attribute* fInEndpoint2;
const Attribute* fInWindings;
SkMatrix fLocalMatrix;
bool fUsesLocalCoords;
GR_DECLARE_GEOMETRY_PROCESSOR_TEST;
typedef GrGeometryProcessor INHERITED;
};
class PLSFinishEffect : public GrGeometryProcessor {
public:
static GrGeometryProcessor* Create(GrColor color, bool useEvenOdd, const SkMatrix& localMatrix,
bool usesLocalCoords) {
return new PLSFinishEffect(color, useEvenOdd, localMatrix, usesLocalCoords);
}
virtual ~PLSFinishEffect() {}
const char* name() const override { return "PLSFinish"; }
const Attribute* inPosition() const { return fInPosition; }
GrColor color() const { return fColor; }
bool colorIgnored() const { return GrColor_ILLEGAL == fColor; }
const SkMatrix& localMatrix() const { return fLocalMatrix; }
bool usesLocalCoords() const { return fUsesLocalCoords; }
GrPixelLocalStorageState getPixelLocalStorageState() const override {
return GrPixelLocalStorageState::kFinish_GrPixelLocalStorageState;
}
const char* getDestColorOverride() const override {
return GR_GL_PLS_DSTCOLOR_NAME;
}
class GLSLProcessor : public GrGLSLGeometryProcessor {
public:
GLSLProcessor(const GrGeometryProcessor&) {}
void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
const PLSFinishEffect& fe = args.fGP.cast<PLSFinishEffect>();
GrGLSLVertexBuilder* vsBuilder = args.fVertBuilder;
GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
fUseEvenOdd = uniformHandler->addUniform(kFragment_GrShaderFlag,
kFloat_GrSLType, kLow_GrSLPrecision,
"useEvenOdd");
const char* useEvenOdd = uniformHandler->getUniformCStr(fUseEvenOdd);
varyingHandler->emitAttributes(fe);
this->setupPosition(vsBuilder, gpArgs, fe.inPosition()->fName);
this->emitTransforms(vsBuilder, varyingHandler, uniformHandler, gpArgs->fPositionVar,
fe.inPosition()->fName, fe.localMatrix(),
args.fFPCoordTransformHandler);
GrGLSLPPFragmentBuilder* fsBuilder = args.fFragBuilder;
SkAssertResult(fsBuilder->enableFeature(
GrGLSLFragmentShaderBuilder::kPixelLocalStorage_GLSLFeature));
fsBuilder->declAppendf(GR_GL_PLS_PATH_DATA_DECL);
fsBuilder->codeAppend("float coverage;");
fsBuilder->codeAppendf("if (%s != 0.0) {", useEvenOdd);
fsBuilder->codeAppend("coverage = float(abs(pls.windings[0]) % 2) * 0.25;");
fsBuilder->codeAppend("coverage += float(abs(pls.windings[1]) % 2) * 0.25;");
fsBuilder->codeAppend("coverage += float(abs(pls.windings[2]) % 2) * 0.25;");
fsBuilder->codeAppend("coverage += float(abs(pls.windings[3]) % 2) * 0.25;");
fsBuilder->codeAppend("} else {");
fsBuilder->codeAppend("coverage = pls.windings[0] != 0 ? 0.25 : 0.0;");
fsBuilder->codeAppend("coverage += pls.windings[1] != 0 ? 0.25 : 0.0;");
fsBuilder->codeAppend("coverage += pls.windings[2] != 0 ? 0.25 : 0.0;");
fsBuilder->codeAppend("coverage += pls.windings[3] != 0 ? 0.25 : 0.0;");
fsBuilder->codeAppend("}");
if (!fe.colorIgnored()) {
this->setupUniformColor(fsBuilder, uniformHandler, args.fOutputColor,
&fColorUniform);
}
fsBuilder->codeAppendf("%s = vec4(coverage);", args.fOutputCoverage);
fsBuilder->codeAppendf("%s = vec4(1.0, 0.0, 1.0, 1.0);", args.fOutputColor);
}
static inline void GenKey(const GrGeometryProcessor& gp,
const GrGLSLCaps&,
GrProcessorKeyBuilder* b) {
const PLSFinishEffect& fe = gp.cast<PLSFinishEffect>();
uint32_t key = 0;
key |= fe.usesLocalCoords() && fe.localMatrix().hasPerspective() ? 0x1 : 0x0;
b->add32(key);
}
void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& gp,
FPCoordTransformIter&& transformIter) override {
const PLSFinishEffect& fe = gp.cast<PLSFinishEffect>();
pdman.set1f(fUseEvenOdd, fe.fUseEvenOdd);
if (fe.color() != fColor && !fe.colorIgnored()) {
GrGLfloat c[4];
GrColorToRGBAFloat(fe.color(), c);
pdman.set4fv(fColorUniform, 1, c);
fColor = fe.color();
}
this->setTransformDataHelper(fe.fLocalMatrix, pdman, &transformIter);
}
private:
GrColor fColor;
UniformHandle fColorUniform;
UniformHandle fUseEvenOdd;
typedef GrGLSLGeometryProcessor INHERITED;
};
virtual void getGLSLProcessorKey(const GrGLSLCaps& caps,
GrProcessorKeyBuilder* b) const override {
GLSLProcessor::GenKey(*this, caps, b);
}
virtual GrGLSLPrimitiveProcessor* createGLSLInstance(const GrGLSLCaps&) const override {
return new GLSLProcessor(*this);
}
private:
PLSFinishEffect(GrColor color, bool useEvenOdd, const SkMatrix& localMatrix,
bool usesLocalCoords)
: fColor(color)
, fUseEvenOdd(useEvenOdd)
, fLocalMatrix(localMatrix)
, fUsesLocalCoords(usesLocalCoords) {
this->initClassID<PLSFinishEffect>();
fInPosition = &this->addVertexAttrib("inPosition", kVec2f_GrVertexAttribType,
kHigh_GrSLPrecision);
}
const Attribute* fInPosition;
GrColor fColor;
bool fUseEvenOdd;
SkMatrix fLocalMatrix;
bool fUsesLocalCoords;
typedef GrGeometryProcessor INHERITED;
};
///////////////////////////////////////////////////////////////////////////////
bool GrPLSPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
// We have support for even-odd rendering, but are having some troublesome
// seams. Disable in the presence of even-odd for now.
SkPath path;
args.fShape->asPath(&path);
return args.fShaderCaps->shaderDerivativeSupport() && args.fAntiAlias &&
args.fShape->style().isSimpleFill() && !path.isInverseFillType() &&
path.getFillType() == SkPath::FillType::kWinding_FillType;
}
class PLSPathBatch : public GrVertexBatch {
public:
DEFINE_BATCH_CLASS_ID
PLSPathBatch(GrColor color, const SkPath& path, const SkMatrix& viewMatrix)
: INHERITED(ClassID())
, fColor(color)
, fPath(path)
, fViewMatrix(viewMatrix) {
// compute bounds
this->setTransformedBounds(path.getBounds(), fViewMatrix, HasAABloat::kYes,
IsZeroArea::kNo);
}
const char* name() const override { return "PLSBatch"; }
void computePipelineOptimizations(GrInitInvariantOutput* color,
GrInitInvariantOutput* coverage,
GrBatchToXPOverrides* overrides) const override {
// When this is called on a batch, there is only one geometry bundle
color->setKnownFourComponents(fColor);
coverage->setUnknownSingleComponent();
overrides->fUsePLSDstRead = true;
}
void initBatchTracker(const GrXPOverridesForBatch& overrides) override {
// Handle any color overrides
if (!overrides.readsColor()) {
fColor = GrColor_ILLEGAL;
}
overrides.getOverrideColorIfSet(&fColor);
// setup batch properties
fUsesLocalCoords = overrides.readsLocalCoords();
}
void onPrepareDraws(Target* target) const override {
SkMatrix invert;
if (fUsesLocalCoords && !fViewMatrix.invert(&invert)) {
SkDebugf("Could not invert viewmatrix\n");
return;
}
// Setup GrGeometryProcessors
sk_sp<GrPLSGeometryProcessor> triangleProcessor(
PLSAATriangleEffect::Create(invert, fUsesLocalCoords));
sk_sp<GrPLSGeometryProcessor> quadProcessor(
PLSQuadEdgeEffect::Create(invert, fUsesLocalCoords));
GrResourceProvider* rp = target->resourceProvider();
SkRect bounds;
this->bounds().roundOut(&bounds);
triangleProcessor->setBounds(bounds);
quadProcessor->setBounds(bounds);
// We use the fact that SkPath::transform path does subdivision based on
// perspective. Otherwise, we apply the view matrix when copying to the
// segment representation.
const SkMatrix* viewMatrix = &fViewMatrix;
// We avoid initializing the path unless we have to
const SkPath* pathPtr = &fPath;
SkTLazy<SkPath> tmpPath;
if (viewMatrix->hasPerspective()) {
SkPath* tmpPathPtr = tmpPath.init(*pathPtr);
tmpPathPtr->setIsVolatile(true);
tmpPathPtr->transform(*viewMatrix);
viewMatrix = &SkMatrix::I();
pathPtr = tmpPathPtr;
}
GrMesh mesh;
PLSVertices triVertices;
PLSVertices quadVertices;
if (!get_geometry(*pathPtr, *viewMatrix, triVertices, quadVertices, rp, bounds)) {
return;
}
if (triVertices.count()) {
const GrBuffer* triVertexBuffer;
int firstTriVertex;
size_t triStride = triangleProcessor->getVertexStride();
PLSVertex* triVerts = reinterpret_cast<PLSVertex*>(target->makeVertexSpace(
triStride, triVertices.count(), &triVertexBuffer, &firstTriVertex));
if (!triVerts) {
SkDebugf("Could not allocate vertices\n");
return;
}
for (int i = 0; i < triVertices.count(); ++i) {
triVerts[i] = triVertices[i];
}
mesh.init(kTriangles_GrPrimitiveType, triVertexBuffer, firstTriVertex,
triVertices.count());
target->draw(triangleProcessor.get(), mesh);
}
if (quadVertices.count()) {
const GrBuffer* quadVertexBuffer;
int firstQuadVertex;
size_t quadStride = quadProcessor->getVertexStride();
PLSVertex* quadVerts = reinterpret_cast<PLSVertex*>(target->makeVertexSpace(
quadStride, quadVertices.count(), &quadVertexBuffer, &firstQuadVertex));
if (!quadVerts) {
SkDebugf("Could not allocate vertices\n");
return;
}
for (int i = 0; i < quadVertices.count(); ++i) {
quadVerts[i] = quadVertices[i];
}
mesh.init(kTriangles_GrPrimitiveType, quadVertexBuffer, firstQuadVertex,
quadVertices.count());
target->draw(quadProcessor.get(), mesh);
}
sk_sp<GrGeometryProcessor> finishProcessor(
PLSFinishEffect::Create(fColor,
pathPtr->getFillType() ==
SkPath::FillType::kEvenOdd_FillType,
invert,
fUsesLocalCoords));
const GrBuffer* rectVertexBuffer;
size_t finishStride = finishProcessor->getVertexStride();
int firstRectVertex;
static const int kRectVertexCount = 6;
SkPoint* rectVerts = reinterpret_cast<SkPoint*>(target->makeVertexSpace(
finishStride, kRectVertexCount, &rectVertexBuffer, &firstRectVertex));
if (!rectVerts) {
SkDebugf("Could not allocate vertices\n");
return;
}
rectVerts[0] = { bounds.fLeft, bounds.fTop };
rectVerts[1] = { bounds.fLeft, bounds.fBottom };
rectVerts[2] = { bounds.fRight, bounds.fBottom };
rectVerts[3] = { bounds.fLeft, bounds.fTop };
rectVerts[4] = { bounds.fRight, bounds.fTop };
rectVerts[5] = { bounds.fRight, bounds.fBottom };
mesh.init(kTriangles_GrPrimitiveType, rectVertexBuffer, firstRectVertex,
kRectVertexCount);
target->draw(finishProcessor.get(), mesh);
}
private:
bool onCombineIfPossible(GrBatch* t, const GrCaps& caps) override {
return false;
}
bool fUsesLocalCoords;
GrColor fColor;
SkPath fPath;
SkMatrix fViewMatrix;
typedef GrVertexBatch INHERITED;
};
SkDEBUGCODE(bool inPLSDraw = false;)
bool GrPLSPathRenderer::onDrawPath(const DrawPathArgs& args) {
SkASSERT(!args.fShape->isEmpty());
SkASSERT(!inPLSDraw);
SkDEBUGCODE(inPLSDraw = true;)
SkPath path;
args.fShape->asPath(&path);
sk_sp<GrDrawBatch> batch(new PLSPathBatch(args.fPaint->getColor(),
path, *args.fViewMatrix));
GrPipelineBuilder pipelineBuilder(*args.fPaint,
args.fRenderTargetContext->mustUseHWAA(*args.fPaint));
pipelineBuilder.setUserStencil(args.fUserStencilSettings);
args.fRenderTargetContext->drawBatch(pipelineBuilder, *args.fClip, batch.get());
SkDEBUGCODE(inPLSDraw = false;)
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef GR_TEST_UTILS
DRAW_BATCH_TEST_DEFINE(PLSPathBatch) {
GrColor color = GrRandomColor(random);
SkMatrix vm = GrTest::TestMatrixInvertible(random);
SkPath path = GrTest::TestPathConvex(random);
return new PLSPathBatch(color, path, vm);
}
#endif