bool GrAAHairLinePathRenderer::createGeom( const SkPath& path, GrDrawTarget* target, int* lineCnt, int* quadCnt, GrDrawTarget::AutoReleaseGeometry* arg) { const GrDrawState& drawState = target->getDrawState(); int rtHeight = drawState.getRenderTarget()->height(); GrIRect devClipBounds; target->getClip()->getConservativeBounds(drawState.getRenderTarget(), &devClipBounds); GrVertexLayout layout = GrDrawState::kEdge_VertexLayoutBit; SkMatrix viewM = drawState.getViewMatrix(); PREALLOC_PTARRAY(128) lines; PREALLOC_PTARRAY(128) quads; IntArray qSubdivs; *quadCnt = generate_lines_and_quads(path, viewM, devClipBounds, &lines, &quads, &qSubdivs); *lineCnt = lines.count() / 2; int vertCnt = kVertsPerLineSeg * *lineCnt + kVertsPerQuad * *quadCnt; GrAssert(sizeof(Vertex) == GrDrawState::VertexSize(layout)); if (!arg->set(target, layout, vertCnt, 0)) { return false; } Vertex* verts = reinterpret_cast<Vertex*>(arg->vertices()); const SkMatrix* toDevice = NULL; const SkMatrix* toSrc = NULL; SkMatrix ivm; if (viewM.hasPerspective()) { if (viewM.invert(&ivm)) { toDevice = &viewM; toSrc = &ivm; } } for (int i = 0; i < *lineCnt; ++i) { add_line(&lines[2*i], rtHeight, toSrc, &verts); } int unsubdivQuadCnt = quads.count() / 3; for (int i = 0; i < unsubdivQuadCnt; ++i) { GrAssert(qSubdivs[i] >= 0); add_quads(&quads[3*i], qSubdivs[i], toDevice, toSrc, &verts); } return true; }
void AAHairlineBatch::generateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) { // Setup the viewmatrix and localmatrix for the GrGeometryProcessor. SkMatrix invert; if (!this->viewMatrix().invert(&invert)) { return; } // we will transform to identity space if the viewmatrix does not have perspective bool hasPerspective = this->viewMatrix().hasPerspective(); const SkMatrix* geometryProcessorViewM = &SkMatrix::I(); const SkMatrix* geometryProcessorLocalM = &invert; const SkMatrix* toDevice = NULL; const SkMatrix* toSrc = NULL; if (hasPerspective) { geometryProcessorViewM = &this->viewMatrix(); geometryProcessorLocalM = &SkMatrix::I(); toDevice = &this->viewMatrix(); toSrc = &invert; } // Setup geometry processors for worst case uint32_t gpFlags = GrDefaultGeoProcFactory::kPosition_GPType | GrDefaultGeoProcFactory::kCoverage_GPType; SkAutoTUnref<const GrGeometryProcessor> lineGP( GrDefaultGeoProcFactory::Create(gpFlags, this->color(), this->usesLocalCoords(), this->coverageIgnored(), *geometryProcessorViewM, *geometryProcessorLocalM, this->coverage())); SkAutoTUnref<const GrGeometryProcessor> quadGP( GrQuadEffect::Create(this->color(), *geometryProcessorViewM, kHairlineAA_GrProcessorEdgeType, batchTarget->caps(), *geometryProcessorLocalM, this->usesLocalCoords(), this->coverage())); SkAutoTUnref<const GrGeometryProcessor> conicGP( GrConicEffect::Create(this->color(), *geometryProcessorViewM, kHairlineAA_GrProcessorEdgeType, batchTarget->caps(), *geometryProcessorLocalM, this->usesLocalCoords(), this->coverage())); // This is hand inlined for maximum performance. PREALLOC_PTARRAY(128) lines; PREALLOC_PTARRAY(128) quads; PREALLOC_PTARRAY(128) conics; IntArray qSubdivs; FloatArray cWeights; int quadCount = 0; int instanceCount = fGeoData.count(); for (int i = 0; i < instanceCount; i++) { const Geometry& args = fGeoData[i]; quadCount += gather_lines_and_quads(args.fPath, args.fViewMatrix, args.fDevClipBounds, &lines, &quads, &conics, &qSubdivs, &cWeights); } int lineCount = lines.count() / 2; int conicCount = conics.count() / 3; // do lines first if (lineCount) { SkAutoTUnref<const GrIndexBuffer> linesIndexBuffer( ref_lines_index_buffer(batchTarget->resourceProvider())); batchTarget->initDraw(lineGP, pipeline); const GrVertexBuffer* vertexBuffer; int firstVertex; size_t vertexStride = lineGP->getVertexStride(); int vertexCount = kLineSegNumVertices * lineCount; LineVertex* verts = reinterpret_cast<LineVertex*>( batchTarget->makeVertSpace(vertexStride, vertexCount, &vertexBuffer, &firstVertex)); if (!verts|| !linesIndexBuffer) { SkDebugf("Could not allocate vertices\n"); return; } SkASSERT(lineGP->getVertexStride() == sizeof(LineVertex)); for (int i = 0; i < lineCount; ++i) { add_line(&lines[2*i], toSrc, this->coverage(), &verts); } { GrVertices vertices; vertices.initInstanced(kTriangles_GrPrimitiveType, vertexBuffer, linesIndexBuffer, firstVertex, kLineSegNumVertices, kIdxsPerLineSeg, lineCount, kLineSegsNumInIdxBuffer); batchTarget->draw(vertices); } } if (quadCount || conicCount) { const GrVertexBuffer* vertexBuffer; int firstVertex; SkAutoTUnref<const GrIndexBuffer> quadsIndexBuffer( ref_quads_index_buffer(batchTarget->resourceProvider())); size_t vertexStride = sizeof(BezierVertex); int vertexCount = kQuadNumVertices * quadCount + kQuadNumVertices * conicCount; void *vertices = batchTarget->makeVertSpace(vertexStride, vertexCount, &vertexBuffer, &firstVertex); if (!vertices || !quadsIndexBuffer) { SkDebugf("Could not allocate vertices\n"); return; } // Setup vertices BezierVertex* verts = reinterpret_cast<BezierVertex*>(vertices); int unsubdivQuadCnt = quads.count() / 3; for (int i = 0; i < unsubdivQuadCnt; ++i) { SkASSERT(qSubdivs[i] >= 0); add_quads(&quads[3*i], qSubdivs[i], toDevice, toSrc, &verts); } // Start Conics for (int i = 0; i < conicCount; ++i) { add_conics(&conics[3*i], cWeights[i], toDevice, toSrc, &verts); } if (quadCount > 0) { batchTarget->initDraw(quadGP, pipeline); { GrVertices verts; verts.initInstanced(kTriangles_GrPrimitiveType, vertexBuffer, quadsIndexBuffer, firstVertex, kQuadNumVertices, kIdxsPerQuad, quadCount, kQuadsNumInIdxBuffer); batchTarget->draw(verts); firstVertex += quadCount * kQuadNumVertices; } } if (conicCount > 0) { batchTarget->initDraw(conicGP, pipeline); { GrVertices verts; verts.initInstanced(kTriangles_GrPrimitiveType, vertexBuffer, quadsIndexBuffer, firstVertex, kQuadNumVertices, kIdxsPerQuad, conicCount, kQuadsNumInIdxBuffer); batchTarget->draw(verts); } } } }
/** * Generates the lines and quads to be rendered. Lines are always recorded in * device space. We will do a device space bloat to account for the 1pixel * thickness. * Quads are recorded in device space unless m contains * perspective, then in they are in src space. We do this because we will * subdivide large quads to reduce over-fill. This subdivision has to be * performed before applying the perspective matrix. */ static int gather_lines_and_quads(const SkPath& path, const SkMatrix& m, const SkIRect& devClipBounds, GrAAHairLinePathRenderer::PtArray* lines, GrAAHairLinePathRenderer::PtArray* quads, GrAAHairLinePathRenderer::PtArray* conics, GrAAHairLinePathRenderer::IntArray* quadSubdivCnts, GrAAHairLinePathRenderer::FloatArray* conicWeights) { SkPath::Iter iter(path, false); int totalQuadCount = 0; SkRect bounds; SkIRect ibounds; bool persp = m.hasPerspective(); for (;;) { SkPoint pathPts[4]; SkPoint devPts[4]; SkPath::Verb verb = iter.next(pathPts); switch (verb) { case SkPath::kConic_Verb: { SkConic dst[4]; // We chop the conics to create tighter clipping to hide error // that appears near max curvature of very thin conics. Thin // hyperbolas with high weight still show error. int conicCnt = chop_conic(pathPts, dst, iter.conicWeight()); for (int i = 0; i < conicCnt; ++i) { SkPoint* chopPnts = dst[i].fPts; m.mapPoints(devPts, chopPnts, 3); bounds.setBounds(devPts, 3); bounds.outset(SK_Scalar1, SK_Scalar1); bounds.roundOut(&ibounds); if (SkIRect::Intersects(devClipBounds, ibounds)) { if (is_degen_quad_or_conic(devPts)) { SkPoint* pts = lines->push_back_n(4); pts[0] = devPts[0]; pts[1] = devPts[1]; pts[2] = devPts[1]; pts[3] = devPts[2]; } else { // when in perspective keep conics in src space SkPoint* cPts = persp ? chopPnts : devPts; SkPoint* pts = conics->push_back_n(3); pts[0] = cPts[0]; pts[1] = cPts[1]; pts[2] = cPts[2]; conicWeights->push_back() = dst[i].fW; } } } break; } case SkPath::kMove_Verb: break; case SkPath::kLine_Verb: m.mapPoints(devPts, pathPts, 2); bounds.setBounds(devPts, 2); bounds.outset(SK_Scalar1, SK_Scalar1); bounds.roundOut(&ibounds); if (SkIRect::Intersects(devClipBounds, ibounds)) { SkPoint* pts = lines->push_back_n(2); pts[0] = devPts[0]; pts[1] = devPts[1]; } break; case SkPath::kQuad_Verb: { SkPoint choppedPts[5]; // Chopping the quad helps when the quad is either degenerate or nearly degenerate. // When it is degenerate it allows the approximation with lines to work since the // chop point (if there is one) will be at the parabola's vertex. In the nearly // degenerate the QuadUVMatrix computed for the points is almost singular which // can cause rendering artifacts. int n = SkChopQuadAtMaxCurvature(pathPts, choppedPts); for (int i = 0; i < n; ++i) { SkPoint* quadPts = choppedPts + i * 2; m.mapPoints(devPts, quadPts, 3); bounds.setBounds(devPts, 3); bounds.outset(SK_Scalar1, SK_Scalar1); bounds.roundOut(&ibounds); if (SkIRect::Intersects(devClipBounds, ibounds)) { int subdiv = num_quad_subdivs(devPts); SkASSERT(subdiv >= -1); if (-1 == subdiv) { SkPoint* pts = lines->push_back_n(4); pts[0] = devPts[0]; pts[1] = devPts[1]; pts[2] = devPts[1]; pts[3] = devPts[2]; } else { // when in perspective keep quads in src space SkPoint* qPts = persp ? quadPts : devPts; SkPoint* pts = quads->push_back_n(3); pts[0] = qPts[0]; pts[1] = qPts[1]; pts[2] = qPts[2]; quadSubdivCnts->push_back() = subdiv; totalQuadCount += 1 << subdiv; } } } break; } case SkPath::kCubic_Verb: m.mapPoints(devPts, pathPts, 4); bounds.setBounds(devPts, 4); bounds.outset(SK_Scalar1, SK_Scalar1); bounds.roundOut(&ibounds); if (SkIRect::Intersects(devClipBounds, ibounds)) { PREALLOC_PTARRAY(32) q; // we don't need a direction if we aren't constraining the subdivision const SkPathPriv::FirstDirection kDummyDir = SkPathPriv::kCCW_FirstDirection; // We convert cubics to quadratics (for now). // In perspective have to do conversion in src space. if (persp) { SkScalar tolScale = GrPathUtils::scaleToleranceToSrc(SK_Scalar1, m, path.getBounds()); GrPathUtils::convertCubicToQuads(pathPts, tolScale, false, kDummyDir, &q); } else { GrPathUtils::convertCubicToQuads(devPts, SK_Scalar1, false, kDummyDir, &q); } for (int i = 0; i < q.count(); i += 3) { SkPoint* qInDevSpace; // bounds has to be calculated in device space, but q is // in src space when there is perspective. if (persp) { m.mapPoints(devPts, &q[i], 3); bounds.setBounds(devPts, 3); qInDevSpace = devPts; } else { bounds.setBounds(&q[i], 3); qInDevSpace = &q[i]; } bounds.outset(SK_Scalar1, SK_Scalar1); bounds.roundOut(&ibounds); if (SkIRect::Intersects(devClipBounds, ibounds)) { int subdiv = num_quad_subdivs(qInDevSpace); SkASSERT(subdiv >= -1); if (-1 == subdiv) { SkPoint* pts = lines->push_back_n(4); // lines should always be in device coords pts[0] = qInDevSpace[0]; pts[1] = qInDevSpace[1]; pts[2] = qInDevSpace[1]; pts[3] = qInDevSpace[2]; } else { SkPoint* pts = quads->push_back_n(3); // q is already in src space when there is no // perspective and dev coords otherwise. pts[0] = q[0 + i]; pts[1] = q[1 + i]; pts[2] = q[2 + i]; quadSubdivCnts->push_back() = subdiv; totalQuadCount += 1 << subdiv; } } } } break; case SkPath::kClose_Verb: break; case SkPath::kDone_Verb: return totalQuadCount; } } }
bool GrAAHairLinePathRenderer::createGeom(GrDrawTarget::StageBitfield stages) { int rtHeight = fTarget->getRenderTarget()->height(); GrIRect clip; if (fTarget->getClip().hasConservativeBounds()) { GrRect clipRect = fTarget->getClip().getConservativeBounds(); clipRect.roundOut(&clip); } else { clip.setLargest(); } // If none of the inputs that affect generation of path geometry have // have changed since last previous path draw then we can reuse the // previous geoemtry. if (stages == fPreviousStages && fPreviousViewMatrix == fTarget->getViewMatrix() && fPreviousTranslate == fTranslate && rtHeight == fPreviousRTHeight && fClipRect == clip) { return true; } GrVertexLayout layout = GrDrawTarget::kEdge_VertexLayoutBit; for (int s = 0; s < GrDrawState::kNumStages; ++s) { if ((1 << s) & stages) { layout |= GrDrawTarget::StagePosAsTexCoordVertexLayoutBit(s); } } GrMatrix viewM = fTarget->getViewMatrix(); PREALLOC_PTARRAY(128) lines; PREALLOC_PTARRAY(128) quads; IntArray qSubdivs; fQuadCnt = generate_lines_and_quads(*fPath, viewM, fTranslate, clip, &lines, &quads, &qSubdivs); fLineSegmentCnt = lines.count() / 2; int vertCnt = kVertsPerLineSeg * fLineSegmentCnt + kVertsPerQuad * fQuadCnt; GrAssert(sizeof(Vertex) == GrDrawTarget::VertexSize(layout)); Vertex* verts; if (!fTarget->reserveVertexSpace(layout, vertCnt, (void**)&verts)) { return false; } Vertex* base = verts; const GrMatrix* toDevice = NULL; const GrMatrix* toSrc = NULL; GrMatrix ivm; if (viewM.hasPerspective()) { if (viewM.invert(&ivm)) { toDevice = &viewM; toSrc = &ivm; } } for (int i = 0; i < fLineSegmentCnt; ++i) { add_line(&lines[2*i], rtHeight, toSrc, &verts); } int unsubdivQuadCnt = quads.count() / 3; for (int i = 0; i < unsubdivQuadCnt; ++i) { GrAssert(qSubdivs[i] >= 0); add_quads(&quads[3*i], qSubdivs[i], toDevice, toSrc, &verts); } fPreviousStages = stages; fPreviousViewMatrix = fTarget->getViewMatrix(); fPreviousRTHeight = rtHeight; fClipRect = clip; fPreviousTranslate = fTranslate; return true; }
bool GrAAHairLinePathRenderer::onDrawPath(const SkPath& path, const SkStrokeRec& stroke, GrDrawTarget* target, bool antiAlias) { GrDrawState* drawState = target->drawState(); SkScalar hairlineCoverage; if (IsStrokeHairlineOrEquivalent(stroke, target->getDrawState().getViewMatrix(), &hairlineCoverage)) { uint8_t newCoverage = SkScalarRoundToInt(hairlineCoverage * target->getDrawState().getCoverage()); target->drawState()->setCoverage(newCoverage); } SkIRect devClipBounds; target->getClip()->getConservativeBounds(drawState->getRenderTarget(), &devClipBounds); int lineCnt; int quadCnt; int conicCnt; PREALLOC_PTARRAY(128) lines; PREALLOC_PTARRAY(128) quads; PREALLOC_PTARRAY(128) conics; IntArray qSubdivs; FloatArray cWeights; quadCnt = generate_lines_and_quads(path, drawState->getViewMatrix(), devClipBounds, &lines, &quads, &conics, &qSubdivs, &cWeights); lineCnt = lines.count() / 2; conicCnt = conics.count() / 3; // do lines first if (lineCnt) { GrDrawTarget::AutoReleaseGeometry arg; SkRect devBounds; if (!this->createLineGeom(path, target, lines, lineCnt, &arg, &devBounds)) { return false; } GrDrawTarget::AutoStateRestore asr; // createLineGeom transforms the geometry to device space when the matrix does not have // perspective. if (target->getDrawState().getViewMatrix().hasPerspective()) { asr.set(target, GrDrawTarget::kPreserve_ASRInit); } else if (!asr.setIdentity(target, GrDrawTarget::kPreserve_ASRInit)) { return false; } GrDrawState* drawState = target->drawState(); // Check devBounds SkASSERT(check_bounds<LineVertex>(drawState, devBounds, arg.vertices(), kVertsPerLineSeg * lineCnt)); { GrDrawState::AutoRestoreEffects are(drawState); target->setIndexSourceToBuffer(fLinesIndexBuffer); int lines = 0; while (lines < lineCnt) { int n = SkTMin(lineCnt - lines, kNumLineSegsInIdxBuffer); target->drawIndexed(kTriangles_GrPrimitiveType, kVertsPerLineSeg*lines, // startV 0, // startI kVertsPerLineSeg*n, // vCount kIdxsPerLineSeg*n, // iCount &devBounds); lines += n; } } } // then quadratics/conics if (quadCnt || conicCnt) { GrDrawTarget::AutoReleaseGeometry arg; SkRect devBounds; if (!this->createBezierGeom(path, target, quads, quadCnt, conics, conicCnt, qSubdivs, cWeights, &arg, &devBounds)) { return false; } GrDrawTarget::AutoStateRestore asr; // createGeom transforms the geometry to device space when the matrix does not have // perspective. if (target->getDrawState().getViewMatrix().hasPerspective()) { asr.set(target, GrDrawTarget::kPreserve_ASRInit); } else if (!asr.setIdentity(target, GrDrawTarget::kPreserve_ASRInit)) { return false; } GrDrawState* drawState = target->drawState(); static const int kEdgeAttrIndex = 1; // Check devBounds SkASSERT(check_bounds<BezierVertex>(drawState, devBounds, arg.vertices(), kVertsPerQuad * quadCnt + kVertsPerQuad * conicCnt)); if (quadCnt > 0) { GrEffect* hairQuadEffect = GrQuadEffect::Create(kHairlineAA_GrEffectEdgeType, *target->caps()); SkASSERT(hairQuadEffect); GrDrawState::AutoRestoreEffects are(drawState); target->setIndexSourceToBuffer(fQuadsIndexBuffer); drawState->setGeometryProcessor(hairQuadEffect, kEdgeAttrIndex)->unref(); int quads = 0; while (quads < quadCnt) { int n = SkTMin(quadCnt - quads, kNumQuadsInIdxBuffer); target->drawIndexed(kTriangles_GrPrimitiveType, kVertsPerQuad*quads, // startV 0, // startI kVertsPerQuad*n, // vCount kIdxsPerQuad*n, // iCount &devBounds); quads += n; } } if (conicCnt > 0) { GrDrawState::AutoRestoreEffects are(drawState); GrEffect* hairConicEffect = GrConicEffect::Create(kHairlineAA_GrEffectEdgeType, *target->caps()); SkASSERT(hairConicEffect); drawState->setGeometryProcessor(hairConicEffect, 1, 2)->unref(); int conics = 0; while (conics < conicCnt) { int n = SkTMin(conicCnt - conics, kNumQuadsInIdxBuffer); target->drawIndexed(kTriangles_GrPrimitiveType, kVertsPerQuad*(quadCnt + conics), // startV 0, // startI kVertsPerQuad*n, // vCount kIdxsPerQuad*n, // iCount &devBounds); conics += n; } } } target->resetIndexSource(); return true; }