static inline void add_cubic_segments(const SkPoint pts[4], SkPath::Direction dir, SegmentArray* segments, SkRect* devBounds) { SkSTArray<15, SkPoint, true> quads; GrPathUtils::convertCubicToQuads(pts, SK_Scalar1, true, dir, &quads); int count = quads.count(); for (int q = 0; q < count; q += 3) { add_quad_segment(&quads[q], segments, devBounds); } }
void AAStrokeRectBatch::onPrepareDraws(Target* target) const { bool canTweakAlphaForCoverage = this->canTweakAlphaForCoverage(); SkAutoTUnref<const GrGeometryProcessor> gp(create_stroke_rect_gp(canTweakAlphaForCoverage, this->viewMatrix(), this->usesLocalCoords(), this->coverageIgnored())); if (!gp) { SkDebugf("Couldn't create GrGeometryProcessor\n"); return; } target->initDraw(gp); size_t vertexStride = gp->getVertexStride(); SkASSERT(canTweakAlphaForCoverage ? vertexStride == sizeof(GrDefaultGeoProcFactory::PositionColorAttr) : vertexStride == sizeof(GrDefaultGeoProcFactory::PositionColorCoverageAttr)); int innerVertexNum = 4; int outerVertexNum = this->miterStroke() ? 4 : 8; int verticesPerInstance = (outerVertexNum + innerVertexNum) * 2; int indicesPerInstance = this->miterStroke() ? kMiterIndexCnt : kBevelIndexCnt; int instanceCount = fGeoData.count(); const SkAutoTUnref<const GrBuffer> indexBuffer( GetIndexBuffer(target->resourceProvider(), this->miterStroke())); InstancedHelper helper; void* vertices = helper.init(target, kTriangles_GrPrimitiveType, vertexStride, indexBuffer, verticesPerInstance, indicesPerInstance, instanceCount); if (!vertices || !indexBuffer) { SkDebugf("Could not allocate vertices\n"); return; } for (int i = 0; i < instanceCount; i++) { const Geometry& args = fGeoData[i]; this->generateAAStrokeRectGeometry(vertices, i * verticesPerInstance * vertexStride, vertexStride, outerVertexNum, innerVertexNum, args.fColor, args.fDevOutside, args.fDevOutsideAssist, args.fDevInside, fMiterStroke, args.fDegenerate, canTweakAlphaForCoverage); } helper.recordDraw(target); }
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); } } } }
// determine that slop required after quad/quad finds a candidate intersection // use the cross of the tangents plus the distance from 1 or 0 as knobs DEF_TEST(PathOpsCubicQuadSlop, reporter) { // create a random non-selfintersecting cubic // break it into quadratics // offset the quadratic, measuring the slop required to find the intersection if (!gPathOpCubicQuadSlopVerbose) { // takes a while to run -- so exclude it by default return; } int results[101]; sk_bzero(results, sizeof(results)); double minCross[101]; sk_bzero(minCross, sizeof(minCross)); double maxCross[101]; sk_bzero(maxCross, sizeof(maxCross)); double sumCross[101]; sk_bzero(sumCross, sizeof(sumCross)); int foundOne = 0; int slopCount = 1; SkRandom ran; for (int index = 0; index < 10000000; ++index) { if (index % 1000 == 999) SkDebugf("."); SkDCubic cubic = {{ {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)}, {ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000)} }}; SkIntersections i; if (i.intersect(cubic)) { continue; } SkSTArray<kCubicToQuadSubdivisionDepth, double, true> ts; cubic.toQuadraticTs(cubic.calcPrecision(), &ts); double tStart = 0; int tsCount = ts.count(); for (int i1 = 0; i1 <= tsCount; ++i1) { const double tEnd = i1 < tsCount ? ts[i1] : 1; SkDCubic part = cubic.subDivide(tStart, tEnd); SkDQuad quad = part.toQuad(); SkReduceOrder reducer; int order = reducer.reduce(quad); if (order != 3) { continue; } for (int i2 = 0; i2 < 100; ++i2) { SkDPoint endDisplacement = {ran.nextRangeF(-100, 100), ran.nextRangeF(-100, 100)}; SkDQuad nearby = {{ {quad[0].fX + endDisplacement.fX, quad[0].fY + endDisplacement.fY}, {quad[1].fX + ran.nextRangeF(-100, 100), quad[1].fY + ran.nextRangeF(-100, 100)}, {quad[2].fX - endDisplacement.fX, quad[2].fY - endDisplacement.fY} }}; order = reducer.reduce(nearby); if (order != 3) { continue; } SkIntersections locals; locals.allowNear(false); locals.intersect(quad, nearby); if (locals.used() != 1) { continue; } // brute force find actual intersection SkDLine cubicLine = {{ {0, 0}, {cubic[0].fX, cubic[0].fY } }}; SkIntersections liner; int i3; int found = -1; int foundErr = true; for (i3 = 1; i3 <= 1000; ++i3) { cubicLine[0] = cubicLine[1]; cubicLine[1] = cubic.ptAtT(i3 / 1000.); liner.reset(); liner.allowNear(false); liner.intersect(nearby, cubicLine); if (liner.used() == 0) { continue; } if (liner.used() > 1) { foundErr = true; break; } if (found > 0) { foundErr = true; break; } foundErr = false; found = i3; } if (foundErr) { continue; } SkDVector dist = liner.pt(0) - locals.pt(0); SkDVector qV = nearby.dxdyAtT(locals[0][0]); double cubicT = (found - 1 + liner[1][0]) / 1000.; SkDVector cV = cubic.dxdyAtT(cubicT); double qxc = qV.crossCheck(cV); double qvLen = qV.length(); double cvLen = cV.length(); double maxLen = SkTMax(qvLen, cvLen); qxc /= maxLen; double quadT = tStart + (tEnd - tStart) * locals[0][0]; double diffT = fabs(cubicT - quadT); int diffIdx = (int) (diffT * 100); results[diffIdx]++; double absQxc = fabs(qxc); if (sumCross[diffIdx] == 0) { minCross[diffIdx] = maxCross[diffIdx] = sumCross[diffIdx] = absQxc; } else { minCross[diffIdx] = SkTMin(minCross[diffIdx], absQxc); maxCross[diffIdx] = SkTMax(maxCross[diffIdx], absQxc); sumCross[diffIdx] += absQxc; } if (diffIdx >= 20) { #if 01 SkDebugf("cubic={{{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}}}" " quad={{{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}}}" " {{{%1.9g,%1.9g}, {%1.9g,%1.9g}}}" " qT=%1.9g cT=%1.9g dist=%1.9g cross=%1.9g\n", cubic[0].fX, cubic[0].fY, cubic[1].fX, cubic[1].fY, cubic[2].fX, cubic[2].fY, cubic[3].fX, cubic[3].fY, nearby[0].fX, nearby[0].fY, nearby[1].fX, nearby[1].fY, nearby[2].fX, nearby[2].fY, liner.pt(0).fX, liner.pt(0).fY, locals.pt(0).fX, locals.pt(0).fY, quadT, cubicT, dist.length(), qxc); #else SkDebugf("qT=%1.9g cT=%1.9g dist=%1.9g cross=%1.9g\n", quadT, cubicT, dist.length(), qxc); SkDebugf("<div id=\"slop%d\">\n", ++slopCount); SkDebugf("{{{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}}}\n" "{{{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}}}\n" "{{{%1.9g,%1.9g}, {%1.9g,%1.9g}}}\n", cubic[0].fX, cubic[0].fY, cubic[1].fX, cubic[1].fY, cubic[2].fX, cubic[2].fY, cubic[3].fX, cubic[3].fY, nearby[0].fX, nearby[0].fY, nearby[1].fX, nearby[1].fY, nearby[2].fX, nearby[2].fY, liner.pt(0).fX, liner.pt(0).fY, locals.pt(0).fX, locals.pt(0).fY); SkDebugf("</div>\n\n"); #endif } ++foundOne; } tStart = tEnd; } if (++foundOne >= 100000) { break; } } #if 01 SkDebugf("slopCount=%d\n", slopCount); int max = 100; while (results[max] == 0) { --max; } for (int i = 0; i <= max; ++i) { if (i > 0 && i % 10 == 0) { SkDebugf("\n"); } SkDebugf("%d ", results[i]); } SkDebugf("min\n"); for (int i = 0; i <= max; ++i) { if (i > 0 && i % 10 == 0) { SkDebugf("\n"); } SkDebugf("%1.9g ", minCross[i]); } SkDebugf("max\n"); for (int i = 0; i <= max; ++i) { if (i > 0 && i % 10 == 0) { SkDebugf("\n"); } SkDebugf("%1.9g ", maxCross[i]); } SkDebugf("avg\n"); for (int i = 0; i <= max; ++i) { if (i > 0 && i % 10 == 0) { SkDebugf("\n"); } SkDebugf("%1.9g ", sumCross[i] / results[i]); } #else for (int i = 1; i < slopCount; ++i) { SkDebugf(" slop%d,\n", i); } #endif SkDebugf("\n"); }
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; }
static bool is_linear_inner(const SkDQuad& q1, double t1s, double t1e, const SkDQuad& q2, double t2s, double t2e, SkIntersections* i, bool* subDivide) { SkDQuad hull = q1.subDivide(t1s, t1e); SkDLine line = {{hull[2], hull[0]}}; const SkDLine* testLines[] = { &line, (const SkDLine*) &hull[0], (const SkDLine*) &hull[1] }; const size_t kTestCount = SK_ARRAY_COUNT(testLines); SkSTArray<kTestCount * 2, double, true> tsFound; for (size_t index = 0; index < kTestCount; ++index) { SkIntersections rootTs; rootTs.allowNear(false); int roots = rootTs.intersect(q2, *testLines[index]); for (int idx2 = 0; idx2 < roots; ++idx2) { double t = rootTs[0][idx2]; #ifdef SK_DEBUG SkDPoint qPt = q2.ptAtT(t); SkDPoint lPt = testLines[index]->ptAtT(rootTs[1][idx2]); SkASSERT(qPt.approximatelyEqual(lPt)); #endif if (approximately_negative(t - t2s) || approximately_positive(t - t2e)) { continue; } tsFound.push_back(rootTs[0][idx2]); } } int tCount = tsFound.count(); if (tCount <= 0) { return true; } double tMin, tMax; if (tCount == 1) { tMin = tMax = tsFound[0]; } else { SkASSERT(tCount > 1); SkTQSort<double>(tsFound.begin(), tsFound.end() - 1); tMin = tsFound[0]; tMax = tsFound[tsFound.count() - 1]; } SkDPoint end = q2.ptAtT(t2s); bool startInTriangle = hull.pointInHull(end); if (startInTriangle) { tMin = t2s; } end = q2.ptAtT(t2e); bool endInTriangle = hull.pointInHull(end); if (endInTriangle) { tMax = t2e; } int split = 0; SkDVector dxy1, dxy2; if (tMin != tMax || tCount > 2) { dxy2 = q2.dxdyAtT(tMin); for (int index = 1; index < tCount; ++index) { dxy1 = dxy2; dxy2 = q2.dxdyAtT(tsFound[index]); double dot = dxy1.dot(dxy2); if (dot < 0) { split = index - 1; break; } } } if (split == 0) { // there's one point if (add_intercept(q1, q2, tMin, tMax, i, subDivide)) { return true; } i->swap(); return is_linear_inner(q2, tMin, tMax, q1, t1s, t1e, i, subDivide); } // At this point, we have two ranges of t values -- treat each separately at the split bool result; if (add_intercept(q1, q2, tMin, tsFound[split - 1], i, subDivide)) { result = true; } else { i->swap(); result = is_linear_inner(q2, tMin, tsFound[split - 1], q1, t1s, t1e, i, subDivide); } if (add_intercept(q1, q2, tsFound[split], tMax, i, subDivide)) { result = true; } else { i->swap(); result |= is_linear_inner(q2, tsFound[split], tMax, q1, t1s, t1e, i, subDivide); } return result; }
static bool get_analytic_clip_processor(const GrReducedClip::ElementList& elements, bool abortIfAA, SkVector& clipToRTOffset, const SkRect* drawBounds, sk_sp<GrFragmentProcessor>* resultFP) { SkRect boundsInClipSpace; if (drawBounds) { boundsInClipSpace = *drawBounds; boundsInClipSpace.offset(-clipToRTOffset.fX, -clipToRTOffset.fY); } SkASSERT(elements.count() <= kMaxAnalyticElements); SkSTArray<kMaxAnalyticElements, sk_sp<GrFragmentProcessor>> fps; GrReducedClip::ElementList::Iter iter(elements); while (iter.get()) { SkRegion::Op op = iter.get()->getOp(); bool invert; bool skip = false; switch (op) { case SkRegion::kReplace_Op: SkASSERT(iter.get() == elements.head()); // Fallthrough, handled same as intersect. case SkRegion::kIntersect_Op: invert = false; if (drawBounds && iter.get()->contains(boundsInClipSpace)) { skip = true; } break; case SkRegion::kDifference_Op: invert = true; // We don't currently have a cheap test for whether a rect is fully outside an // element's primitive, so don't attempt to set skip. break; default: return false; } if (!skip) { GrPrimitiveEdgeType edgeType; if (iter.get()->isAA()) { if (abortIfAA) { return false; } edgeType = invert ? kInverseFillAA_GrProcessorEdgeType : kFillAA_GrProcessorEdgeType; } else { edgeType = invert ? kInverseFillBW_GrProcessorEdgeType : kFillBW_GrProcessorEdgeType; } switch (iter.get()->getType()) { case SkClipStack::Element::kPath_Type: fps.emplace_back(GrConvexPolyEffect::Make(edgeType, iter.get()->getPath(), &clipToRTOffset)); break; case SkClipStack::Element::kRRect_Type: { SkRRect rrect = iter.get()->getRRect(); rrect.offset(clipToRTOffset.fX, clipToRTOffset.fY); fps.emplace_back(GrRRectEffect::Make(edgeType, rrect)); break; } case SkClipStack::Element::kRect_Type: { SkRect rect = iter.get()->getRect(); rect.offset(clipToRTOffset.fX, clipToRTOffset.fY); fps.emplace_back(GrConvexPolyEffect::Make(edgeType, rect)); break; } default: break; } if (!fps.back()) { return false; } } iter.next(); } *resultFP = nullptr; if (fps.count()) { *resultFP = GrFragmentProcessor::RunInSeries(fps.begin(), fps.count()); } return true; }
void AAHairlineOp::onPrepareDraws(Target* target) const { // 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 = nullptr; const SkMatrix* toSrc = nullptr; if (hasPerspective) { geometryProcessorViewM = &this->viewMatrix(); geometryProcessorLocalM = &SkMatrix::I(); toDevice = &this->viewMatrix(); toSrc = &invert; } // 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 = fPaths.count(); for (int i = 0; i < instanceCount; i++) { const PathData& args = fPaths[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) { sk_sp<GrGeometryProcessor> lineGP; { using namespace GrDefaultGeoProcFactory; Color color(this->color()); LocalCoords localCoords(this->usesLocalCoords() ? LocalCoords::kUsePosition_Type : LocalCoords::kUnused_Type); localCoords.fMatrix = geometryProcessorLocalM; lineGP = GrDefaultGeoProcFactory::Make(color, Coverage::kAttribute_Type, localCoords, *geometryProcessorViewM); } sk_sp<const GrBuffer> linesIndexBuffer( ref_lines_index_buffer(target->resourceProvider())); const GrBuffer* vertexBuffer; int firstVertex; size_t vertexStride = lineGP->getVertexStride(); int vertexCount = kLineSegNumVertices * lineCount; LineVertex* verts = reinterpret_cast<LineVertex*>( target->makeVertexSpace(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); } GrMesh mesh; mesh.initInstanced(kTriangles_GrPrimitiveType, vertexBuffer, linesIndexBuffer.get(), firstVertex, kLineSegNumVertices, kIdxsPerLineSeg, lineCount, kLineSegsNumInIdxBuffer); target->draw(lineGP.get(), this->pipeline(), mesh); } if (quadCount || conicCount) { sk_sp<GrGeometryProcessor> quadGP( GrQuadEffect::Make(this->color(), *geometryProcessorViewM, kHairlineAA_GrProcessorEdgeType, target->caps(), *geometryProcessorLocalM, this->usesLocalCoords(), this->coverage())); sk_sp<GrGeometryProcessor> conicGP( GrConicEffect::Make(this->color(), *geometryProcessorViewM, kHairlineAA_GrProcessorEdgeType, target->caps(), *geometryProcessorLocalM, this->usesLocalCoords(), this->coverage())); const GrBuffer* vertexBuffer; int firstVertex; sk_sp<const GrBuffer> quadsIndexBuffer( ref_quads_index_buffer(target->resourceProvider())); size_t vertexStride = sizeof(BezierVertex); int vertexCount = kQuadNumVertices * quadCount + kQuadNumVertices * conicCount; void *vertices = target->makeVertexSpace(vertexStride, vertexCount, &vertexBuffer, &firstVertex); if (!vertices || !quadsIndexBuffer) { SkDebugf("Could not allocate vertices\n"); return; } // Setup vertices BezierVertex* bezVerts = 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, &bezVerts); } // Start Conics for (int i = 0; i < conicCount; ++i) { add_conics(&conics[3*i], cWeights[i], toDevice, toSrc, &bezVerts); } if (quadCount > 0) { GrMesh mesh; mesh.initInstanced(kTriangles_GrPrimitiveType, vertexBuffer, quadsIndexBuffer.get(), firstVertex, kQuadNumVertices, kIdxsPerQuad, quadCount, kQuadsNumInIdxBuffer); target->draw(quadGP.get(), this->pipeline(), mesh); firstVertex += quadCount * kQuadNumVertices; } if (conicCount > 0) { GrMesh mesh; mesh.initInstanced(kTriangles_GrPrimitiveType, vertexBuffer, quadsIndexBuffer.get(), firstVertex, kQuadNumVertices, kIdxsPerQuad, conicCount, kQuadsNumInIdxBuffer); target->draw(conicGP.get(), this->pipeline(), mesh); } } }
void generateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) override { int instanceCount = fGeoData.count(); SkMatrix invert; if (this->usesLocalCoords() && !this->viewMatrix().invert(&invert)) { SkDebugf("Could not invert viewmatrix\n"); return; } // Setup GrGeometryProcessor SkAutoTUnref<GrGeometryProcessor> quadProcessor(QuadEdgeEffect::Create(this->color(), invert)); batchTarget->initDraw(quadProcessor, pipeline); // TODO remove this when batch is everywhere GrPipelineInfo init; init.fColorIgnored = fBatch.fColorIgnored; init.fOverrideColor = GrColor_ILLEGAL; init.fCoverageIgnored = fBatch.fCoverageIgnored; init.fUsesLocalCoords = this->usesLocalCoords(); quadProcessor->initBatchTracker(batchTarget->currentBatchTracker(), init); // TODO generate all segments for all paths and use one vertex buffer for (int i = 0; i < instanceCount; i++) { Geometry& args = fGeoData[i]; // 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 = &args.fViewMatrix; if (viewMatrix->hasPerspective()) { args.fPath.transform(*viewMatrix); viewMatrix = &SkMatrix::I(); } int vertexCount; int indexCount; enum { kPreallocSegmentCnt = 512 / sizeof(Segment), kPreallocDrawCnt = 4, }; SkSTArray<kPreallocSegmentCnt, Segment, true> segments; SkPoint fanPt; if (!get_segments(args.fPath, *viewMatrix, &segments, &fanPt, &vertexCount, &indexCount)) { continue; } const GrVertexBuffer* vertexBuffer; int firstVertex; size_t vertexStride = quadProcessor->getVertexStride(); void *vertices = batchTarget->vertexPool()->makeSpace(vertexStride, vertexCount, &vertexBuffer, &firstVertex); if (!vertices) { SkDebugf("Could not allocate vertices\n"); return; } const GrIndexBuffer* indexBuffer; int firstIndex; void *indices = batchTarget->indexPool()->makeSpace(indexCount, &indexBuffer, &firstIndex); if (!indices) { SkDebugf("Could not allocate indices\n"); return; } QuadVertex* verts = reinterpret_cast<QuadVertex*>(vertices); uint16_t* idxs = reinterpret_cast<uint16_t*>(indices); SkSTArray<kPreallocDrawCnt, Draw, true> draws; create_vertices(segments, fanPt, &draws, verts, idxs); GrDrawTarget::DrawInfo info; info.setVertexBuffer(vertexBuffer); info.setIndexBuffer(indexBuffer); info.setPrimitiveType(kTriangles_GrPrimitiveType); info.setStartIndex(firstIndex); int vOffset = 0; for (int i = 0; i < draws.count(); ++i) { const Draw& draw = draws[i]; info.setStartVertex(vOffset + firstVertex); info.setVertexCount(draw.fVertexCnt); info.setIndexCount(draw.fIndexCnt); batchTarget->draw(info); vOffset += draw.fVertexCnt; } } }
// FIXME: this and find chase should be merge together, along with // other code that walks winding in angles // OPTIMIZATION: Probably, the walked winding should be rolled into the angle structure // so it isn't duplicated by walkers like this one static SkOpSegment* findChaseOp(SkTDArray<SkOpSpan*>& chase, int& nextStart, int& nextEnd) { while (chase.count()) { SkOpSpan* span; chase.pop(&span); const SkOpSpan& backPtr = span->fOther->span(span->fOtherIndex); SkOpSegment* segment = backPtr.fOther; nextStart = backPtr.fOtherIndex; SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle, true> angles; int done = 0; if (segment->activeAngle(nextStart, &done, &angles)) { SkOpAngle* last = angles.end() - 1; nextStart = last->start(); nextEnd = last->end(); #if TRY_ROTATE *chase.insert(0) = span; #else *chase.append() = span; #endif return last->segment(); } if (done == angles.count()) { continue; } SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle*, true> sorted; bool sortable = SkOpSegment::SortAngles(angles, &sorted, SkOpSegment::kMayBeUnordered_SortAngleKind); int angleCount = sorted.count(); #if DEBUG_SORT sorted[0]->segment()->debugShowSort(__FUNCTION__, sorted, 0, sortable); #endif if (!sortable) { continue; } // find first angle, initialize winding to computed fWindSum int firstIndex = -1; const SkOpAngle* angle; bool foundAngle = true; do { ++firstIndex; if (firstIndex >= angleCount) { foundAngle = false; break; } angle = sorted[firstIndex]; segment = angle->segment(); } while (segment->windSum(angle) == SK_MinS32); if (!foundAngle) { continue; } #if DEBUG_SORT segment->debugShowSort(__FUNCTION__, sorted, firstIndex, sortable); #endif int sumMiWinding = segment->updateWindingReverse(angle); int sumSuWinding = segment->updateOppWindingReverse(angle); if (segment->operand()) { SkTSwap<int>(sumMiWinding, sumSuWinding); } int nextIndex = firstIndex + 1; int lastIndex = firstIndex != 0 ? firstIndex : angleCount; SkOpSegment* first = NULL; do { SkASSERT(nextIndex != firstIndex); if (nextIndex == angleCount) { nextIndex = 0; } angle = sorted[nextIndex]; segment = angle->segment(); int start = angle->start(); int end = angle->end(); int maxWinding, sumWinding, oppMaxWinding, oppSumWinding; segment->setUpWindings(start, end, &sumMiWinding, &sumSuWinding, &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding); if (!segment->done(angle)) { if (!first) { first = segment; nextStart = start; nextEnd = end; } (void) segment->markAngle(maxWinding, sumWinding, oppMaxWinding, oppSumWinding, angle); } } while (++nextIndex != lastIndex); if (first) { #if TRY_ROTATE *chase.insert(0) = span; #else *chase.append() = span; #endif return first; } } return NULL; }
bool GrCCPathParser::finalize(GrOnFlushResourceProvider* onFlushRP) { SkASSERT(!fParsingPath); // Call saveParsedPath() or discardParsedPath(). SkASSERT(fCoverageCountBatches.back().fEndNonScissorIndices == // Call closeCurrentBatch(). fTotalPrimitiveCounts[(int)ScissorMode::kNonScissored]); SkASSERT(fCoverageCountBatches.back().fEndScissorSubBatchIdx == fScissorSubBatches.count()); // Here we build a single instance buffer to share with every internal batch. // // CCPR processs 3 different types of primitives: triangles, quadratics, cubics. Each primitive // type is further divided into instances that require a scissor and those that don't. This // leaves us with 3*2 = 6 independent instance arrays to build for the GPU. // // Rather than place each instance array in its own GPU buffer, we allocate a single // megabuffer and lay them all out side-by-side. We can offset the "baseInstance" parameter in // our draw calls to direct the GPU to the applicable elements within a given array. // // We already know how big to make each of the 6 arrays from fTotalPrimitiveCounts, so layout is // straightforward. Start with triangles and quadratics. They both view the instance buffer as // an array of TriPointInstance[], so we can begin at zero and lay them out one after the other. fBaseInstances[0].fTriangles = 0; fBaseInstances[1].fTriangles = fBaseInstances[0].fTriangles + fTotalPrimitiveCounts[0].fTriangles; fBaseInstances[0].fQuadratics = fBaseInstances[1].fTriangles + fTotalPrimitiveCounts[1].fTriangles; fBaseInstances[1].fQuadratics = fBaseInstances[0].fQuadratics + fTotalPrimitiveCounts[0].fQuadratics; int triEndIdx = fBaseInstances[1].fQuadratics + fTotalPrimitiveCounts[1].fQuadratics; // Wound triangles and cubics both view the same instance buffer as an array of // QuadPointInstance[]. So, reinterpreting the instance data as QuadPointInstance[], we start // them on the first index that will not overwrite previous TriPointInstance data. int quadBaseIdx = GR_CT_DIV_ROUND_UP(triEndIdx * sizeof(TriPointInstance), sizeof(QuadPointInstance)); fBaseInstances[0].fWeightedTriangles = quadBaseIdx; fBaseInstances[1].fWeightedTriangles = fBaseInstances[0].fWeightedTriangles + fTotalPrimitiveCounts[0].fWeightedTriangles; fBaseInstances[0].fCubics = fBaseInstances[1].fWeightedTriangles + fTotalPrimitiveCounts[1].fWeightedTriangles; fBaseInstances[1].fCubics = fBaseInstances[0].fCubics + fTotalPrimitiveCounts[0].fCubics; fBaseInstances[0].fConics = fBaseInstances[1].fCubics + fTotalPrimitiveCounts[1].fCubics; fBaseInstances[1].fConics = fBaseInstances[0].fConics + fTotalPrimitiveCounts[0].fConics; int quadEndIdx = fBaseInstances[1].fConics + fTotalPrimitiveCounts[1].fConics; fInstanceBuffer = onFlushRP->makeBuffer(kVertex_GrBufferType, quadEndIdx * sizeof(QuadPointInstance)); if (!fInstanceBuffer) { return false; } TriPointInstance* triPointInstanceData = static_cast<TriPointInstance*>(fInstanceBuffer->map()); QuadPointInstance* quadPointInstanceData = reinterpret_cast<QuadPointInstance*>(triPointInstanceData); SkASSERT(quadPointInstanceData); PathInfo* nextPathInfo = fPathsInfo.begin(); float atlasOffsetX = 0.0, atlasOffsetY = 0.0; Sk2f atlasOffset; PrimitiveTallies instanceIndices[2] = {fBaseInstances[0], fBaseInstances[1]}; PrimitiveTallies* currIndices = nullptr; SkSTArray<256, int32_t, true> currFan; bool currFanIsTessellated = false; const SkTArray<SkPoint, true>& pts = fGeometry.points(); int ptsIdx = -1; int nextConicWeightIdx = 0; // Expand the ccpr verbs into GPU instance buffers. for (GrCCGeometry::Verb verb : fGeometry.verbs()) { switch (verb) { case GrCCGeometry::Verb::kBeginPath: SkASSERT(currFan.empty()); currIndices = &instanceIndices[(int)nextPathInfo->scissorMode()]; atlasOffsetX = static_cast<float>(nextPathInfo->atlasOffsetX()); atlasOffsetY = static_cast<float>(nextPathInfo->atlasOffsetY()); atlasOffset = {atlasOffsetX, atlasOffsetY}; currFanIsTessellated = nextPathInfo->hasFanTessellation(); if (currFanIsTessellated) { emit_tessellated_fan(nextPathInfo->fanTessellation(), nextPathInfo->fanTessellationCount(), atlasOffset, triPointInstanceData, quadPointInstanceData, currIndices); } ++nextPathInfo; continue; case GrCCGeometry::Verb::kBeginContour: SkASSERT(currFan.empty()); ++ptsIdx; if (!currFanIsTessellated) { currFan.push_back(ptsIdx); } continue; case GrCCGeometry::Verb::kLineTo: ++ptsIdx; if (!currFanIsTessellated) { SkASSERT(!currFan.empty()); currFan.push_back(ptsIdx); } continue; case GrCCGeometry::Verb::kMonotonicQuadraticTo: triPointInstanceData[currIndices->fQuadratics++].set(&pts[ptsIdx], atlasOffset); ptsIdx += 2; if (!currFanIsTessellated) { SkASSERT(!currFan.empty()); currFan.push_back(ptsIdx); } continue; case GrCCGeometry::Verb::kMonotonicCubicTo: quadPointInstanceData[currIndices->fCubics++].set(&pts[ptsIdx], atlasOffsetX, atlasOffsetY); ptsIdx += 3; if (!currFanIsTessellated) { SkASSERT(!currFan.empty()); currFan.push_back(ptsIdx); } continue; case GrCCGeometry::Verb::kMonotonicConicTo: quadPointInstanceData[currIndices->fConics++].setW( &pts[ptsIdx], atlasOffset, fGeometry.getConicWeight(nextConicWeightIdx)); ptsIdx += 2; ++nextConicWeightIdx; if (!currFanIsTessellated) { SkASSERT(!currFan.empty()); currFan.push_back(ptsIdx); } continue; case GrCCGeometry::Verb::kEndClosedContour: // endPt == startPt. if (!currFanIsTessellated) { SkASSERT(!currFan.empty()); currFan.pop_back(); } // fallthru. case GrCCGeometry::Verb::kEndOpenContour: // endPt != startPt. SkASSERT(!currFanIsTessellated || currFan.empty()); if (!currFanIsTessellated && currFan.count() >= 3) { int fanSize = currFan.count(); // Reserve space for emit_recursive_fan. Technically this can grow to // fanSize + log3(fanSize), but we approximate with log2. currFan.push_back_n(SkNextLog2(fanSize)); SkDEBUGCODE(TriPointInstance* end =) emit_recursive_fan(pts, currFan, 0, fanSize, atlasOffset, triPointInstanceData + currIndices->fTriangles); currIndices->fTriangles += fanSize - 2; SkASSERT(triPointInstanceData + currIndices->fTriangles == end); } currFan.reset(); continue; } }
void onPrepareDraws(Target* target) const override { #ifndef SK_IGNORE_LINEONLY_AA_CONVEX_PATH_OPTS if (this->linesOnly()) { this->prepareLinesOnlyDraws(target); return; } #endif int instanceCount = fGeoData.count(); SkMatrix invert; if (this->usesLocalCoords() && !this->viewMatrix().invert(&invert)) { SkDebugf("Could not invert viewmatrix\n"); return; } // Setup GrGeometryProcessor sk_sp<GrGeometryProcessor> quadProcessor( QuadEdgeEffect::Make(this->color(), invert, this->usesLocalCoords())); // TODO generate all segments for all paths and use one vertex buffer for (int i = 0; i < instanceCount; i++) { const Geometry& args = fGeoData[i]; // 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 = &args.fViewMatrix; // We avoid initializing the path unless we have to const SkPath* pathPtr = &args.fPath; SkTLazy<SkPath> tmpPath; if (viewMatrix->hasPerspective()) { SkPath* tmpPathPtr = tmpPath.init(*pathPtr); tmpPathPtr->setIsVolatile(true); tmpPathPtr->transform(*viewMatrix); viewMatrix = &SkMatrix::I(); pathPtr = tmpPathPtr; } int vertexCount; int indexCount; enum { kPreallocSegmentCnt = 512 / sizeof(Segment), kPreallocDrawCnt = 4, }; SkSTArray<kPreallocSegmentCnt, Segment, true> segments; SkPoint fanPt; if (!get_segments(*pathPtr, *viewMatrix, &segments, &fanPt, &vertexCount, &indexCount)) { continue; } const GrBuffer* vertexBuffer; int firstVertex; size_t vertexStride = quadProcessor->getVertexStride(); QuadVertex* verts = reinterpret_cast<QuadVertex*>(target->makeVertexSpace( vertexStride, vertexCount, &vertexBuffer, &firstVertex)); if (!verts) { SkDebugf("Could not allocate vertices\n"); return; } const GrBuffer* indexBuffer; int firstIndex; uint16_t *idxs = target->makeIndexSpace(indexCount, &indexBuffer, &firstIndex); if (!idxs) { SkDebugf("Could not allocate indices\n"); return; } SkSTArray<kPreallocDrawCnt, Draw, true> draws; create_vertices(segments, fanPt, &draws, verts, idxs); GrMesh mesh; for (int j = 0; j < draws.count(); ++j) { const Draw& draw = draws[j]; mesh.initIndexed(kTriangles_GrPrimitiveType, vertexBuffer, indexBuffer, firstVertex, firstIndex, draw.fVertexCnt, draw.fIndexCnt); target->draw(quadProcessor.get(), mesh); firstVertex += draw.fVertexCnt; firstIndex += draw.fIndexCnt; } } }
void SkIntersections::cubicNearEnd(const SkDCubic& cubic1, bool start, const SkDCubic& cubic2, const SkDRect& bounds2) { SkDLine line; int t1Index = start ? 0 : 3; double testT = (double) !start; // don't bother if the two cubics are connnected static const int kPointsInCubic = 4; // FIXME: move to DCubic, replace '4' with this static const int kMaxLineCubicIntersections = 3; SkSTArray<(kMaxLineCubicIntersections - 1) * kMaxLineCubicIntersections, double, true> tVals; line[0] = cubic1[t1Index]; // this variant looks for intersections with the end point and lines parallel to other points for (int index = 0; index < kPointsInCubic; ++index) { if (index == t1Index) { continue; } SkDVector dxy1 = cubic1[index] - line[0]; dxy1 /= SkDCubic::gPrecisionUnit; line[1] = line[0] + dxy1; SkDRect lineBounds; lineBounds.setBounds(line); if (!bounds2.intersects(&lineBounds)) { continue; } SkIntersections local; if (!local.intersect(cubic2, line)) { continue; } for (int idx2 = 0; idx2 < local.used(); ++idx2) { double foundT = local[0][idx2]; if (approximately_less_than_zero(foundT) || approximately_greater_than_one(foundT)) { continue; } if (local.pt(idx2).approximatelyEqual(line[0])) { if (swapped()) { // FIXME: insert should respect swap insert(foundT, testT, line[0]); } else { insert(testT, foundT, line[0]); } } else { tVals.push_back(foundT); } } } if (tVals.count() == 0) { return; } SkTQSort<double>(tVals.begin(), tVals.end() - 1); double tMin1 = start ? 0 : 1 - LINE_FRACTION; double tMax1 = start ? LINE_FRACTION : 1; int tIdx = 0; do { int tLast = tIdx; while (tLast + 1 < tVals.count() && roughly_equal(tVals[tLast + 1], tVals[tIdx])) { ++tLast; } double tMin2 = SkTMax(tVals[tIdx] - LINE_FRACTION, 0.0); double tMax2 = SkTMin(tVals[tLast] + LINE_FRACTION, 1.0); int lastUsed = used(); if (start ? tMax1 < tMin2 : tMax2 < tMin1) { ::intersect(cubic1, tMin1, tMax1, cubic2, tMin2, tMax2, 1, *this); } if (lastUsed == used()) { tMin2 = SkTMax(tVals[tIdx] - (1.0 / SkDCubic::gPrecisionUnit), 0.0); tMax2 = SkTMin(tVals[tLast] + (1.0 / SkDCubic::gPrecisionUnit), 1.0); if (start ? tMax1 < tMin2 : tMax2 < tMin1) { ::intersect(cubic1, tMin1, tMax1, cubic2, tMin2, tMax2, 1, *this); } } tIdx = tLast + 1; } while (tIdx < tVals.count()); return; }
SkOpSegment* FindChase(SkTDArray<SkOpSpan*>& chase, int& tIndex, int& endIndex) { while (chase.count()) { SkOpSpan* span; chase.pop(&span); const SkOpSpan& backPtr = span->fOther->span(span->fOtherIndex); SkOpSegment* segment = backPtr.fOther; tIndex = backPtr.fOtherIndex; SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle, true> angles; int done = 0; if (segment->activeAngle(tIndex, &done, &angles)) { SkOpAngle* last = angles.end() - 1; tIndex = last->start(); endIndex = last->end(); #if TRY_ROTATE *chase.insert(0) = span; #else *chase.append() = span; #endif return last->segment(); } if (done == angles.count()) { continue; } SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle*, true> sorted; bool sortable = SkOpSegment::SortAngles(angles, &sorted, SkOpSegment::kMayBeUnordered_SortAngleKind); int angleCount = sorted.count(); #if DEBUG_SORT sorted[0]->segment()->debugShowSort(__FUNCTION__, sorted, 0, 0, 0); #endif if (!sortable) { continue; } // find first angle, initialize winding to computed fWindSum int firstIndex = -1; const SkOpAngle* angle; int winding; do { angle = sorted[++firstIndex]; segment = angle->segment(); winding = segment->windSum(angle); } while (winding == SK_MinS32); int spanWinding = segment->spanSign(angle->start(), angle->end()); #if DEBUG_WINDING SkDebugf("%s winding=%d spanWinding=%d\n", __FUNCTION__, winding, spanWinding); #endif // turn span winding into contour winding if (spanWinding * winding < 0) { winding += spanWinding; } #if DEBUG_SORT segment->debugShowSort(__FUNCTION__, sorted, firstIndex, winding, 0); #endif // we care about first sign and whether wind sum indicates this // edge is inside or outside. Maybe need to pass span winding // or first winding or something into this function? // advance to first undone angle, then return it and winding // (to set whether edges are active or not) int nextIndex = firstIndex + 1; int lastIndex = firstIndex != 0 ? firstIndex : angleCount; angle = sorted[firstIndex]; winding -= angle->segment()->spanSign(angle); do { SkASSERT(nextIndex != firstIndex); if (nextIndex == angleCount) { nextIndex = 0; } angle = sorted[nextIndex]; segment = angle->segment(); int maxWinding = winding; winding -= segment->spanSign(angle); #if DEBUG_SORT SkDebugf("%s id=%d maxWinding=%d winding=%d sign=%d\n", __FUNCTION__, segment->debugID(), maxWinding, winding, angle->sign()); #endif tIndex = angle->start(); endIndex = angle->end(); int lesser = SkMin32(tIndex, endIndex); const SkOpSpan& nextSpan = segment->span(lesser); if (!nextSpan.fDone) { // FIXME: this be wrong? assign startWinding if edge is in // same direction. If the direction is opposite, winding to // assign is flipped sign or +/- 1? if (SkOpSegment::UseInnerWinding(maxWinding, winding)) { maxWinding = winding; } segment->markAndChaseWinding(angle, maxWinding, 0); break; } } while (++nextIndex != lastIndex); *chase.insert(0) = span; return segment; } return NULL; }
void GrCCFillGeometry::cubicTo(const SkPoint P[4], float inflectPad, float loopIntersectPad) { SkASSERT(fBuildingContour); SkASSERT(P[0] == fPoints.back()); // Don't crunch on the curve or inflate geometry if it is nearly flat (or just very small). // Flat curves can break the math below. if (are_collinear(P)) { Sk2f p0 = Sk2f::Load(P); Sk2f p3 = Sk2f::Load(P+3); this->appendLine(p0, p3); return; } Sk2f p0 = Sk2f::Load(P); Sk2f p1 = Sk2f::Load(P+1); Sk2f p2 = Sk2f::Load(P+2); Sk2f p3 = Sk2f::Load(P+3); // Also detect near-quadratics ahead of time. Sk2f tan0, tan1, c; get_cubic_tangents(p0, p1, p2, p3, &tan0, &tan1); if (is_cubic_nearly_quadratic(p0, p1, p2, p3, tan0, tan1, &c)) { this->appendQuadratics(p0, c, p3); return; } double tt[2], ss[2], D[4]; fCurrCubicType = SkClassifyCubic(P, tt, ss, D); SkASSERT(!SkCubicIsDegenerate(fCurrCubicType)); Sk2f t = Sk2f(static_cast<float>(tt[0]), static_cast<float>(tt[1])); Sk2f s = Sk2f(static_cast<float>(ss[0]), static_cast<float>(ss[1])); ExcludedTerm skipTerm = (std::abs(D[2]) > std::abs(D[1])) ? ExcludedTerm::kQuadraticTerm : ExcludedTerm::kLinearTerm; Sk2f C0 = SkNx_fma(Sk2f(3), p1 - p2, p3 - p0); Sk2f C1 = (ExcludedTerm::kLinearTerm == skipTerm ? SkNx_fma(Sk2f(-2), p1, p0 + p2) : p1 - p0) * 3; Sk2f C0x1 = C0 * SkNx_shuffle<1,0>(C1); float Cdet = C0x1[0] - C0x1[1]; SkSTArray<4, float> chops; if (SkCubicType::kLoop != fCurrCubicType) { find_chops_around_inflection_points(inflectPad, t, s, C0, C1, skipTerm, Cdet, &chops); } else { find_chops_around_loop_intersection(loopIntersectPad, t, s, C0, C1, skipTerm, Cdet, &chops); } if (4 == chops.count() && chops[1] >= chops[2]) { // This just the means the KLM roots are so close that their paddings overlap. We will // approximate the entire middle section, but still have it chopped midway. For loops this // chop guarantees the append code only sees convex segments. Otherwise, it means we are (at // least almost) a cusp and the chop makes sure we get a sharp point. Sk2f ts = t * SkNx_shuffle<1,0>(s); chops[1] = chops[2] = (ts[0] + ts[1]) / (2*s[0]*s[1]); } #ifdef SK_DEBUG for (int i = 1; i < chops.count(); ++i) { SkASSERT(chops[i] >= chops[i - 1]); } #endif this->appendCubics(AppendCubicMode::kLiteral, p0, p1, p2, p3, chops.begin(), chops.count()); }