| /* |
| * 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.fTransformsIn, |
| args.fTransformsOut); |
| |
| GrGLSLPPFragmentBuilder* fsBuilder = args.fFragBuilder; |
| SkAssertResult(fsBuilder->enableFeature( |
| GrGLSLFragmentShaderBuilder::kPixelLocalStorage_GLSLFeature)); |
| SkAssertResult(fsBuilder->enableFeature( |
| GrGLSLFragmentShaderBuilder::kStandardDerivatives_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); |
| } |
| |
| virtual void setData(const GrGLSLProgramDataManager& pdman, |
| const GrPrimitiveProcessor& gp) override { |
| } |
| |
| void setTransformData(const GrPrimitiveProcessor& primProc, |
| const GrGLSLProgramDataManager& pdman, |
| int index, |
| const SkTArray<const GrCoordTransform*, true>& transforms) override { |
| this->setTransformDataHelper(primProc.cast<PLSAATriangleEffect>().fLocalMatrix, pdman, |
| index, transforms); |
| } |
| |
| 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(Attribute("inPosition", kVec2f_GrVertexAttribType, |
| kHigh_GrSLPrecision)); |
| fInVertex1 = &this->addVertexAttrib(Attribute("inVertex1", kVec2f_GrVertexAttribType, |
| kHigh_GrSLPrecision)); |
| fInVertex2 = &this->addVertexAttrib(Attribute("inVertex2", kVec2f_GrVertexAttribType, |
| kHigh_GrSLPrecision)); |
| fInVertex3 = &this->addVertexAttrib(Attribute("inVertex3", kVec2f_GrVertexAttribType, |
| kHigh_GrSLPrecision)); |
| fInWindings = &this->addVertexAttrib(Attribute("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.fTransformsIn, |
| args.fTransformsOut); |
| |
| GrGLSLPPFragmentBuilder* fsBuilder = args.fFragBuilder; |
| SkAssertResult(fsBuilder->enableFeature( |
| GrGLSLFragmentShaderBuilder::kPixelLocalStorage_GLSLFeature)); |
| SkAssertResult(fsBuilder->enableFeature( |
| GrGLSLFragmentShaderBuilder::kStandardDerivatives_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); |
| } |
| |
| virtual void setData(const GrGLSLProgramDataManager& pdman, |
| const GrPrimitiveProcessor& gp) override { |
| } |
| |
| void setTransformData(const GrPrimitiveProcessor& primProc, |
| const GrGLSLProgramDataManager& pdman, |
| int index, |
| const SkTArray<const GrCoordTransform*, true>& transforms) override { |
| this->setTransformDataHelper(primProc.cast<PLSQuadEdgeEffect>().fLocalMatrix, pdman, |
| index, transforms); |
| } |
| |
| 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(Attribute("inPosition", kVec2f_GrVertexAttribType, |
| kHigh_GrSLPrecision)); |
| fInUV = &this->addVertexAttrib(Attribute("inUV", kVec2f_GrVertexAttribType, |
| kHigh_GrSLPrecision)); |
| fInEndpoint1 = &this->addVertexAttrib(Attribute("inEndpoint1", kVec2f_GrVertexAttribType, |
| kHigh_GrSLPrecision)); |
| fInEndpoint2 = &this->addVertexAttrib(Attribute("inEndpoint2", kVec2f_GrVertexAttribType, |
| kHigh_GrSLPrecision)); |
| fInWindings = &this->addVertexAttrib(Attribute("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.fTransformsIn, |
| args.fTransformsOut); |
| |
| 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); |
| } |
| |
| virtual void setData(const GrGLSLProgramDataManager& pdman, |
| const GrPrimitiveProcessor& gp) 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(); |
| } |
| } |
| |
| void setTransformData(const GrPrimitiveProcessor& primProc, |
| const GrGLSLProgramDataManager& pdman, |
| int index, |
| const SkTArray<const GrCoordTransform*, true>& transforms) override { |
| this->setTransformDataHelper(primProc.cast<PLSFinishEffect>().fLocalMatrix, pdman, |
| index, transforms); |
| } |
| |
| 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(Attribute("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 |
| SkAutoTUnref<GrPLSGeometryProcessor> triangleProcessor( |
| PLSAATriangleEffect::Create(invert, fUsesLocalCoords)); |
| SkAutoTUnref<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, 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, mesh); |
| } |
| |
| SkAutoTUnref<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, 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); |
| |
| SkAutoTUnref<GrDrawBatch> batch(new PLSPathBatch(args.fPaint->getColor(), |
| path, *args.fViewMatrix)); |
| |
| GrPipelineBuilder pipelineBuilder(*args.fPaint, args.fDrawContext->mustUseHWAA(*args.fPaint)); |
| pipelineBuilder.setUserStencil(args.fUserStencilSettings); |
| |
| args.fDrawContext->drawBatch(pipelineBuilder, *args.fClip, batch); |
| |
| 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 |