/* * Copyright 2016 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "GrMSAAPathRenderer.h" #include "GrBatchFlushState.h" #include "GrDefaultGeoProcFactory.h" #include "GrPathStencilSettings.h" #include "GrPathUtils.h" #include "GrPipelineBuilder.h" #include "GrMesh.h" #include "SkGeometry.h" #include "SkTraceEvent.h" #include "glsl/GrGLSLGeometryProcessor.h" #include "glsl/GrGLSLFragmentShaderBuilder.h" #include "glsl/GrGLSLVertexShaderBuilder.h" #include "glsl/GrGLSLProgramDataManager.h" #include "glsl/GrGLSLUtil.h" #include "gl/GrGLVaryingHandler.h" #include "batches/GrRectBatchFactory.h" #include "batches/GrVertexBatch.h" static const float kTolerance = 0.5f; //////////////////////////////////////////////////////////////////////////////// // Helpers for drawPath static inline bool single_pass_path(const SkPath& path) { if (!path.isInverseFillType()) { return path.isConvex(); } return false; } GrPathRenderer::StencilSupport GrMSAAPathRenderer::onGetStencilSupport(const SkPath& path) const { if (single_pass_path(path)) { return GrPathRenderer::kNoRestriction_StencilSupport; } else { return GrPathRenderer::kStencilOnly_StencilSupport; } } struct MSAALineVertices { struct Vertex { SkPoint fPosition; SkColor fColor; }; Vertex* vertices; Vertex* nextVertex; #ifdef SK_DEBUG Vertex* verticesEnd; #endif uint16_t* indices; uint16_t* nextIndex; }; struct MSAAQuadVertices { struct Vertex { SkPoint fPosition; SkPoint fUV; SkColor fColor; }; Vertex* vertices; Vertex* nextVertex; #ifdef SK_DEBUG Vertex* verticesEnd; #endif uint16_t* indices; uint16_t* nextIndex; }; static inline void append_contour_edge_indices(uint16_t fanCenterIdx, uint16_t edgeV0Idx, MSAALineVertices& lines) { *(lines.nextIndex++) = fanCenterIdx; *(lines.nextIndex++) = edgeV0Idx; *(lines.nextIndex++) = edgeV0Idx + 1; } static inline void add_quad(MSAALineVertices& lines, MSAAQuadVertices& quads, const SkPoint pts[], SkColor color, bool indexed, uint16_t subpathLineIdxStart) { SkASSERT(lines.nextVertex < lines.verticesEnd); *lines.nextVertex = { pts[2], color }; if (indexed) { int prevIdx = (uint16_t) (lines.nextVertex - lines.vertices - 1); if (prevIdx > subpathLineIdxStart) { append_contour_edge_indices(subpathLineIdxStart, prevIdx, lines); } } lines.nextVertex++; SkASSERT(quads.nextVertex + 2 < quads.verticesEnd); // the texture coordinates are drawn from the Loop-Blinn rendering algorithm *(quads.nextVertex++) = { pts[0], SkPoint::Make(0.0, 0.0), color }; *(quads.nextVertex++) = { pts[1], SkPoint::Make(0.5, 0.0), color }; *(quads.nextVertex++) = { pts[2], SkPoint::Make(1.0, 1.0), color }; if (indexed) { uint16_t offset = (uint16_t) (quads.nextVertex - quads.vertices) - 3; *(quads.nextIndex++) = offset++; *(quads.nextIndex++) = offset++; *(quads.nextIndex++) = offset++; } } class MSAAQuadProcessor : public GrGeometryProcessor { public: static GrGeometryProcessor* Create(const SkMatrix& viewMatrix) { return new MSAAQuadProcessor(viewMatrix); } virtual ~MSAAQuadProcessor() {} const char* name() const override { return "MSAAQuadProcessor"; } const Attribute* inPosition() const { return fInPosition; } const Attribute* inUV() const { return fInUV; } const Attribute* inColor() const { return fInColor; } const SkMatrix& viewMatrix() const { return fViewMatrix; } const SkMatrix& localMatrix() const { return SkMatrix::I(); } class GLSLProcessor : public GrGLSLGeometryProcessor { public: GLSLProcessor(const GrGeometryProcessor& qpr) {} void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { const MSAAQuadProcessor& qp = args.fGP.cast(); GrGLSLVertexBuilder* vsBuilder = args.fVertBuilder; GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; // emit attributes varyingHandler->emitAttributes(qp); varyingHandler->addPassThroughAttribute(qp.inColor(), args.fOutputColor); GrGLSLVertToFrag uv(kVec2f_GrSLType); varyingHandler->addVarying("uv", &uv, kHigh_GrSLPrecision); vsBuilder->codeAppendf("%s = %s;", uv.vsOut(), qp.inUV()->fName); // Setup position this->setupPosition(vsBuilder, uniformHandler, gpArgs, qp.inPosition()->fName, qp.viewMatrix(), &fViewMatrixUniform); // emit transforms this->emitTransforms(vsBuilder, varyingHandler, uniformHandler, gpArgs->fPositionVar, qp.inPosition()->fName, SkMatrix::I(), args.fTransformsIn, args.fTransformsOut); GrGLSLPPFragmentBuilder* fsBuilder = args.fFragBuilder; fsBuilder->codeAppendf("if (%s.x * %s.x >= %s.y) discard;", uv.fsIn(), uv.fsIn(), uv.fsIn()); fsBuilder->codeAppendf("%s = vec4(1.0);", args.fOutputCoverage); } static inline void GenKey(const GrGeometryProcessor& gp, const GrGLSLCaps&, GrProcessorKeyBuilder* b) { const MSAAQuadProcessor& qp = gp.cast(); uint32_t key = 0; key |= qp.viewMatrix().hasPerspective() ? 0x1 : 0x0; key |= qp.viewMatrix().isIdentity() ? 0x2: 0x0; b->add32(key); } virtual void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& gp) override { const MSAAQuadProcessor& qp = gp.cast(); if (!qp.viewMatrix().isIdentity()) { float viewMatrix[3 * 3]; GrGLSLGetMatrix<3>(viewMatrix, qp.viewMatrix()); pdman.setMatrix3f(fViewMatrixUniform, viewMatrix); } } void setTransformData(const GrPrimitiveProcessor& primProc, const GrGLSLProgramDataManager& pdman, int index, const SkTArray& transforms) override { this->setTransformDataHelper(primProc, pdman, index, transforms); } private: typedef GrGLSLGeometryProcessor INHERITED; UniformHandle fViewMatrixUniform; }; 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: MSAAQuadProcessor(const SkMatrix& viewMatrix) : fViewMatrix(viewMatrix) { this->initClassID(); fInPosition = &this->addVertexAttrib(Attribute("inPosition", kVec2f_GrVertexAttribType, kHigh_GrSLPrecision)); fInUV = &this->addVertexAttrib(Attribute("inUV", kVec2f_GrVertexAttribType, kHigh_GrSLPrecision)); fInColor = &this->addVertexAttrib(Attribute("inColor", kVec4ub_GrVertexAttribType)); this->setSampleShading(1.0f); } const Attribute* fInPosition; const Attribute* fInUV; const Attribute* fInColor; SkMatrix fViewMatrix; GR_DECLARE_GEOMETRY_PROCESSOR_TEST; typedef GrGeometryProcessor INHERITED; }; class MSAAPathBatch : public GrVertexBatch { public: DEFINE_BATCH_CLASS_ID struct Geometry { GrColor fColor; SkPath fPath; SkScalar fTolerance; }; static MSAAPathBatch* Create(const Geometry& geometry, const SkMatrix& viewMatrix, const SkRect& devBounds) { return new MSAAPathBatch(geometry, viewMatrix, devBounds); } const char* name() const override { return "MSAAPathBatch"; } 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(fGeoData[0].fColor); coverage->setKnownSingleComponent(0xff); } bool isValid() const { return !fIsIndexed || fMaxLineIndices <= SK_MaxU16; } private: void initBatchTracker(const GrXPOverridesForBatch& overrides) override { // Handle any color overrides if (!overrides.readsColor()) { fGeoData[0].fColor = GrColor_ILLEGAL; } overrides.getOverrideColorIfSet(&fGeoData[0].fColor); } void computeWorstCasePointCount(const SkPath& path, int* subpaths, SkScalar tol, int* outLinePointCount, int* outQuadPointCount) const { int linePointCount = 0; int quadPointCount = 0; *subpaths = 1; bool first = true; SkPath::Iter iter(path, false); SkPath::Verb verb; SkPoint pts[4]; while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { switch (verb) { case SkPath::kLine_Verb: linePointCount += 1; break; case SkPath::kConic_Verb: { SkScalar weight = iter.conicWeight(); SkAutoConicToQuads converter; converter.computeQuads(pts, weight, kTolerance); int quadPts = converter.countQuads(); linePointCount += quadPts; quadPointCount += 3 * quadPts; } case SkPath::kQuad_Verb: linePointCount += 1; quadPointCount += 3; break; case SkPath::kCubic_Verb: { SkSTArray<15, SkPoint, true> quadPts; GrPathUtils::convertCubicToQuads(pts, kTolerance, &quadPts); int count = quadPts.count(); linePointCount += count / 3; quadPointCount += count; break; } case SkPath::kMove_Verb: linePointCount += 1; if (!first) { ++(*subpaths); } break; default: break; } first = false; } *outLinePointCount = linePointCount; *outQuadPointCount = quadPointCount; } void onPrepareDraws(Target* target) const override { SkASSERT(this->isValid()); if (fMaxLineVertices == 0) { SkASSERT(fMaxQuadVertices == 0); return; } GrPrimitiveType primitiveType = fIsIndexed ? kTriangles_GrPrimitiveType : kTriangleFan_GrPrimitiveType; // allocate vertex / index buffers const GrBuffer* lineVertexBuffer; int firstLineVertex; MSAALineVertices lines; size_t lineVertexStride = sizeof(MSAALineVertices::Vertex); lines.vertices = (MSAALineVertices::Vertex*) target->makeVertexSpace(lineVertexStride, fMaxLineVertices, &lineVertexBuffer, &firstLineVertex); if (!lines.vertices) { SkDebugf("Could not allocate vertices\n"); return; } lines.nextVertex = lines.vertices; SkDEBUGCODE(lines.verticesEnd = lines.vertices + fMaxLineVertices;) MSAAQuadVertices quads; size_t quadVertexStride = sizeof(MSAAQuadVertices::Vertex); SkAutoFree quadVertexPtr(sk_malloc_throw(fMaxQuadVertices * quadVertexStride)); quads.vertices = (MSAAQuadVertices::Vertex*) quadVertexPtr.get(); quads.nextVertex = quads.vertices; SkDEBUGCODE(quads.verticesEnd = quads.vertices + fMaxQuadVertices;) const GrBuffer* lineIndexBuffer = nullptr; int firstLineIndex; if (fIsIndexed) { lines.indices = target->makeIndexSpace(fMaxLineIndices, &lineIndexBuffer, &firstLineIndex); if (!lines.indices) { SkDebugf("Could not allocate indices\n"); return; } lines.nextIndex = lines.indices; } else { lines.indices = nullptr; lines.nextIndex = nullptr; } SkAutoFree quadIndexPtr; if (fIsIndexed) { quads.indices = (uint16_t*) sk_malloc_throw(fMaxQuadIndices * sizeof(uint16_t)); quadIndexPtr.set(quads.indices); quads.nextIndex = quads.indices; } else { quads.indices = nullptr; quads.nextIndex = nullptr; } // fill buffers for (int i = 0; i < fGeoData.count(); i++) { const Geometry& args = fGeoData[i]; if (!this->createGeom(lines, quads, args.fPath, args.fTolerance, fViewMatrix, args.fColor, fIsIndexed)) { return; } } int lineVertexOffset = (int) (lines.nextVertex - lines.vertices); int lineIndexOffset = (int) (lines.nextIndex - lines.indices); SkASSERT(lineVertexOffset <= fMaxLineVertices && lineIndexOffset <= fMaxLineIndices); int quadVertexOffset = (int) (quads.nextVertex - quads.vertices); int quadIndexOffset = (int) (quads.nextIndex - quads.indices); SkASSERT(quadVertexOffset <= fMaxQuadVertices && quadIndexOffset <= fMaxQuadIndices); if (lineVertexOffset) { SkAutoTUnref lineGP; { using namespace GrDefaultGeoProcFactory; lineGP.reset(GrDefaultGeoProcFactory::Create(Color(Color::kAttribute_Type), Coverage(255), LocalCoords(LocalCoords::kUnused_Type), fViewMatrix)); } SkASSERT(lineVertexStride == lineGP->getVertexStride()); GrMesh lineMeshes; if (fIsIndexed) { lineMeshes.initIndexed(primitiveType, lineVertexBuffer, lineIndexBuffer, firstLineVertex, firstLineIndex, lineVertexOffset, lineIndexOffset); } else { lineMeshes.init(primitiveType, lineVertexBuffer, firstLineVertex, lineVertexOffset); } target->draw(lineGP, lineMeshes); } if (quadVertexOffset) { SkAutoTUnref quadGP(MSAAQuadProcessor::Create(fViewMatrix)); SkASSERT(quadVertexStride == quadGP->getVertexStride()); const GrBuffer* quadVertexBuffer; int firstQuadVertex; MSAAQuadVertices::Vertex* quadVertices = (MSAAQuadVertices::Vertex*) target->makeVertexSpace(quadVertexStride, quadVertexOffset, &quadVertexBuffer, &firstQuadVertex); memcpy(quadVertices, quads.vertices, quadVertexStride * quadVertexOffset); GrMesh quadMeshes; if (fIsIndexed) { const GrBuffer* quadIndexBuffer; int firstQuadIndex; uint16_t* quadIndices = (uint16_t*) target->makeIndexSpace(quadIndexOffset, &quadIndexBuffer, &firstQuadIndex); memcpy(quadIndices, quads.indices, sizeof(uint16_t) * quadIndexOffset); quadMeshes.initIndexed(kTriangles_GrPrimitiveType, quadVertexBuffer, quadIndexBuffer, firstQuadVertex, firstQuadIndex, quadVertexOffset, quadIndexOffset); } else { quadMeshes.init(kTriangles_GrPrimitiveType, quadVertexBuffer, firstQuadVertex, quadVertexOffset); } target->draw(quadGP, quadMeshes); } } SkSTArray<1, Geometry, true>* geoData() { return &fGeoData; } MSAAPathBatch(const Geometry& geometry, const SkMatrix& viewMatrix, const SkRect& devBounds) : INHERITED(ClassID()) , fViewMatrix(viewMatrix) { fGeoData.push_back(geometry); this->setBounds(devBounds); int contourCount; this->computeWorstCasePointCount(geometry.fPath, &contourCount, kTolerance, &fMaxLineVertices, &fMaxQuadVertices); fMaxLineIndices = fMaxLineVertices * 3; fMaxQuadIndices = fMaxQuadVertices * 3; fIsIndexed = contourCount > 1; } bool onCombineIfPossible(GrBatch* t, const GrCaps& caps) override { MSAAPathBatch* that = t->cast(); if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pipeline(), that->bounds(), caps)) { return false; } if (!fViewMatrix.cheapEqualTo(that->fViewMatrix)) { return false; } if ((fMaxLineIndices + that->fMaxLineIndices > SK_MaxU16) || (fMaxQuadIndices + that->fMaxQuadIndices > SK_MaxU16)) { return false; } fGeoData.push_back_n(that->geoData()->count(), that->geoData()->begin()); this->joinBounds(that->bounds()); fIsIndexed = true; fMaxLineVertices += that->fMaxLineVertices; fMaxQuadVertices += that->fMaxQuadVertices; fMaxLineIndices += that->fMaxLineIndices; fMaxQuadIndices += that->fMaxQuadIndices; return true; } bool createGeom(MSAALineVertices& lines, MSAAQuadVertices& quads, const SkPath& path, SkScalar srcSpaceTol, const SkMatrix& m, SkColor color, bool isIndexed) const { { uint16_t subpathIdxStart = (uint16_t) (lines.nextVertex - lines.vertices); SkPoint pts[4]; bool first = true; SkPath::Iter iter(path, false); bool done = false; while (!done) { SkPath::Verb verb = iter.next(pts); switch (verb) { case SkPath::kMove_Verb: if (!first) { uint16_t currIdx = (uint16_t) (lines.nextVertex - lines.vertices); subpathIdxStart = currIdx; } SkASSERT(lines.nextVertex < lines.verticesEnd); *(lines.nextVertex++) = { pts[0], color }; break; case SkPath::kLine_Verb: if (isIndexed) { uint16_t prevIdx = (uint16_t) (lines.nextVertex - lines.vertices - 1); if (prevIdx > subpathIdxStart) { append_contour_edge_indices(subpathIdxStart, prevIdx, lines); } } SkASSERT(lines.nextVertex < lines.verticesEnd); *(lines.nextVertex++) = { pts[1], color }; break; case SkPath::kConic_Verb: { SkScalar weight = iter.conicWeight(); SkAutoConicToQuads converter; const SkPoint* quadPts = converter.computeQuads(pts, weight, kTolerance); for (int i = 0; i < converter.countQuads(); ++i) { add_quad(lines, quads, quadPts + i * 2, color, isIndexed, subpathIdxStart); } break; } case SkPath::kQuad_Verb: { add_quad(lines, quads, pts, color, isIndexed, subpathIdxStart); break; } case SkPath::kCubic_Verb: { SkSTArray<15, SkPoint, true> quadPts; GrPathUtils::convertCubicToQuads(pts, kTolerance, &quadPts); int count = quadPts.count(); for (int i = 0; i < count; i += 3) { add_quad(lines, quads, &quadPts[i], color, isIndexed, subpathIdxStart); } break; } case SkPath::kClose_Verb: break; case SkPath::kDone_Verb: done = true; } first = false; } } return true; } SkSTArray<1, Geometry, true> fGeoData; SkMatrix fViewMatrix; int fMaxLineVertices; int fMaxQuadVertices; int fMaxLineIndices; int fMaxQuadIndices; bool fIsIndexed; typedef GrVertexBatch INHERITED; }; bool GrMSAAPathRenderer::internalDrawPath(GrDrawTarget* target, GrPipelineBuilder* pipelineBuilder, GrColor color, const SkMatrix& viewMatrix, const SkPath& path, bool stencilOnly) { const GrXPFactory* xpFactory = pipelineBuilder->getXPFactory(); SkAutoTUnref backupXPFactory(SkSafeRef(xpFactory)); // face culling doesn't make sense here SkASSERT(GrPipelineBuilder::kBoth_DrawFace == pipelineBuilder->getDrawFace()); int passCount = 0; const GrStencilSettings* passes[3]; GrPipelineBuilder::DrawFace drawFace[3]; bool reverse = false; bool lastPassIsBounds; if (single_pass_path(path)) { passCount = 1; if (stencilOnly) { passes[0] = &gDirectToStencil; } else { passes[0] = nullptr; } drawFace[0] = GrPipelineBuilder::kBoth_DrawFace; lastPassIsBounds = false; } else { switch (path.getFillType()) { case SkPath::kInverseEvenOdd_FillType: reverse = true; // fallthrough case SkPath::kEvenOdd_FillType: passes[0] = &gEOStencilPass; if (stencilOnly) { passCount = 1; lastPassIsBounds = false; } else { passCount = 2; lastPassIsBounds = true; if (reverse) { passes[1] = &gInvEOColorPass; } else { passes[1] = &gEOColorPass; } } drawFace[0] = drawFace[1] = GrPipelineBuilder::kBoth_DrawFace; break; case SkPath::kInverseWinding_FillType: reverse = true; // fallthrough case SkPath::kWinding_FillType: passes[0] = &gWindStencilSeparateWithWrap; passCount = 2; drawFace[0] = GrPipelineBuilder::kBoth_DrawFace; if (stencilOnly) { lastPassIsBounds = false; --passCount; } else { lastPassIsBounds = true; drawFace[passCount-1] = GrPipelineBuilder::kBoth_DrawFace; if (reverse) { passes[passCount-1] = &gInvWindColorPass; } else { passes[passCount-1] = &gWindColorPass; } } break; default: SkDEBUGFAIL("Unknown path fFill!"); return false; } } SkRect devBounds; GetPathDevBounds(path, pipelineBuilder->getRenderTarget(), viewMatrix, &devBounds); for (int p = 0; p < passCount; ++p) { pipelineBuilder->setDrawFace(drawFace[p]); if (passes[p]) { *pipelineBuilder->stencil() = *passes[p]; } if (lastPassIsBounds && (p == passCount-1)) { // Reset the XP Factory on pipelineBuilder pipelineBuilder->setXPFactory(backupXPFactory); SkRect bounds; SkMatrix localMatrix = SkMatrix::I(); if (reverse) { SkASSERT(pipelineBuilder->getRenderTarget()); // draw over the dev bounds (which will be the whole dst surface for inv fill). bounds = devBounds; SkMatrix vmi; // mapRect through persp matrix may not be correct if (!viewMatrix.hasPerspective() && viewMatrix.invert(&vmi)) { vmi.mapRect(&bounds); } else { if (!viewMatrix.invert(&localMatrix)) { return false; } } } else { bounds = path.getBounds(); } const SkMatrix& viewM = (reverse && viewMatrix.hasPerspective()) ? SkMatrix::I() : viewMatrix; SkAutoTUnref batch( GrRectBatchFactory::CreateNonAAFill(color, viewM, bounds, nullptr, &localMatrix)); target->drawBatch(*pipelineBuilder, batch); } else { if (passCount > 1) { pipelineBuilder->setDisableColorXPFactory(); } MSAAPathBatch::Geometry geometry; geometry.fColor = color; geometry.fPath = path; geometry.fTolerance = kTolerance; SkAutoTUnref batch(MSAAPathBatch::Create(geometry, viewMatrix, devBounds)); if (batch->isValid()) { target->drawBatch(*pipelineBuilder, batch); } else { return false; } } } return true; } bool GrMSAAPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const { // This path renderer does not support hairlines. We defer on anything that could be handled // as a hairline by another path renderer. Also, arbitrary path effects could produce // a hairline result. return !IsStrokeHairlineOrEquivalent(*args.fStyle, *args.fViewMatrix, nullptr) && !args.fStyle->hasNonDashPathEffect() && !args.fAntiAlias; } bool GrMSAAPathRenderer::onDrawPath(const DrawPathArgs& args) { GR_AUDIT_TRAIL_AUTO_FRAME(args.fTarget->getAuditTrail(), "GrMSAAPathRenderer::onDrawPath"); SkPath tmpPath; const SkPath* path; if (args.fStyle->applies()) { SkStrokeRec::InitStyle fill; SkScalar styleScale = GrStyle::MatrixToScaleFactor(*args.fViewMatrix); if (!args.fStyle->applyToPath(&tmpPath, &fill, *args.fPath, styleScale)) { return false; } // We don't accept styles that are hairlines or have path effects that could produce // hairlines. SkASSERT(SkStrokeRec::kFill_InitStyle == fill); path = &tmpPath; } else { path = args.fPath; } return this->internalDrawPath(args.fTarget, args.fPipelineBuilder, args.fColor, *args.fViewMatrix, *path, false); } void GrMSAAPathRenderer::onStencilPath(const StencilPathArgs& args) { GR_AUDIT_TRAIL_AUTO_FRAME(args.fTarget->getAuditTrail(),"GrMSAAPathRenderer::onStencilPath"); SkASSERT(SkPath::kInverseEvenOdd_FillType != args.fPath->getFillType()); SkASSERT(SkPath::kInverseWinding_FillType != args.fPath->getFillType()); this->internalDrawPath(args.fTarget, args.fPipelineBuilder, GrColor_WHITE, *args.fViewMatrix, *args.fPath, true); } ///////////////////////////////////////////////////////////////////////////////////////////////////