| /* | |
| * 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("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.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("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.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("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 |